자바 ORM 표준 JPA 프로그래밍 : 교보문고
자바 ORM 표준 JPA 프로그래밍 - 기본편 : 인프런
Member
@Entity
public class Member
{
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
private MemberType memberType;
Team
@Entity
public class Team
{
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
JPQL
에서 연관관계에 있는 컬럼 조회 시 연관관계 엔티티 까지 영속화하는 JOIN
- 연관관계 Mapping 에서 즉시 로딩
FetchType.EAGER
와 같은 효과를 지닌다.- 지연로딩으로 선언된 연관관계라도 즉시 영속화 한다.
JPQL
을 사용하여FetchType.EAGER
연관관계 컬럼을 조회 시 발생하는N+1
문제를 해결한다,
main
try
{
Address address = new Address("1","1","1");
Team teamA = new Team();
teamA.setName("A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("B");
em.persist(teamB);
Member member1 = new Member();
...
em.persist(member1);
Member member2 = new Member();
...
em.persist(member2);
Member member3 = new Member();
...
em.persist(member3);
em.flush();
em.clear();
String query = "select m from Member m";
List<Member> result = em.createQuery(query,Member.class)
.getResultList();
for (Member m : result) {
System.out.println("Member : "+m.getUsername() + "| TEAM : "+m.getTeam().getName());
}
결과
Hibernate:
/* select
m
from
Member m */ select
member0_.id as id1_0_,
member0_.city as city2_0_,
member0_.street as street3_0_,
member0_.zipcode as zipcode4_0_,
member0_.age as age5_0_,
member0_.memberType as memberTy6_0_,
member0_.TEAM_ID as TEAM_ID8_0_,
member0_.username as username7_0_
from
Member member0_
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
Member : 회원1| TEAM : A
Member : 회원2| TEAM : A
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
Member : 회원3| TEAM : B
Member
에서Team
의 연관관계를LazyLoading
으로 설정하였다.
1.Member
만 조회
2.Member
에서getTeam().getName()
으로Proxy
객체 초기화 진행
3.name
이A
인 팀 초기화를 위해 Query 발생
4.name
이A
인 팀 객체가 영속화 되어 있기 때문에 쿼리 발생 x
5.name
이B
인 팀은 영속화 되어 있지 않기 때문에 영속성 초기화를 위해 Query 발생
N+1 이 발생한다.
이번에는
FETCH JOIN
을 사용해 보자
main : 위의 코드에서 쿼리 부분만 변경String query = "select m from Member m join fetch m.team";
결과
Hibernate: /* select m from Member m join fetch m.team */ select member0_.id as id1_0_0_, team1_.id as id1_3_1_, member0_.city as city2_0_0_, member0_.street as street3_0_0_, member0_.zipcode as zipcode4_0_0_, member0_.age as age5_0_0_, member0_.memberType as memberTy6_0_0_, member0_.TEAM_ID as TEAM_ID8_0_0_, member0_.username as username7_0_0_, team1_.name as name2_3_1_ from Member member0_ inner join Team team1_ on member0_.TEAM_ID=team1_.id Member : 회원1| TEAM : A Member : 회원2| TEAM : A Member : 회원3| TEAM : B
한번의 쿼리에 연관된
Team
데이터를 모두 들고 온다.
Team
과의 연관관계가LazyLoading
임에도 한꺼번에 들고 온다.
결과 값이
Collection
인FETCH JOIN
조회
mainString query = "select t from Team t join fetch t.members where t.name = 'A'"; List<Team> result = em.createQuery(query,Team.class).getResultList(); for (Team t : result) { System.out.println("Member : "+t.getMembers() + "| TEAM : "+t.getName()); }
result
Hibernate: /* select t from Team t join fetch t.members where t.name = 'A' */ select team0_.id as id1_3_0_, members1_.id as id1_0_1_, team0_.name as name2_3_0_, members1_.city as city2_0_1_, members1_.street as street3_0_1_, members1_.zipcode as zipcode4_0_1_, members1_.age as age5_0_1_, members1_.memberType as memberTy6_0_1_, members1_.TEAM_ID as TEAM_ID8_0_1_, members1_.username as username7_0_1_, members1_.TEAM_ID as TEAM_ID8_0_0__, members1_.id as id1_0_0__ from Team team0_ inner join Member members1_ on team0_.id=members1_.TEAM_ID where team0_.name='A' Member : [Member{id=3, username='회원1', age=20, address=org.example.entity.Address@37c36608}, Member{id=4, username='회원2', age=20, address=org.example.entity.Address@5d497a91}]| TEAM : A Member : [Member{id=3, username='회원1', age=20, address=org.example.entity.Address@37c36608}, Member{id=4, username='회원2', age=20, address=org.example.entity.Address@5d497a91}]| TEAM : A
Team
의name
이A
인 조건을 걸었지만 2개의 결과가 출력 된다.
Query
의 결과가 테이블에서는Team
의name
이A
에 해당 되는Member
가 2개 임으로 2개가 출력이 된다.- 중복 제거를
DISTINCT
를 추가하고 엔티티 중복 제거를 수행한다.
(SQL
에서는DISTINCT
는 완전히 값들이 같아야 중복 제거를 한다.
JPQL 의 DISTINCT
는같은 식별자를 가진 Entity 가 발견
될 경우 추가적으로 제거 작업을 수행한다. )
main : distinct 추가
String query = "select distinct t from Team t join fetch t.members where t.name = 'A'";
결과
Hibernate: /* select distinct t from Team t join fetch t.members where t.name = 'A' */ select distinct team0_.id as id1_3_0_, members1_.id as id1_0_1_, team0_.name as name2_3_0_, members1_.city as city2_0_1_, members1_.street as street3_0_1_, members1_.zipcode as zipcode4_0_1_, members1_.age as age5_0_1_, members1_.memberType as memberTy6_0_1_, members1_.TEAM_ID as TEAM_ID8_0_1_, members1_.username as username7_0_1_, members1_.TEAM_ID as TEAM_ID8_0_0__, members1_.id as id1_0_0__ from Team team0_ inner join Member members1_ on team0_.id=members1_.TEAM_ID where team0_.name='A' Member : [Member{id=3, username='회원1', age=20, address=org.example.entity.Address@1c8f6a90}, Member{id=4, username='회원2', age=20, address=org.example.entity.Address@3050ac2f}]| TEAM : A
- 하이버네이트 구현체에는 사용가능하지만 왠만하면 지양
- JPA 객체 그래프 탐색 의도와 맞지 않아 의도치 않은 조작 발생 우려
데이터 중복이 발생
할 수 있기 때문에페이징 사용 불가능
메모리에서 페이징을 시도할 수 도 있음
Batch Size 컬럼을 사용하면 어느정도 성능 처리를 할 수 있다.
Team : members 필드에 @BatchSize(size = 100) 추가@Entity public class Team { @Id @GeneratedValue private Long id; private String name; // @BatchSize(size = 100) 추가 @BatchSize(size = 100) @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>();
main : fetch join 을 사용하지 않고 Paging
String query = "select t from Team t"; List<Team> result = em.createQuery(query,Team.class) .setFirstResult(0) .setMaxResults(2) .getResultList(); for (Team t : result) { System.out.println("Member : "+t.getMembers() + "| TEAM : "+t.getName()); }
결과
Hibernate: /* select t from Team t */ select team0_.id as id1_3_, team0_.name as name2_3_ from Team team0_ limit ? Hibernate: /* load one-to-many org.example.entity.Team.members */ select members0_.TEAM_ID as TEAM_ID8_0_1_, members0_.id as id1_0_1_, members0_.id as id1_0_0_, members0_.city as city2_0_0_, members0_.street as street3_0_0_, members0_.zipcode as zipcode4_0_0_, members0_.age as age5_0_0_, members0_.memberType as memberTy6_0_0_, members0_.TEAM_ID as TEAM_ID8_0_0_, members0_.username as username7_0_0_ from Member members0_ where members0_.TEAM_ID in ( ?, ? ) Member : [Member{id=3, username='회원1', age=20, address=org.example.entity.Address@57f9b467}, Member{id=4, username='회원2', age=20, address=org.example.entity.Address@6d5c2745}]| TEAM : A Member : [Member{id=5, username='회원3', age=20, address=org.example.entity.Address@44b29496}]| TEAM : B
@BatchSize(size = 100)를 통해서
LazyLoading
시 한번에 100개 씩 가져온다.
이것을 사용하면N+1
을 최소화 할 수 있다.
- 보통 batchsize 를 global 세팅하여 사용 ( value 는 1000 이하로 설정 )
<property name="hibernate.default_batch_fetch_size" value="100" />
대부분의 연관관계는
지연로딩
을 사용하고 성능 개선이 필요할 때fetch Join
을 사용