문제
QueryDSL을 공부하던 중에 QueryDSL을 이용해서 Join 하는 코드를 개발하는 중이었는데, 이 코드가 제대로 동작하지 않았다.
문제가 되는 코드는 다음과 같았다.
@Test
@DisplayName("조인 Error가 발생함")
void searchErrorTest() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
MemberSearchCondition memberSearchCondition = new MemberSearchCondition();
memberSearchCondition.setUsername("member4");
memberSearchCondition.setAgeGoe(35);
memberSearchCondition.setAgeLoe(40);
memberSearchCondition.setTeamName(teamB.getName());
List<MemberTeamDto> result = memberRepository.search(memberSearchCondition);
assertThat(result).extracting("username").containsExactly("member4");
}
늘 하듯이 GPT에게 캐물었고 이상한 쓸데없는 소리만 늘어놨다. 데이터를 잘못 넣어줬다, 조건에 부합하지 않는다 등등...
그래서 디버깅을 해보기 위해서 application.yaml 파일에 다음과 같이 작성했다.
spring:
jpa:
show-sql: true
properties:
format_sql: true
dialect: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password: ""
logging:
level:
org:
hibernate:
SQL: trace
쿼리를 보니까 내가 의도한 대로 제대로 나가고 있었다. 처음에 디버깅을 할 때는 member1 어쩌구 하길래 응? member1이랑만 비교를 한다는 뜻인가? 라고 생각했지만 그게 아니고 QueryDsl이 Custom으로 별칭을 지정해준 것이었다. 쿼리 내용 👇
select m1_0.member_id,m1_0.username,m1_0.age,t1_0.team_id,t1_0.name from member m1_0 join team t1_0 on t1_0.team_id=m1_0.team_id where m1_0.username=? and t1_0.name=? and m1_0.age>=? and m1_0.age<=?
그래서 나는 search() 메서드의 조건을 하나씩 주석처리 해보기로 했다. 일단 가장 의심이 가는 teamNameEq 부분을 주석처리하니 내가 의도한대로 member4와 같다는 Test 조건에 만족했다.
@Override
public List<MemberTeamDto> search(MemberSearchCondition condition) {
return queryFactory
.select(new QMemberTeamDto(
member.id,
member.username,
member.age,
team.id,
team.name
))
.from(member)
.join(member.team, team)
.where(
usernameEq(condition.getUsername()),
// teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
).fetch();
}
teamNameEq()라는 메서드에 문제가 있는 것을 알았으니 fetch() 전략으로 데이터를 한 번 쭉 뽑아보기로 했다. 내가 team.name이랑 비교하기 위해서 조건을 잘못 설정하고 있는지 확인해보기 위해서였다.
public List<Member> searchAllMembers() {
return queryFactory
.selectFrom(member)
.join(member.team, team)
.fetch();
}
위와 같이 메서드를 작성하고 test코드에서 Debug Point를 걸어보니 아무런 데이터도 잡히지 않았다.
그래서 JPA 기본 내장 findAll() 메서드를 사용해서 확인해보니 member가 4개가 출력되기는 하지만 team이 모두 null로 지정되있었다.
해결
MemberEntity의 changeTeam 메서드를 사용해서 연관관계를 제대로 맵핑을 해줘야지 해당 코드가 정상적으로 동작했다.
👇 changeTeam 메서드
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
👇 의도한대로 동작하는 테스트 코드
@Test
@DisplayName("커스텀 레포지토리 동작 테스트 추가")
void searchTest() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10);
member1.changeTeam(teamA);
Member member2 = new Member("member2", 20);
member2.changeTeam(teamA);
Member member3 = new Member("member3", 30);
member3.changeTeam(teamB);
Member member4 = new Member("member4", 40);
member4.changeTeam(teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
MemberSearchCondition memberSearchCondition = new MemberSearchCondition();
memberSearchCondition.setUsername("member4");
memberSearchCondition.setAgeGoe(35);
memberSearchCondition.setAgeLoe(40);
memberSearchCondition.setTeamName(teamB.getName());
List<MemberTeamDto> result = memberRepository.search(memberSearchCondition);
List<Member> members = memberRepository.findAll();
assertThat(result).extracting("username").containsExactly("member4");
}
👇 Cascade 옵션을 활용해서 좀 더 간략화 한 코드
@Test
@DisplayName("cascade 옵션을 사용한 간략화")
void searchTest2() {
Member member1 = new Member("member1", 10);
Member member2 = new Member("member2", 20);
Member member3 = new Member("member3", 30);
Member member4 = new Member("member4", 40);
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
teamA.addMembers(List.of(member1, member2));
teamB.addMembers(List.of(member3, member4));
teamRepository.save(teamA);
teamRepository.save(teamB);
MemberSearchCondition memberSearchCondition = new MemberSearchCondition();
memberSearchCondition.setUsername("member4");
memberSearchCondition.setAgeGoe(35);
memberSearchCondition.setAgeLoe(40);
memberSearchCondition.setTeamName(teamB.getName());
List<MemberTeamDto> result = memberRepository.search(memberSearchCondition);
assertThat(result).extracting("username").containsExactly("member4");
}
'Spring > Error & Review' 카테고리의 다른 글
[겜문철] Unchecked Exception (0) | 2024.04.20 |
---|---|
[겜문철] Dependency Inversion (0) | 2024.04.13 |
[겜문철] 개발 Log (0) | 2024.03.23 |
통합 테스트의 필요성 (0) | 2024.03.20 |
회원가입 로직 역할과 책임에 맞게 리팩터링 (0) | 2024.03.19 |