๋ฐฐ๊ฒฝ
์ ํฌ ๋ชจ๋ฝ ํ๋ก์ ํธ์ ์๋น์ค ๋ก์ง ๊ฐ๋ฐ ์ค, 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
๋ณต์ฌ