λͺ©μ°¨
λ°°κ²½
μ ν¬ νλ‘μ νΈμ κΈ°λ₯λ€μ κ·Έλ£Ήμ κΈ°λ°μΌλ‘ μ΄μλ©λλ€. μ΄μ λ°λΌ μμ²ν νκ³Ό λ©€λ²μ μ 보λ₯Ό λλΆλΆμ API μμ λ°κ³ μκ³ μ΄λ₯Ό κ²μ¦νλ λ‘μ§μ΄ μ‘΄μ¬ν©λλ€. λλΆλΆμ μλΉμ€ κ³μΈ΅ λ©μλμμ λ°λ³΅λμ΄ λνλλ ν΄λΉ κ²μ¦ λ‘μ§μ λ°λ³΅μ μ€μ΄κΈ° μν΄ μ΄κΈ°μλ ν΄λμ€ λ΄μ λ©μλλ‘ λΆλ¦¬νλ λ°©μμΌλ‘ ν΄κ²°ν΄ μμ΅λλ€. νμ§λ§ κΈ°λ₯λ€μ΄ μ μ°¨ μΆκ°λλ©΄μ μλΉμ€ κ³μΈ΅μ΄ ν‘μ μΌλ‘ νμ₯μ΄ λμκ³ , λμΌν κ²μ¦ λ‘μ§μ μλ‘μ΄ μλΉμ€ κ³μΈ΅μμ λ°λ³΅ν΄μΌ νλ μν©μ΄ λ°μνμμ΅λλ€. μ΄λλΆν° μ μ ꡬ쑰 λ³κ²½μ νμμ±μ λλΌκ³ μμλλ°, 쿼리 μ±λ₯ νλμ μ§ννλ©΄μ κ²μ¦ λ‘μ§ λ΄ JPA λ©μλλ₯Ό λ³κ²½νμμ΅λλ€. κ·Έλ¬μ μ΄ κ²μ¦ λ‘μ§ λ΄ JPA λ©μλλ₯Ό μ΄μ©νλ λͺ¨λ μλΉμ€ κ³μΈ΅μμ λΉ¨κ°μ€μ΄ κ·Έμ΄μ‘κ³ , νμ¬ κ΅¬μ‘°λ μ μ§ λ³΄μκ° νλ€λ€λ κ²μ κΉ¨λ¬μ ꡬ쑰 λ³κ²½μ κ²°μ¬νμμ΅λλ€.
λ¬Έμ μ
νμ¬ κ΅¬μ‘°κ° κ°μ§λ λ¬Έμ μ μ λ€μκ³Ό κ°μ΅λλ€.
1.
μ 체 μλΉμ€ κ³μΈ΅μμ κ³΅ν΅ λ‘μ§μ΄ λ°λ³΅λλ€.
β’
λͺ¨λ μλΉμ€μμ κ°μ λ‘μ§μ μν΄ ν΄λΉ λ©μλλ₯Ό μ¬μ©μ€μ
λλ€. μ΄μ λ°λΌ κ²μ¦ λ‘μ§ νλκ° λ°λλ©΄ 4κ°μ ν΄λμ€μμ λ°λλ λ§λ²β¦! ν
μ€νΈμͺ½ λ³κ²½μ μμ§ μμλ μν μνλΌλ μ¬ν μ¬μ€,,,
2.
λ§μ½ μλ‘μ΄ κΈ°λ₯μ΄ μΆκ°λλ€λ©΄?
β’
κ²μ¦μ νμν MemberRepository, TeamRepository, TeamMemberRepository λ₯Ό κΈ°λ³Έμ μΌλ‘ μμ‘΄νλ©° λμΌν κ²μ¦ λ‘μ§μ μμ±ν΄μΌ ν©λλ€. λν μλ‘μ΄ API λ§λ€ κ²μ¦ λ‘μ§μ μΆκ°ν΄μΌ νκΈ°λλ¬Έμ μ€μ λΉμ¦λμ€ λ‘μ§μ μ§μ€ν μ μλ μ¬κ±΄μ΄ λ€μ λ¨μ΄μ§λλ€.
λ¬Έμ λ₯Ό μμ½ν΄λ³΄λ©΄ λ κ°μ§ λ¬Έμ λ₯Ό μκ°ν΄λ³Ό μ μλλ°μ. 첫째, κ²μ¦ λ‘μ§μ΄ λ°λλ©΄ ν΄λΉ λ‘μ§μ μ΄μ©νλ λͺ¨λ λ‘μ§μμ μμ μ ν΄μΌ ν©λλ€. μλ₯Ό λ€μ΄ μ¬μ©μλ₯Ό νμΈνλ νμ
μ΄ Long μμ UUID μ κ°μ λ¬Έμμ΄λ‘ λ°λλ©΄ ν΄λΉ λ‘μ§μ μ΄μ©νλ λͺ¨λ λ©μλλ₯Ό μ°Ύμ μΌμΌμ΄ μμ μ ν΄μ£Όμ΄μΌ ν©λλ€. μ΄λ‘ μΈν΄ μ μ§, 보μ μΈ‘λ©΄μμ μ΄λ €μμ κ²ͺμ μ μμ΅λλ€. λμ§Έ, μλ‘μ΄ APIκ° μΆκ°λ λ κ²μ¦ λ‘μ§λ μΆκ°ν΄μΌ νλ€λ μ μ
λλ€. μ΄λ‘ μΈν΄ μλ‘μ΄ λΉμ¦λμ€ λ‘μ§μ λν μ§μ€μ΄ λ¨μ΄μ§κ³ κ°λ
μ± λΆλΆμμλ λΉν¨μ¨μ μ΄μμ΅λλ€. μ΄λ¬ν λ¬Έμ μ λ€μ κ°μ§κ³ μμκΈ°λλ¬Έμ κ°λ
μ± μΈ‘λ©΄μΌλ‘λ μ μ§, 보μ, νμ₯μ μΈ‘λ©΄μΌλ‘λ κ°μ μ΄ νμνλ€κ³ μκ°νμ΅λλ€.
μλΉμ€ κ³μΈ΅ μ€κ³νκΈ°
μμ μΈκΈλλ Έλ€μνΌ, λͺ¨λ½ μλΉμ€μ λͺ¨λ κΈ°λ₯μ νμ κΈ°λ°μΌλ‘ μ 곡λ©λλ€. μ΄μ λ°λΌ λͺ¨λ API μλ μμ²ν νμ μμ²ν μ¬μ©μκ° μνλμ§ νμΈνλ λ‘μ§μ΄ μ‘΄μ¬ν©λλ€. μ΄ κ³Όμ μ μμ½νλ©΄ λ€μκ³Ό κ°μ΅λλ€.
β’
interceptor
β¦
μΈν°μ
ν°μμλ ν ν°μ΄ μ ν¨νμ§ κ²μ¦ν©λλ€.
β’
resolver
β¦
resolver μμλ λ°μ ν ν°μ νμ±νμ¬ payload λ₯Ό νλνκ³ , controller μ νλΌλ―Έν°μμ μꡬνλ νν(Long)λ‘ λ³ννμ¬ μ λ¬ν©λλ€.
β’
controller
β¦
path variable λ‘ λ°μ team code μ νλΌλ―Έν°λ‘ λ°μ user id λ₯Ό μλΉμ€ κ³μΈ΅μΌλ‘ μ λ¬ν©λλ€.
β’
service
β¦
μλΉμ€ κ³μΈ΅μμλ μ¬μ©μκ° μμ²ν νμ μνλμ§ νμΈν λ€ λΉμ¦λμ€ λ‘μ§μ μνν©λλ€.
κΈ°μ‘΄μ μλΉμ€ λ‘μ§μ λ€μκ³Ό κ°μ΅λλ€.
// κΈ°μ‘΄ λ‘μ§
@Transactional(readOnly=true)
public List<PollResponse> findPolls(String teamCode, Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> MemberNotFoundException.of(CustomErrorCode.MEMBER_NOT_FOUND_ERROR, memberId));
Team team = teamRepository.findByCode(teamCode)
.orElseThrow(() -> TeamNotFoundException.ofTeam(CustomErrorCode.TEAM_NOT_FOUND_ERROR, teamCode));
validateMemberInTeam(team, member);
List<Poll> polls = pollRepository.findAllByTeam(team);
return polls.stream()
.map(poll -> PollResponse.from(poll, member))
.sorted()
.collect(Collectors.toList());
}
Java
볡μ¬
κΈ°μ‘΄μλ λ©μλ 첫 λ€μ― μ€μ κ²μ¦ λ‘μ§μκ² μ리λ₯Ό μ보ν΄μΌ νκ³ , κ²μ¦μ΄ λλ μ΄νμ λΉλ‘μ λΉμ¦λμ€ λ‘μ§μ μ§νν μ μμμ΅λλ€. ν λμ λ€μ΄μ€μ§ μλ λΉμ¦λμ€ λ‘μ§μ΄μκ³ , μ΄λ¬ν λ°λ³΅λλ κ²μ¦ λ‘μ§μ΄ μ 체μ μΌλ‘ λ°λ³΅λμ΄ λΆνΈν¨μ΄ κ³μ λμλ κ²μ
λλ€.
κ°μ λ°©ν₯μΌλ‘λ, νμ¬μ κ²μ¦ λ‘μ§μ΄ λ©μλλ‘ λΆλ¦¬(validateMemberInTeam())λμ΄ μλλ°, μ΄λ₯Ό λ©μλμμ κ²μ¦ λ‘μ§ κ°μ²΄ λλ κΈ°λ₯μΌλ‘ νλ¨κ³ μ°¨μμ λνλ³΄κΈ°λ‘ νμμ΅λλ€. ν΄κ²° λ°©λ²μΌλ‘ μκ°ν΄λ³Έ λ°©μμ λ€μ μΈ κ°μ§κ° μμ΅λλ€.
AOP
Spring AOP λ₯Ό μ΄μ©νμ¬, μλΉμ€ κ³μΈ΅μ ν΄λμ€ λ¨μ λλ λ©μλ λ¨μμ AOP λ₯Ό μ μ©νλ κ²μ
λλ€. @Transactional μ 보λ€κ° μκ°μ΄ λ λ°©μμ
λλ€. κ·Έλμ νλ¦λ @Transactional κ³Ό μ μ¬νκ² λμνλλ‘ μλΉμ€ κ³μΈ΅μ λ©μλκ° νΈμΆλκ³ λΉμ¦λμ€ λ‘μ§μ΄ μνλκΈ° μ , κ²μ¦ λ‘μ§μ μννλ κ²μ
λλ€.
μ΄ λ°©λ²μ μ₯μ μΌλ‘λ λΉμ¦λμ€ λ‘μ§μμ κ²μ¦ λ‘μ§ μ½λλ₯Ό μ κ±°ν μ μκ³ , ν΄λμ€, λ©μλ μ΄λλ μ€μ ν μ μμ΅λλ€. λν μ΄λ
Έν
μ΄μ
μ μ΄μ©ν΄ ν΄λμ€ νΉμ λ©μλμ ν΄λΉ λ‘μ§μ΄ λ€μ΄κ°λ€λ κ²μ μ§κ΄μ μΌλ‘ λͺ
μν μ μμ΅λλ€.
νμ§λ§ μκ°ν΄λ³Έ λ¨μ μΌλ‘λ κ³μΈ΅κ° νλ¦ λ°©ν₯κ³Ό κ΄μ¬μ¬ λΆλ¦¬ μ°¨μμ λ¬Έμ κ° μμμ΅λλ€.
κ·Έλ¦Όκ³Ό κ°μ΄ κ³μΈ΅κ° νλ¦ λ°©ν₯μμ λ ν¬μ§ν 리 κ³μΈ΅μ μλΉμ€ κ³μΈ΅μ μμμ νλ², λΉμ¦λμ€ λ‘μ§μμ λ νλ² μμ‘΄νλ λΆλΆμ μμ‘΄μ± νλ¦μ κ΄μ μμ 보μμ λ μ½νμλ€κ³ νλ¨νμμ΅λλ€. λν AOP λ 곡ν΅μ μΌλ‘ λΆκ°λλ κΈ°λ₯μ λ¬Άκ³ λΆλ¦¬νμ¬ λΉμ¦λμ€ λ‘μ§μ μ§μ€ ν μ μλλ‘ λͺ¨λννλ λ°©μμ
λλ€. μ΄λ¬ν κ΄μ μμ βκ²μ¦ λ‘μ§μ΄ μ°λ¦¬μ λΉμ¦λμ€ λ‘μ§κ³Ό λΆλ¦¬ν΄μΌνλ κ΄μ¬μ¬μΈκ°?β λΌκ³ μκ°ν΄ 보μμ λ, @Transactional μ κ΄μ¬μ¬μΈ νΈλμμ
νλκ³Όλ λ¬λ¦¬, κ²μ¦ λ‘μ§λ λΉμ¦λμ€ λ‘μ§ μ€ μΌλΆλΌκ³ μκ°νμμ΅λλ€. μ΄μ λ°λΌ AOP λ°©μμ λ¨μ μ΄ λ€μ λͺ
ννμ¬ μΌλ¨ 보λ₯νμμ΅λλ€.
μΈν°μ ν° νμ©
κΈ°μ‘΄ μΈν°μ
ν°μμ μννλ ν ν°μ μ ν¨μ± κ²μ¦μ νμ₯νμ¬, λ©€λ²κ° νμ μνλμ§μ λν κ²μ¦κΉμ§ λͺ¨λ μννλ κ²μ
λλ€.
μμ AOP λ°©μμ²λΌ μ μ νμ΅μ΄ νμνμ§ μμ κ°λ°μ λΉ λ₯΄κ² ν μ μκ³ , AOP λ°©μκ³Ό λ§μ°¬κ°μ§λ‘ λΉμ¦λμ€ λ‘μ§μμ κ²μ¦ λ‘μ§ μ½λλ₯Ό μ κ±°ν μ μμ΅λλ€.
νμ§λ§ AOP λ°©μλ³΄λ€ λ¨μ μ΄ λμ± λͺ
ννμμ΅λλ€. team code λ url path λ‘ νμΈν μ μκΈ°λλ¬Έμ url path λ¬Έμμ΄μ λν λΆκ°μ μΈ μ²λ¦¬ κ³Όμ μ΄ λ°μν©λλ€. λν home νλ©΄μ²λΌ κ²μ¦ λ‘μ§μ΄ μνλμ§ μλ API κ° μλλ°, ν΄λΉ μμ² APIκ° κ²μ¦ λ‘μ§μ μνν΄μΌ νλμ§ HTTP METHOD μ url path λ‘ νμΈν΄μΌ νλ λ²κ±°λ‘μμ΄ μμ΅λλ€. μ΄μ ν¨κ», λΉμ¦λμ€ λ‘μ§μμλ κ²μ¦ λ‘μ§μ΄ μνλμλ€λ κ²μ μ§κ΄μ μΌλ‘ λνλΌ μ μμμ΅λλ€. μ΄μ λ°λΌ μΈν°μ
ν° λ°©μλ 보λ₯νμμ΅λλ€.
μλ‘μ΄ μλΉμ€ κ³μΈ΅
κ²μ¦λ§ μ λ¬ΈμΌλ‘ νλ μλ‘μ΄ μλΉμ€ κ³μΈ΅μ λ§λ€μ΄ κ²μ¦ λ‘μ§ μ κ²μ¦ μλΉμ€ κ³μΈ΅μμ, μ΄μΈμ λΉμ¦λμ€ λ‘μ§μ κ°κ°μ μλΉμ€ κ³μΈ΅μμ μννλ κ²μ
λλ€. μ΄ λ°©μμ JdbcTemplate μ ꡬ경νλ€κ° μ μ¬ν λ³Έ ν
νλ¦Ώ-μ½λ°± ν¨ν΄ λ°©μμ μ°¨μ©νμμ΅λλ€.
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
// ...
@Nullable
private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(obtainDataSource());
Statement stmt = null;
try {
stmt = con.createStatement();
applyStatementSettings(stmt);
T result = action.doInStatement(stmt);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
String sql = getSql(action);
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("StatementCallback", sql, ex);
}
finally {
if (closeResources) {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
}
}
Java
볡μ¬
JdbcTemplate μ λ©μλ μ€ μμ execute λ©μλλ JdbcTemplate μ λ€λ₯Έ λ©μλμΈ update, execute, query λ±μ΄ νΈμΆλ λ νμ νΈμΆλλ λ©μλμ
λλ€.
κ° update, execute, query λ©μλλ StatementCallback<T> μ λ§λ Functional Interface ꡬν ν΄λμ€λ₯Ό λ§λ€κ³ , μμ execute λ©μλλ statement λ₯Ό λ§λλ κ³΅ν΅ λ‘μ§μ μν ν action.doInStatement(stmt); μ ν΅ν΄ μΈμλ‘ λ°μ λ©μλλ₯Ό μνν©λλ€.
μμ execute λ©μλμ μ μ¬νκ², κ°κ°μ μλΉμ€λ λλ€μμ νμ©νμ¬ λΉμ¦λμ€ λ‘μ§μ λ§λ€κ³ , κ²μ¦ μλΉμ€(Authorization Service)μ λ©μλλ₯Ό νΈμΆν©λλ€. κ·Έλ¦¬κ³ κ²μ¦ μλΉμ€μμλ κ³΅ν΅ λ‘μ§λ§ μν ν, μΈμλ‘ λ°μ λ©μλλ₯Ό μννλ ꡬ쑰λ₯Ό ꡬμνμμ΅λλ€.
β’
JdbcTemplate.execute() == AuthorizationService.authorize()
β’
execute() λ΄λΆμ createStatement() == κ²μ¦ λ‘μ§
β’
action.doInStatement(stmt) == κ°κ°μ λΉμ¦λμ€ λ‘μ§
μΌλ‘ μκ°ν μ μμ΅λλ€.
μ΄λ¬ν ꡬ쑰λ₯Ό κ°μ§λ©΄ κ²μ¦ λ‘μ§μ μννλ€λ κ²μ λͺ
μν μ μμΌλ©΄μ, κ²μ¦ λ‘μ§μ μ¨κΈΈ μ μμ΅λλ€. λν κ³μΈ΅κ° νλ¦ λ°©ν₯λ μλΉμ€ κ³μΈ΅ λ¨μμ λ ν¬μ§ν 리 κ³μΈ΅ λ¨μΌλ‘ νλ₯΄λ λ°©ν₯μΌλ‘ ꡬμ±ν μ μκ³ , κ²μ¦ μλΉμ€μμλ κ°κ°μ μλΉμ€λ₯Ό μ°Έμ‘°νμ§ μμ μλ°©ν₯ μ°Έμ‘° λν λ°μνμ§ μμ΅λλ€. κ·Έλ¦¬κ³ κ²μ¦ λ‘μ§μ νλμ ν΄λμ€μλ§ μμ±νμ¬ μμ§λλ₯Ό λμΌ μλ μμ΅λλ€. μ΄μ λ°λΌ μΈ λ²μ§Έ λ°©μμΈ ν
νλ¦Ώ-μ½λ°± ν¨ν΄μ μ μ©ν μλ‘μ΄ μλΉμ€ κ³μΈ΅μ λ§λ€κΈ°λ‘ κ²°μ νμμ΅λλ€.
μλΉμ€ κ³μΈ΅ μ¬μ€κ³ μ μ©
μλΉμ€ κ³μΈ΅μ μ μ©ν ꡬ쑰λ μμ μ 리ν κ·Έλ¦Όμ ν λλ‘ μ μ©νλλ‘ νκ² μ΅λλ€.
Authorization Service
@Transactional(readOnly=true)
public class AuthorizationService {
private final TeamRepository teamRepository;
private final MemberRepository memberRepository;
private final TeamMemberRepository teamMemberRepository;
public <R> R withTeamMemberValidation(Supplier<R> supplier, String teamCode, Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> MemberNotFoundException.of(CustomErrorCode.MEMBER_NOT_FOUND_ERROR, memberId));
Team team = teamRepository.findByCode(teamCode)
.orElseThrow(() -> TeamNotFoundException.ofTeam(CustomErrorCode.TEAM_NOT_FOUND_ERROR, teamCode));
validateMemberInTeam(team, member);
return supplier.get();
}
Java
볡μ¬
κ²μ¦ μλΉμ€μμλ ν, λ©€λ², νλ©€λ² λ ν¬μ§ν λ¦¬λ§ μ£Όμ
λ°κ³ , κ²μ¦κ³Ό κ΄λ ¨λ λ‘μ§λ§ μνν©λλ€. κ²μ¦ λ‘μ§μ μνν νμλ μΈμλ‘ λ€μ΄μ¨ λ©μλλ₯Ό μ€νλ§ νκ³ , μ΄μΈμ λΉμ¦λμ€ λ‘μ§μλ κ°μνμ§ μλλ‘ νμμ΅λλ€. ν¨μν μΈν°νμ΄μ€λ‘λ μΈμ μμ΄ λ¦¬ν΄ κ°λ§ μλ Supplier λ₯Ό μ¬μ©νμκ³ , μ¬λ¬ μλΉμ€ κ³μΈ΅μμ μ¬μ©ν μ μλλ‘ μ λ€λ¦μ νμ©νμμ΅λλ€. ν¨μν μΈν°νμ΄μ€λ‘ μ¬μ©λλ λ€λ₯Έ μΈν°νμ΄μ€μ λν λ΄μ©μ Functional Interfaceλ μ μ°Έκ³ ν΄μ£ΌμΈμ!
Poll Service
@Transactional(readOnly=true)
public List<PollResponse> findPolls(String teamCode, Long memberId) {
return authorizationService.withTeamMemberValidation(
() -> pollRepository.findAllByTeamCode(teamCode)
.stream()
.map(poll -> PollResponse.from(memberId, poll))
.sorted()
.collect(Collectors.toList()),
teamCode,
memberId
);
}
Java
볡μ¬
κ²μ¦ λ‘μ§μ μλΉμ€μμ λΆλ¦¬ν¨μΌλ‘μ¨ κ°κ°μ λλ©μΈ μλΉμ€ κ³μΈ΅μμλ λΉμ¦λμ€ λ‘μ§μ λμ± μ§μ€ν μ μκ² λμμ΅λλ€. μλΉμ€ κ³μΈ΅μμλ μΈμλ‘ λ€μ΄μ¨ teamCode μ memberId μ λν κ²μ¦μ κ²μ¦ μλΉμ€ κ³μΈ΅μ 맑기기 μν΄ λ©μλμ νλΌλ―Έν°λ‘ μ λ¬νκ³ , λΉμ¦λμ€ λ‘μ§μ λλ€μμΌλ‘ ꡬμ±νμμ΅λλ€.
κ²°κ³Ό
κ²μ¦ λ‘μ§μ μμ ν
νλ¦Ώ-μ½λ°± ν¨ν΄ λ°©μμΌλ‘ λ³κ²½νλ©΄μ λ€μκ³Ό κ°μ μ΄μ μ κ°μ§ μ μμμ΅λλ€.
1.
κ°λ
μ± μ¦λ
κ²μ¦μ μν΄ orElseThrow() κΉμ§ λ μ€μ© μ΄μ΄μ§λ team, member μ‘°ν λ‘μ§μ μ κ±°νκ³ , κ²μ¦ μλΉμ€μ withTeamMemberValidation μ΄λΌλ λ©μλλͺ
μΌλ‘ κ²μ¦ λ‘μ§μ ννν μ μμ΄ κ°λ
μ± μΈ‘λ©΄μμ κ°μ μ΄ λμμ΅λλ€.
2.
λΉμ¦λμ€ λ‘μ§μ μ§μ€
κ²μ¦ λ‘μ§μ κ²μ¦ μλΉμ€μ μΌμνκ³ , μλΉμ€ κ³μΈ΅ λ©μλμμλ μ½λ°± λ°©μμ λΉμ¦λμ€ λ‘μ§λ§ μμ΄ λΉμ¦λμ€ λ‘μ§μλ§ μ§μ€ν μ μμμ΅λλ€.
3.
ν‘μ μΌλ‘ λͺ¨λ λλ©μΈ μλΉμ€ κ³μΈ΅μμ λ°μνλ κ²μ¦ λ‘μ§μ ν κ³³μΌλ‘ μ§μ€
νλΌλ―Έν° λ³κ²½ λ± κ²μ¦ λ‘μ§μ λ³κ²½μ΄ λ°μνμ¬λ κ²μ¦ μλΉμ€ λ΄λΆμ κ²μ¦ λ‘μ§μμλ§ λ³κ²½μ΄ λ°μνκΈ°λλ¬Έμ μ μ§λ³΄μ λ± κ΄λ¦¬ μΈ‘λ©΄μμλ μ΄μ μ΄ μμμ΅λλ€.