λ°°κ²½
μ ν¬ λͺ¨λ½ νλ‘μ νΈμ μλΉμ€ λ‘μ§ κ°λ° μ€, equals λ‘ λΉκ΅ν΄μΌ ν λ‘μ§μ΄ νμνμ΅λλ€. μ‘°κΈ λ ꡬ체μ μΌλ‘λ ν¬νλ₯Ό μμ±ν νΈμ€νΈμ, μμ /μμ λ₯Ό μμ²ν λ©€λ²κ° κ°μμ§ νμΈνλ λ‘μ§μ΄μμ΅λλ€. κ·Έλμ findById λ‘ κ°μ²΄λ₯Ό λΆλ¬μ λΉκ΅λ₯Ό νλ €κ³ νλλ° μνλλλ‘ κ²°κ³Όκ° λμ€μ§ μκ³ κ³μ μΌμΉνμ§ μλλ€λ μλ΅μ λ°μμ΅λλ€. κ·Έλμ equals λ΄λΆμ λλ²κ·Έλ₯Ό μ°μ΄λ³΄λ μ΄μνκ² κ³μ μΈμλ‘ λ°μ κ°μ²΄κ° νλ‘μ κ°μ²΄λ‘ μ μ₯λμ΄ μμμ΅λλ€.
JPA λ νλ‘μ κ°μ²΄λ₯Ό μμμ± μ»¨ν
μ€νΈμ μ μ₯νκ³ μ΄λ₯Ό λ€μν λΆλΆμμ νμ©ν©λλ€. μ΄ λλΆμ DB 컀λ₯μ
μ ν λ λ°μνλ μ±λ₯ μ νλ₯Ό μ€μ΄κ³ , μ΄ κΈ°λ₯μ΄ JPA μ κ°μ₯ κ°λ ₯ν κΈ°λ₯ μ€ νλμ
λλ€. νμ§λ§ μ‘°ν μ μμμ± μ»¨ν
μ€νΈμ μ‘°νλ₯Ό μνλ κ°μ²΄κ° μμΌλ©΄ λ°λμ DB λ₯Ό μ°λ¬ ν΄λΉ κ°μ²΄λ₯Ό κ°μ§κ³ μμΌ νκ³ , μ ν¬κ° λ§λ λ‘μ§μμλ ν΄λΉ κ°μ²΄κ° μμμ± μ»¨ν
μ€νΈμ μλ€κ³ νλ¨νμμ΅λλ€. μ΄μ λλΆμ΄ ν΄λΉ κ°μ²΄λ₯Ό μ‘°ννλ SQL κ΅¬λ¬Έμ΄ λ‘κ·Έμ μ°νμμλ λΆκ΅¬νκ³ κ³μ νλ‘μ κ°μ²΄λ‘ λ¨μμμκ³ , μ΄ κ³Όμ μμ equals λ΄λΆ κ°μ²΄ λΉκ΅ λ‘μ§μμ false λ‘ λ°νλλ κ²μ΄μμ΅λλ€.
λλ체 λ¬Έμ κ° λμ§, λλ²κ·Έλ₯Ό μ°μ΄λ ν΄κ²°μ΄ μλμκ³ μ½μΉλΆλ€κ» λμμ μμ²νμ¬ λ€νν ν΄λΉ λ¬Έμ λ₯Ό ν΄κ²°ν μ μμμ΅λλ€. μ΄ κ³Όμ μμ μκ²λ λ΄μ©κ³Ό ν΄κ²° λ°©λ²μ λν΄ κ³΅μ νκ³ μ ν©λλ€.
κΈ°λ³Έ μΈν
μ ν¬λ ν¬νμ λ©€λ² λλ©μΈ μ¬μ΄μμ λ°μν λ¬Έμ μλλ°, ν΄λΉ λ¬Έμ λ 1:N μ°κ΄κ΄κ³μμ λ°μνλ λ¬Έμ μ΄κΈ° λλ¬Έμ μ‘°κΈ λ 보νΈμ μΈ μμλ₯Ό λ€κ³ μ ν©λλ€. ν¬νλ₯Ό λ©€λ²λ‘ λ°κΎΈκ³ , λ©€λ²λ₯Ό νμΌλ‘ λ°κΎΈμ΄ ν
μ€νΈλ₯Ό μ§ννλ €κ³ ν©λλ€. μ¦ λ©€λ²:ν¬ν=1:N μ κ΄κ³λ₯Ό ν:λ©€λ²=1:N μΌλ‘ λ°κΎΌ μμμ
λλ€.
@Entity
@NoArgsConstructor
@Getter
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
Team team = (Team)o;
return Objects.equals(getId(), team.getId()) && Objects.equals(getName(), team.getName());
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
Java
볡μ¬
λΉκ΅λ₯Ό μν΄ equals λ₯Ό μ¬μ μνμμ΅λλ€.
@Entity
@NoArgsConstructor
@Getter
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Team team;
public boolean isTeam(Team team) {
return this.team.equals(team);
}
}
Java
볡μ¬
μ ν¬κ° λ°μνλ νκ²½κ³Ό λμΌνκ² νκΈ° μν΄ 1:N @ManyToOne μ°κ΄ κ΄κ³λ₯Ό μ€μ νμμ΅λλ€.
@Service
@RequiredArgsConstructor
public class MorakService {
private final MemberRepository memberRepository;
private final TeamRepository teamRepository;
public boolean test(Long memberId, Long teamId) {
Member member = memberRepository.findById(teamId).orElseThrow();
Team team = teamRepository.findById(memberId).orElseThrow();
return member.isTeam(team);
}
}
Java
볡μ¬
member μ team μ μ‘°ννκ³ λΉκ΅ λ‘μ§μ μννμμ΅λλ€.
λ¬Έμ μ§μ νμΈ
λ¬Έμ μ§μ νμΈμ μν΄ ν
μ€νΈλ₯Ό λλ Έμ΅λλ€. λλ―Έ λ°μ΄ν°λ λ€μκ³Ό κ°μ΄ λ£μμ΅λλ€.
Team
id | name |
1 | morak |
Member
id | name | team_id |
1 | eden | 1 |
@Test
@DisplayName("κ°μ νμΈμ§ νμΈνλ€.")
void test() {
//given
Long memberId = 1L;
Long teamId = 1L;
//when
boolean isSameTeam = morakService.test(memberId, teamId);
//then
assertThat(isSameTeam).isTrue();
}
Java
볡μ¬
ν
μ€νΈλ₯Ό λ리면 μ‘°νν ν(id=1)κ³Ό λ©€λ²μ μ μ₯λ ν(id=1)μ΄ κ°μλ° μ€ν¨ν©λλ€.
κ³Όμ
μ°¨κ·Όμ°¨κ·Ό μλΉμ€ λ‘μ§λΆν° λλ²κ·Έλ₯Ό λλ € 보μμ΅λλ€.
μ‘°ν μ΄ν μ°μ λλ²κ·Έμμ member μ team κ³Ό team λ λ€ νλ‘μ κ°μ²΄λ‘ μ€μ λμ΄ μμμ΅λλ€. member μ team μ member μ‘°ν μ fetch type μ lazy λ‘ ν΄μ μ΄ν΄κ° λμμ§λ§, team μ‘°ν μμλ team μ΄ νλ‘μ κ°μ²΄λ‘ μλ κ²μ΄ μ΄μνμ΅λλ€. μ΄ νμμ μΌλ¨ λμ€μ νμΈνλ€ ν΄λ, λ©€λ²μ νκ³Ό μ‘°νν νμ λ λ€ νλ‘μ κ°μ²΄μ΄κ³ κ°μ μ£Όμκ°μ κ°μ§λλ° μ false κ° λνλ¬λμ§ μ΄ν΄κ° λμ§ μμμ΅λλ€.
λ΄λΆ λ‘μ§μΌλ‘ λλ²κ·Έλ₯Ό λ λ€μ΄κ° 보μμ΅λλ€.
β’
member.isTeam(team) λ΄λΆ
νμ¬κΉμ§λ member μ team κ³Ό νλΌλ―Έν°λ‘ λ€μ΄μ¨ team λͺ¨λ κ°μ μ£Όμκ°μ κ°μ§λ λμΌν νλ‘μ κ°μ²΄μ
λλ€.
β’
Override ν team.equals(team) λ΄λΆ
this μΈ βmember μ teamβ μ΄ Team κ°μ²΄λ‘ λ°λμμ΅λλ€. νμ§λ§ νλΌλ―Έν°λ‘ λ€μ΄μ¨ team(λ³μλͺ
o)μ μ¬μ ν νλ‘μ κ°μ²΄μκ³ ,Β getClass() != o.getClass()Β μμ false λ‘ κ±Έλ¬μ§ κ²μ΄μμ΅λλ€.
μ΄λ»κ² λ©μλ νΈμΆμ΄ νλ‘μλ₯Ό μ€μ λ°μ΄ν° κ°μ²΄λ‘ λ§λ€κ² λλ κ²μΌκΉ?
νλ‘μΒ κ°μ²΄μΒ λ©μλκ°Β νΈμΆλλ©΄Β ProxyConfigurationΒ λ΄λΆμΒ InterceptorDispatcherΒ ν΄λμ€μΒ interceptΒ λ©μλκ°Β λ¨Όμ μ€νλ©λλ€.
public interface ProxyConfiguration {
// ...
interface Interceptor {
@RuntimeType
Object intercept(@This Object instance, @Origin Method method, @AllArguments Object[] arguments) throws Throwable;
}
class InterceptorDispatcher {
@RuntimeType
public static Object intercept(
@This final Object instance,
@Origin final Method method,
@AllArguments final Object[] arguments,
@StubValue final Object stubValue,
@FieldValue(INTERCEPTOR_FIELD_NAME) Interceptor interceptor
) throws Throwable {
if ( interceptor == null ) { // 1
if ( method.getName().equals( "getHibernateLazyInitializer" ) ) {
return instance;
}
else {
return stubValue;
}
}
else {
return interceptor.intercept( instance, method, arguments );// 2
}
}
}
}
Java
볡μ¬
1 μμ interceptor κ° null μ΄ μλκΈ°λλ¬Έμ 2κ° μ€νλ©λλ€.
ν΄λΉ interceptorλ ProxyConfiguration.InterceptorΒ μΈν°νμ΄μ€μμΒ μ μΈλ
intercept(instance,Β method,Β arguments)Β λ©μλλ₯ΌΒ νΈμΆλ©λλ€.
ν΄λΉ interceptor μΈν°νμ΄μ€λ₯Ό ꡬνν ν΄λμ€λ μλμ ByteBuddyInterceptor μ
λλ€.
public class ByteBuddyInterceptor extends BasicLazyInitializer implements ProxyConfiguration.Interceptor {
// ...
public Object intercept(Object proxy, Method thisMethod, Object[] args) throws Throwable {
Object result = this.invoke( thisMethod, args, proxy );
if ( result == INVOKE_IMPLEMENTATION ) {
Object target = getImplementation();
final Object returnValue;
try {
if ( ReflectHelper.isPublic( persistentClass, thisMethod ) ) {
if ( !thisMethod.getDeclaringClass().isInstance(target) ) {
// ...
}
returnValue = thisMethod.invoke( target, args );// - 2
}
// ...
return returnValue;
}
catch (InvocationTargetException ite) {
throw ite.getTargetException();
}
}
else {
return result;
}
}
protected final Object invoke(Method method, Object[] args, Object proxy) throws Throwable {
String methodName = method.getName();
int params = args.length;
if (params == 0) {
// ...
} else if (params == 1) {
if (!overridesEquals && "equals".equals(methodName)) {// - 1
return args[0] == proxy;
} else if (method.equals(setIdentifierMethod)) {
initialize();
setIdentifier((Serializable) args[0]);
return INVOKE_IMPLEMENTATION;
}
}
}
}
Java
볡μ¬
interceptor() κ° νΈμΆλλ©΄ λ¨Όμ invoke() λ©μλλ‘ equals λ₯Ό μ€λ²λΌμ΄λ νλμ§ νμΈν©λλ€. μ ν¬λ equals λ₯Ό μ€λ²λΌμ΄λ νκΈ°λλ¬Έμ 1λ²μ !overridesEquals() μμ false κ° λκ³ INVOKE_IMPLEMENTATION μ 리ν΄ν©λλ€.Β μ΄ν getImplementation() μΌλ‘ μμμ± μ»¨ν
μ€νΈμμ μ€μ λ°μ΄ν°λ₯Ό κ°μ§ κ°μ²΄λ₯Ό κ°μ Έμ€κ³ , μμ νΈμΆνΒ λ©μλ(equals)κ° 2λ²μ thisMethod.invoke( target, args ); λ‘ μ€νλ©λλ€.
ν΄κ²° λ°©λ²
1. fetch type EAGER
μ§μ° λ‘λ©μ νμ§ μκ³ λ°λ‘ λΆλ¬μ€κΈ°λλ¬Έμ μ΄νμ λΆλ¬μ€λ team λ νλ‘μ κ°μ²΄κ° μλ μ€μ κ°μ²΄κ° λ©λλ€.
νμ§λ§ ν λ΄λΆ νμ κ²°κ³Ό, κ΅³μ΄ λ§€λ² EAGER λ‘ λΆλ¬μ¬ νμμ±μ λλΌμ§ λͺ»νμ¬ λ€λ₯Έ λ°©μμ μκ°νμμ΅λλ€.
2. find νλ μμλ₯Ό λ°κΎΌλ€.
Team team = teamRepository.findById(memberId).orElseThrow();
Member member = memberRepository.findById(teamId).orElseThrow();
Plain Text
볡μ¬
team μ λ¨Όμ λΆλ¬ ν΄λΉ team μ΄ λ°λ‘ μ€μ κ°μ²΄κ° λλλ‘ ν©λλ€.
νμ§λ§ κ·Όλ³Έμ μΈ ν΄κ²°μ±
μ΄ μλκΈ°λλ¬Έμ λ§€λ² μμλ₯Ό κ³ λ €ν΄μΌ ν©λλ€. κ·Έλμ λ€λ₯Έ λ°©λ²μ λ μ°Ύμ보μμ΅λλ€.
3. equals λ₯Ό override νμ§ μκΈ°
ByteBuddyInterceptor μ invoke() λ©μλμμ !overrideEquals κ° true κ° λμ΄ λ κ°μ²΄ λͺ¨λ νλ‘μ κ°μ²΄λ‘ λ¨μ νλ‘μ κ°μ²΄λΌλ¦¬μ λΉκ΅κ° κ°λ₯ν©λλ€.
μ무리 JPA κ° μμμ± μ»¨ν
μ€νΈλ₯Ό ν΅ν΄ λμΌμ±μ 보μ₯ν μ μλ€μ§λ§ μ μ΄κΆμ΄ JPA μ λμ΄κ°λ κ² κ°μμ΅λλ€.
4. equals override 컀μ€ν
νκΈ°
override ν equals μμ getClass() != o.getClass() λΆλΆμ μ§μμ£Όκ³ !(o instanceof Team) μ λ£μμ΅λλ€. νλ‘μ κ°μ²΄λ μμμ μ΄μ©νμ¬ μμ±λκΈ° λλ¬Έμ instanceof ν€μλλ‘ μΆ©λΆν λ§μ‘±ν μ μμ΅λλ€.
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null !(o instanceof Team)) {
return false;
}
Team team = (Team) o;
return Objects.equals(id, team.getId()) && Objects.equals(name, team.getName());
}
Kotlin
볡μ¬