JPA에서 @manytoone으로 다른 entity와 join했을 경우 list를 출력하면,
리스트를 한번 조회하고, join column의 id 수만큼 다시 select를 하게된다.
Member.java
@Entity
@Getter
@Setter
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="team_id")
private Team team;
}
MemberRepo.java
public interface MemberRepo extends JpaRepository<Member, Long> {}
Team.java
@Entity
@Getter
@Setter
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
}
TeamRepo.java
public interface TeamRepo extends JpaRepository<Team, Long> {}
test 데이터를 넣고 조회를 해보면
MemberTest.java
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemberTest {
@Autowired
MemberRepo memberRepo;
@Autowired
TeamRepo teamRepo;
@Before
@Transactional
public void setup() {
Team team = new Team();
team.setName("team-1");
team = teamRepo.save(team);
Team team2 = new Team();
team2.setName("team-2");
team2 = teamRepo.save(team2);
Member member = new Member();
member.setName("member-1");
member.setTeam(team);
memberRepo.save(member);
Member member1 = new Member();
member1.setName("member-2");
member1.setTeam(team2);
memberRepo.save(member1);
}
@Test
public void joinTest() {
List<Member> all = memberRepo.findAll();
for (Member member1 : all) {
System.out.println(member1.getTeam().getName());
}
}
}
조회 쿼리를 살펴보면 아래와 같이 member table을 한번 조회하고 team테이블을 2번 조회하여 리스트 결과를 만들어 준다.
@DataJpaTest로 안하고 @SpringBootTest로 한 이유는 @DataJpaTest에 경우 -똑똑한 jpa가 저장한 값을 들고 있어서
조인쿼리를 한방에 만들어 준다. join 테스트를 하고 싶을때는 springbootest로 하길 권장한다.
Hibernate:
select
member0_.id as id1_0_,
member0_.name as name2_0_,
member0_.team_id as team_id3_0_
from
member member0_
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
2019-03-20 00:48:57.513 TRACE 1102 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [14]
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
2019-03-20 00:48:57.516 TRACE 1102 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [15]
지금은 데이터가 2건이라 2번이지만 더 복잡할경우 N+1만큼 조회하는 문제가 생긴다.
이문제를 해결하려면 결과값을 entity가 아닌 새로운 dto에 맵핑해주면 된다.
맵핑하는 방법은 생각외로 간단한데
우선 맵핑할 dto 클래스를 하나 생성한다
MemberMapping.java
public interface MemberMapping {
Long getId();
String getName();
Team getTeam();
}
그리고 MemberRepo에 method를 하나 추가한다.
public interface MemberRepo extends JpaRepository<Member, Long> {
List<MemberMapping> findAllBy();
}
그리고 테스트 코드에 내용을 아래와 같이 변경한다.
@Test
public void joinTest() {
System.out.println("-result entity-------------");
List<Member> all = memberRepo.findAll();
for (Member member1 : all) {
System.out.println(member1.getTeam().getName());
}
System.out.println("--------------------");
System.out.println("-result dto---------");
List<MemberMapping> memberAll = memberRepo.findAllBy();
for (MemberMapping member1 : memberAll) {
System.out.println(member1.getTeam().getName());
}
System.out.println("--------------------");
}
첫번째는 entity를 result로 맵핑하는 데이터고 두번쨰는 우리가 만들어준 mapping dto에 맵핑하는 결과이다.
테스트를 돌려보면 result entity는 기존과 마찬가지로 n+1만큼 team에 대한 조회가 일어나고
result dto는 조인쿼리로 한번만 조회가 된다.
-result entity-------------
2019-03-20 23:42:08.366 INFO 1022 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate:
select
member0_.id as id1_0_,
member0_.name as name2_0_,
member0_.team_id as team_id3_0_
from
member member0_
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
2019-03-20 23:42:08.445 TRACE 1022 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1]
Hibernate:
select
team0_.id as id1_1_0_,
team0_.name as name2_1_0_
from
team team0_
where
team0_.id=?
2019-03-20 23:42:08.448 TRACE 1022 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
team-1
team-2
--------------------
-result dto---------
Hibernate:
select
member0_.id as col_0_0_,
member0_.name as col_1_0_,
team1_.id as col_2_0_,
team1_.id as id1_1_,
team1_.name as name2_1_
from
member member0_
left outer join
team team1_
on member0_.team_id=team1_.id
team-1
team-2
--------------------
위와 같은 방법으로 Interface를 생성하면 entity클래스에서 필요한 값만 리턴해줄수가 있다.
자세한 코드는 아래 github에 있습니다.
https://github.com/barocoding/jpa-manytoone-join-example