시그마 삽질==six 시그마

JPA 성능 최적화 본문

프로그래밍/JPA

JPA 성능 최적화

Ethan Matthew Hunt 2020. 8. 8. 00:59

JPA는 one:many 일 경우 one에 해당되는   many는 다 보여주는거로 설계함

하이버 네이트에서만 유독 fetch join을 사용해서 many 중에 일부만 가져오게 우회할 수 있긴하다

select t from Team t join fetch t.members m  멤버 5명이 있고

select t from Team t join fetch t.members m  where m.age>10; 해당 조건으로 멤버가  3명으로 줄어버림

이럴거면 차라리 멤버테이블에서 시작해서 조회하는게  맞음.

 

1) ManyToOne   sql을 사용하자

객체끼리 Id로 연결되 있든 연관객체로 되있든  oneToMany 대신 ManyToOne을 사용하면됨.

 

2) oneToMany  컬렉션 조인 정 쓰고 싶으면 객체연관관계말고  Id로 연결시켜서 oneToMany써라

 

3)Many를 Map에 담아 one에 매핑

one to many sql 할꺼면

one을 sql로 불러오고

one keys 추출해서 many를  불러와서 one에 매핑해자

 

4)마지막으로 그냥 @BatchSize 나 @Fetch(FetchMode.SUBSELECT) 사용하자

 

 

 

stackoverflow.com/questions/8695062/jpql-join-where-clause-has-unexpected-effect

   

 

 

 

 

-일반 조인 vs 패치조인

 

일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음  select 프로젝션에 자신만 있음!!!!

페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩) 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념 

 

1.무조건 lazy loading==>n+1문제 =>해결책 fetch join

 

eager는 순수sql 실행후 eager 끌고옴 불피요한 값까지 싹  조회한다.(예상하지 못한 SQL) 뻑날수 있음.

고로 무조건 lazy loading로 하라(연관 엔티티가 실제 사용될 때까지 DB조회를 지연)

일반 JPA는 안그런데(일반 JPA는 모두 한방쿼리로 가져옴) 즉시로딩이든 지연로딩이든 JPQL에서 N+1 문제를 일으킨다. 

@XToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정해야 한다. 

 

N+1문제 : 처음조회한 데이터 수만큼 다시 sql을 사용해서 조회하는 것

 

@Test
    public void testTeam(){
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        Team teamC = new Team("teamC");
        em.persist(teamA);
        em.persist(teamB);
        em.persist(teamC);

        Member member1 = new Member("member1",20,teamA);
        Member member2 = new Member("member2",20,teamA);

        Member member3 = new Member("member3",30,teamB);
        Member member4 = new Member("member4",40,teamB);
        Member member5 = new Member("member5",50,teamC);
        Member member6 = new Member("member6",60,teamC);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
        em.persist(member5);
        em.persist(member6);

        //초기화
        em.flush();
        em.clear();

       //n+1문제 발생 =>해결책 fetch join
       List<Team> teams = em.createQuery("select t from Team t", Team.class).getResultList();

        for (Team t: teams){
            System.out.println("t = " + t);

            boolean beforeLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
            System.out.println("before loaded = " + beforeLoaded);

            System.out.println("t.getMembers() = " + t.getMembers());

            boolean afterLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
            System.out.println("after loaded = " + afterLoaded);

        }
    
    
    
    select
            team0_.team_id as team_id1_2_,
            team0_.name as name2_2_ 
        from
            team team0_
            
            
     select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=1
  


    select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=2
        
        
  
  
      select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=3

2. n+1문제는  fetch join 쓴다. 문제점: 그 결과  one to many 경우 뻥튀기 됨(동일한 one에 many가 많이 붙어서) => 해결책 distinct

(1:다)에 꼭 fetch join 아니더라도 join시 뻥튀기됨!!

 

//fetch join 으로 n+1해결 , 문제점 :그 결과  one to many 경우 뻥튀기 됨 => 해결책 distinct
//(1:다)에 꼭 fetch join 아니더라도 join시 뻥튀기됨!!
        List<Team> teams = em.createQuery("select t from Team t join fetch t.members", Team.class).getResultList();



        for (Team t: teams){
            System.out.println("t = " + t);
            System.out.println("t.getMembers() = " + t.getMembers());
        }
        
        
        
		select
            team0_.team_id as team_id1_2_0_,
            members1_.member_id as member_i1_1_1_,
            team0_.name as name2_2_0_,
            members1_.age as age2_1_1_,
            members1_.team_id as team_id4_1_1_,
            members1_.username as username3_1_1_,
            members1_.team_id as team_id4_1_0__,
            members1_.member_id as member_i1_1_0__ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id

eager든 lazy든  n+1문제 발생한다.

고로 (모든걸 Lazy로딩으로 맞춘 상태에서 ) 연관객체그래프 탐색할때는   fetch join 쓴다.  (n+1해결)

 

select t from Team t; ( 1:다  t.members를 안쓴다면 lazy에서 n+1 미발생)

select m from Member m; (다:1 ,. m.team을 안쓴다면 lazy에서 n+1 미발생)

 

 

 

 

3. ToMany(컬랙션 조회) 는 추가로 distinct 를 넣어준다

 

//distict 사용해서 뻥튀기 잡음. 그래야 페이징 먹힘.

==>해결책 : fetch join 과 distinct를 빼고 1:다 컬럼에 @BatchSize(size = 100) @Fetch(FetchMode.SUBSELECT)

 

 

        //distict 사용해서 뻥튀기 잡음. 그 결과 페이징에서 제대로 안먹힘 ==>해결책 : fetch join 과 distinct를 빼고 1:다 컬럼에  @BatchSize(size = 100) @Fetch(FetchMode.SUBSELECT)
        List<Team> teams = em.createQuery("select distinct t from Team t join fetch t.members", Team.class).getResultList();

        for (Team t: teams){
            System.out.println("t = " + t);
            System.out.println("t.getMembers() = " + t.getMembers());
        }
        
        
        
        
		select
            distinct team0_.team_id as team_id1_2_0_,
            members1_.member_id as member_i1_1_1_,
            team0_.name as name2_2_0_,
            members1_.age as age2_1_1_,
            members1_.team_id as team_id4_1_1_,
            members1_.username as username3_1_1_,
            members1_.team_id as team_id4_1_0__,
            members1_.member_id as member_i1_1_0__ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id

 

 

4. ToMany(컬랙션 조회)  페이징이 들어가면 연관객체가 올바로 나오지 않는다.

 

 

일대일,다대일같은단일값연관필드들은페치조인해도페이징가능

하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험)

firstResult/maxResults specified with collection fetch; applying in memory

 

고로 (lazy상태에서) distinct   fetch join 빼고 

 

방법1 : batch_size 조정해서 해줘라 

 

전역

jpa:

    properties:

        hibernate.default_batch_fetch_size: 100

 

개별

@BatchSize(size = 100)

 

 

방법2 : 페치모드를 서브쿼리로하자

@Fetch(FetchMode.SUBSELECT)

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "team_id")
    private Long id;
    private String name;

    @JsonIgnore
    //@Fetch(FetchMode.SUBSELECT)
    @BatchSize(size = 10)
    @OneToMany(mappedBy = "team")
    List<Member> members = new ArrayList<>();
    public Team(String name) { this.name = name;
    }
}


	 //n+1 문제 테스트!!!
   @Test
    public void testTeam(){
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        Team teamC = new Team("teamC");
        em.persist(teamA);
        em.persist(teamB);
        em.persist(teamC);

        Member member1 = new Member("member1",20,teamA);
        Member member2 = new Member("member2",20,teamA);

        Member member3 = new Member("member3",30,teamB);
        Member member4 = new Member("member4",40,teamB);
        Member member5 = new Member("member5",50,teamC);
        Member member6 = new Member("member6",60,teamC);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
        em.persist(member5);
        em.persist(member6);

        //초기화
        em.flush();
        em.clear();

        //n+1문제 발생 =>해결책 fetch join
       //List<Team> teams = em.createQuery("select t from Team t", Team.class).getResultList();

        //fetch join 으로 n+1해결 , 문제점 :그 결과  one to many 경우 뻥튀기 됨 => 해결책 distinct
        //List<Team> teams = em.createQuery("select t from Team t join fetch t.members", Team.class).getResultList();


        //distict 사용해서 뻥튀기 잡음. 그 결과 페이징에서 제대로 안먹힘 ==>해결책 : fetch join 과 distinct를 빼고 1:다 컬럼에  @BatchSize(size = 100) @Fetch(FetchMode.SUBSELECT)
        //List<Team> teams = em.createQuery("select distinct t from Team t join fetch t.members", Team.class).getResultList();

        //아무문제 없음.
        //List<Team> teams = em.createQuery("select  t from Team t", Team.class).getResultList();

        //번외
        List<Team> teams =em.createQuery("select t from Team t join  t.members m", Team.class).getResultList();

        //번외2
        //List<Team> teams =em.createQuery("select t from Team t join  t.members m where t.name='teamA' and m.id=4", Team.class).getResultList();
        //List<Team> teams =em.createQuery("select t from Team t join  t.members m where t.name='teamA' and m.age=20", Team.class).getResultList();

        //번외3
        //List<Team> teams =em.createQuery("select t from Team t join fetch t.members m where t.name='teamA' and  m.age=20", Team.class)
        // .getResultList();


        for (Team t: teams){
            System.out.println("t = " + t);

            boolean beforeLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
            System.out.println("before loaded = " + beforeLoaded);

            System.out.println("t.getMembers() = " + t.getMembers());

            boolean afterLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
            System.out.println("after loaded = " + afterLoaded);

        }
    }
        
        
		select
            team0_.team_id as team_id1_2_,
            team0_.name as name2_2_ 
        from
            team team0_
            
            
            
select
        members0_.team_id as team_id4_1_1_,
        members0_.member_id as member_i1_1_1_,
        members0_.member_id as member_i1_1_0_,
        members0_.age as age2_1_0_,
        members0_.team_id as team_id4_1_0_,
        members0_.username as username3_1_0_ 
    from
        member members0_ 
    where
        members0_.team_id in (
            1,2,3
        )

 

5. 읽기 전용 쿼리는 @Transactional(readOnly=true)로 하자.

 

플러시할때 일어나는 스냅샷 비교 같은 무거운 로직 수행을 안한다.

 

 

 

 

 

번외1

lazy loading 일반 join으로 호출시

재미있는건 뻥튀기와 N+1이 동시에 발생한다.

N+1은  각 첫번째 many 호출시 발생한다

 

//일반 join으로 할때
List<Team> teams = em.createQuery("select t from Team t join  t.members", Team.class).getResultList();


select
            team0_.team_id as team_id1_2_,
            team0_.name as name2_2_ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id
            
            
            
     select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=1
  


    select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=2
        
        
  
  
      select
        members0_.team_id as team_id4_1_0_,
        members0_.member_id as member_i1_1_0_,
        members0_.member_id as member_i1_1_1_,
        members0_.age as age2_1_1_,
        members0_.team_id as team_id4_1_1_,
        members0_.username as username3_1_1_ 
    from
        member members0_ 
    where
        members0_.team_id=3

 

번외2-1

fetch join 이 아닌 일반 조인의 many 에 조건을 주면  

lazy loading시 

 

뻥튀기는 똑같다.

manywhere 조건대로 추출을 안한다.  

 

 List<Team> teams =em.createQuery("select t from Team t  join  t.members m where t.name='teamA' and m.username='member2'", Team.class).getResultList();
            
	select
            team0_.team_id as team_id1_5_,
            team0_.name as name2_5_ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id 
        where
            team0_.name='teamA' 
            and members1_.username='member2'
            
            select
        members0_.team_id as team_id4_1_1_,
        members0_.member_id as member_i1_1_1_,
        members0_.member_id as member_i1_1_0_,
        members0_.age as age2_1_0_,
        members0_.team_id as team_id4_1_0_,
        members0_.username as username3_1_0_ 
    from
        member members0_ 
    where
        members0_.team_id=1
        
        
        

 

 

번외2-2

 

fetch join 이 아닌 left join  조인의  on을 사용해서 many 에 조건을 주면  

 

lazy loading시 

 

many의 on 무시된다

 List<Team> teams =em.createQuery("select t from Team t left join  t.members m on t.name='teamA' and m.username='member2'", Team.class).getResultList();
 
 
		select
                team0_.team_id as team_id1_5_,
                team0_.name as name2_5_ 
        from
            team team0_ 
        left outer join
            member members1_ 
                on team0_.team_id=members1_.team_id 
                and (
                    team0_.name='teamA' 
                    and members1_.username='member2'
                )
                
                
                    select
        members0_.team_id as team_id4_2_0_,
        members0_.member_id as member_i1_2_0_,
        members0_.member_id as member_i1_2_1_,
        members0_.age as age2_2_1_,
        members0_.team_id as team_id4_2_1_,
        members0_.username as username3_2_1_ 
    from
        member members0_ 
    where
        members0_.team_id=1
        
        
            select
        members0_.team_id as team_id4_2_0_,
        members0_.member_id as member_i1_2_0_,
        members0_.member_id as member_i1_2_1_,
        members0_.age as age2_2_1_,
        members0_.team_id as team_id4_2_1_,
        members0_.username as username3_2_1_ 
    from
        member members0_ 
    where
        members0_.team_id=2
        
        
            select
        members0_.team_id as team_id4_2_0_,
        members0_.member_id as member_i1_2_0_,
        members0_.member_id as member_i1_2_1_,
        members0_.age as age2_2_1_,
        members0_.team_id as team_id4_2_1_,
        members0_.username as username3_2_1_ 
    from
        member members0_ 
    where
        members0_.team_id=3

번외3

 

 List<Team> teams =em.createQuery("select t from Team t join fetch t.members m where t.name='teamA' and m.username='member2'", Team.class).getResultList();


select
            team0_.team_id as team_id1_5_0_,
            members1_.member_id as member_i1_2_1_,
            team0_.name as name2_5_0_,
            members1_.age as age2_2_1_,
            members1_.team_id as team_id4_2_1_,
            members1_.username as username3_2_1_,
            members1_.team_id as team_id4_2_0__,
            members1_.member_id as member_i1_2_0__ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id 
        where
            team0_.name='teamA' 
            and members1_.username='member2'

 

 

 

QueryDsl 로 했을시

@Test
	public void testTeam(){
		queryFactory = new JPAQueryFactory(em);
		Team teamA = new Team("teamA");
		Team teamB = new Team("teamB");
		Team teamC = new Team("teamC");
		em.persist(teamA);
		em.persist(teamB);
		em.persist(teamC);

		Member member1 = new Member("member1",20,teamA);
		Member member2 = new Member("member2",20,teamA);
		Member member3 = new Member("member3",30,teamA);
		Member member4 = new Member("member4",30,teamA);

		Member member5 = new Member("member5",40,teamB);
		Member member6 = new Member("member6",50,teamB);
		Member member7 = new Member("member7",60,teamC);
		Member member8 = new Member("member8",70,teamC);
		em.persist(member1);
		em.persist(member2);
		em.persist(member3);
		em.persist(member4);
		em.persist(member5);
		em.persist(member6);
		em.persist(member7);
		em.persist(member8);

		//초기화
		em.flush();
		em.clear();


		Pageable pageable = PageRequest.of(0,1);

		QueryResults<Team> teamQueryResults = queryFactory
			.select(team)
			.from(team)
			.join(team.members, member)
			.fetchResults();



		PageImpl<Team> teams = new PageImpl<>(teamQueryResults.getResults(), pageable, teamQueryResults.getTotal());
		Page<TeamTest> teamTests = teams.map(team -> new TeamTest(team.getName()));
		for (Team t: teams){
			System.out.println("t = " + t);

			boolean beforeLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
			System.out.println("before loaded = " + beforeLoaded);

			System.out.println("t.getMembers() = " + t.getMembers());

			boolean afterLoaded = em.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(t.getMembers());
			System.out.println("after loaded = " + afterLoaded);

		}
	}
    
    
    
    
    /*select
		team0_.team_id as team_id1_7_,
			team0_.name as name2_7_
		from
		team team0_
		inner join
		member members1_
		on team0_.team_id=members1_.team_id

		select
        members0_.team_id as team_id4_2_0_,
        members0_.member_id as member_i1_2_0_,
        members0_.member_id as member_i1_2_1_,
        members0_.age as age2_2_1_,
        members0_.team_id as team_id4_2_1_,
        members0_.username as username3_2_1_
    from
        member members0_
    where
        members0_.team_id=1

		*/

 

 

		Pageable pageable = PageRequest.of(0,1);


		//applyPagination 사용
		Page<Team> teams = teamTeamRepository.applyPagination(pageable);
		System.out.println("teamTests = " + teams.getContent());
        
        
        /* select
            team0_.team_id as team_id1_7_,
            team0_.name as name2_7_ 
        from
            team team0_ 
        inner join
            member members1_ 
                on team0_.team_id=members1_.team_id limit ?
         */
         
         

'프로그래밍 > JPA' 카테고리의 다른 글

findTopBy, findTopBy  (0) 2020.10.22
트랜잭션과 락 ,2차 캐시  (0) 2020.04.11
JPA- 영속성 관리  (0) 2020.04.10
스프링 데이터 JPA  (0) 2020.04.10
JPA -객체 지향 쿼리언어  (0) 2020.04.09
Comments