Life Developer
인생 개발자
전체 글 (141)
[JPA]BatchSize - 전역선언해서 사용하기

쿼리가 N+1이 아니라 딱 테이블 수만큼 맞출수가 있다.

 

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<persistence-unit name="hello">
<properties>
<!-- 필수 속성 -->
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
<property name="hibernate.dialect" value="dialect.MyH2Dialect"/>
<!-- 옵션 -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.use_sql_comments" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<property name="hibernate.default_batch_fetch_size" value="100"/>
</properties>
</persistence-unit>
</persistence>

  Comments,     Trackbacks
[JPA]BatchSize - LAZY로딩 시 몇개를 가져올거냐?

참 골때리는 놈이다.

 

 

String query = "select t from Team t";
List<Team> resultList = em.createQuery(query, Team.class).
setFirstResult(0).setMaxResults(2).getResultList();

System.out.println("resultList = " + resultList.size());
for (Team t : resultList) {
System.out.println("t.getName()+\"|\"+t.getMembers().size()+\"\" = " + t.getName()+"|"+t.getMembers().size()+"");
for (Member m : t.getMembers()) {
System.out.println("m = " + m);
}
}

 

아래는 Team의 Member 리스트 선언.

 

@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

 

만약 이대로 실행하면 맨처음 Team 가져오는 select 쿼리때리고 지연로딩이니까 프록시 가져온 다음,

 

루프 안에서 팀 개수대로 Member select 쿼리를 조질것이다.

 

그럼 결국 총 2번의 member select 쿼리를 조지지.

 

근데 이 조짐의 대상의 수를 조절할수가 있다.ㅋㅋ

 

방법은 BatchSize를 쓰는것이다.

 

@BatchSize(size = 2)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();

 

이렇게 한번에 몇개를 조질지 사이즈를 정하면 한방에 몇개를 가져오느냐를 정할수 있다.

 

사이즈를 2로 했으니 2번 할 작업을 한번만 할거다.

 

실행하면 루프안에서 select 한번만 때린다.

 

사이즈를 1로 하면 어차피 없는거나 다름없으므로 역시 2번을 때린다.

'Developer' 카테고리의 다른 글

[JPA]다형성 JPQL 쿼리  (0) 2020.09.22
[JPA]BatchSize - 전역선언해서 사용하기  (0) 2020.09.22
[JAP]fetch 조인 - 특징과 한계  (0) 2020.09.22
[JPA]JPQL - distinct 중복제거  (0) 2020.09.22
[JAP]fetch 조인 - 한방쿼리  (0) 2020.09.22
  Comments,     Trackbacks
[JAP]fetch 조인 - 특징과 한계

연관된 엔티티들을 SQL 한번으로 조회 - 성능 최적화

 

LAZY 전략보다 우선시 되어 한방에 DB에서 뽑아온다.

 

실무에서는 글로벌 로딩 전략은 모두 지연로딩(LAZY 로딩) 으로 잡고,

 

최적화가 필요한 곳에 이 fetch 조인은 적용한다. (성능 문제 대부분 해결)

 

 

 

fetch 조인 대상에게는 별명을 줄수 없다.

 

근데 하이버네이트는 가능하긴 한데 사용하지 말자.

 

어차피 다 조회하는 한방쿼리인데 그 대상의 속성들에게 별명을 줄 필요도 없고 준다해도 위험함.

 

 

 

그리고 둘 이상의 컨렉션은 fetch 조인을 할수 없다.

 

 

컬렉션을 fetch 조인 하면 페이징을 사용할수가 없다.

 

일대일, 다대일 같은 단일 값 연관 필드들은 fetch 써도 페이징 가능.

 

데이타가 뻥튀기 되기 때문에 컬렉션을 fetch 조인하면 페이징을 절대 못함.

 

일대다 를 뒤집어서 다대일로 바꿔서 페이징 구현할 수도 있음.

 

작동은 해도 개판되는거임. 망함. (하이버네이트는 경고로그를 남기고 메모리에서 페이징함,겁나위험함)

 

 

 

  Comments,     Trackbacks
[JPA]JPQL - distinct 중복제거

String query = "select t from Team t join fetch t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();

System.out.println("resultList = " + resultList.size());
for (Team t : resultList) {
System.out.println("t.getName()+\"|\"+t.getMembers().size()+\"\" = " + t.getName()+"|"+t.getMembers().size()+"");
for (Member m : t.getMembers()) {
System.out.println("m = " + m);
}
}

 

이걸 실행하면 

 

이렇게 나온다.

 

중복값이 생긴다는 것이다.

 

물론 중복값을 일부러 얻을 때도 분명 있을것이다.

 

그럴 경우가 아닐때 제외를 시키고 싶다면 어케할까?

 

쿼리에 distinct를 써주면 된다.

 

String query = "select distinct t from Team t join fetch t.members";

 

그래서 이렇게 바꾸고 db에서 돌아갈 쿼리를 생각해보면 이상하다.

 

db에서는 아래 표에서 모든 컬럼의 값이 일치해야 중복이 제거된다.

 

 

그런데 distinct 를 넣었을 뿐인데 

 

이렇게 나온다...

 

중복제거가 되긴했다.

 

이 의미는 db에서 중복제거를 하지않았고, JPA가 직접 중복제거를 했다는 소리다.

 

SQL의 distinct 만으로는 중복제거를 100% 하지 못한다는 말이다.

 

그렇다! JPA에서 distinct 쿼리가 포함된 JPQL을 보면 같은 식별자를 바라보고 있는 Entity를 대상으로 중복제거를

 

해준다.

 

증말 똑똑한 놈이다.

'Developer' 카테고리의 다른 글

[JPA]BatchSize - LAZY로딩 시 몇개를 가져올거냐?  (0) 2020.09.22
[JAP]fetch 조인 - 특징과 한계  (0) 2020.09.22
[JAP]fetch 조인 - 한방쿼리  (0) 2020.09.22
[JPA]JPQL 기본 함수  (0) 2020.09.21
[JPA]COALESCE와 NULLIF  (0) 2020.09.21
  Comments,     Trackbacks
[JAP]fetch 조인 - 한방쿼리

fetch가 무엇인가?

 

join 할때 한방에 다 가져오는 전략. fetch 전략!

 

Entity만들때 FK를 클래스에서 지정하고 fetch=FetchType~~~어쩌고저쩌고

 

이렇게 쓴적이있다. ex ) @ManyToOne(fetch=FetchType.LAZY)

 

이 fetch는 LAZY, 즉, 지연로딩에 관한 것이었고

 

지금 사용하는 이 fetch는 쿼리문 작성할때 사용한다.

 

아래처럼 join 쿼리 작성시 끼워넣는다.

 

String query = "select m from Member m join fetch m.team";

 

일단 fetch를 사용하지 않고 아래를 실행해보자.

 

Team team = new Team();
team.setName("teamA");
em.persist(team);

Team team1 = new Team();
team1.setName("teamB");
em.persist(team1);

Member member = new Member();
member.setUsername("userA");
member.setAge(10);
member.setType(MemberType.ADMIN);

Member member1 = new Member();
member1.setUsername("userB");
member1.setAge(70);
member1.setType(MemberType.USER);

Member member2 = new Member();
member2.setUsername("userC");
member2.setAge(90);
member2.setType(MemberType.USER);

member.setTeam(team);
member1.setTeam(team1);
member2.setTeam(team1);

em.persist(member);
em.persist(member1);
em.persist(member2);


em.flush();
em.clear();

String query = "select m from Member m";
List<Member> resultList = em.createQuery(query, Member.class).getResultList();

for (Member member : resultList) {
System.out.println("member = " + member.getUsername()+","+member.getTeam().getName());
}

System.out.println(" ======================================================= ");
tx.commit(); //이시점에 DB 쿼리가 날라감

 

 

 

실행을 하면 em.clear(); 이후에 쿼리는 select m from Member m 이때 member만 일단 select 쿼리가 나가게 된다.

 

왜냐하면 Member 에서 Team변수에 지연로딩을 걸어놨기 때문에 조인이 안된 상태로 Member만 select 때린다.

 

이때 proxy 객체를 얻어온다. (그냥 객체가 아니다, 왜냐면 지연로딩이기 때문에 - 지연로딩= 프록시객체 얻어옴)

 

그리고 아래에 member3.getTeam().getName() 을 딱 호출하면 team과 연관된 데이터를 건드렸기 때문에 team select

 

문이 호출되면서 프록시 객체가 없어지고 그냥 일반 객체로 변하게 된다.

 

그러면 teamA에 관한 첫번째 member가 출력이 된다. 만약 teamA가 2명이면 2명의 member가 출력되겠지?

 

그러고 또다시 루프를 돌면서 두번째 member에 대해 member.getTeam().getName() 를 할것이고 이때 Team은

 

teamA가 아니라 teamB라서 또다시 DB에서 쿼리를 가져온다.... 뭔가 복잡하다.

 

이때 teamB는 2명이다. 두번째 member일때 select 쿼리를 때리고 세번째 member는 또다시 teamB이기 때문에

 

영속성 컨텍스트에 이미 담긴 놈을 불러오므로 DB에 쿼리를 안때리는것이다..

 

개어렵노.ㅡㅡ

 

지금 적는것 조차 뭔가 복잡하고 알아듣기 어렵지만... 내 자신은 이해를 완벽히 해서 다행일뿐이다.

 

 

========================================================================

 

결론은 팀이 다른 100명의 멤버를 만약 루프를 돌리면 team select 쿼리를 100번 때리게 된다는 것이다.

 

겁나 비효율적이지 않나?

 

그래서 사용하는게 fetch이다.

 

사용법은 간단하다. 

 

String query = "select m from Member m join fetch m.team";

 

이렇게 하면된다.

 

이렇게 사용하면 join 할때 아예 전체 데이터를 DB에서 가져온다.

 

그래서 실행하면.

 

이렇게 getResultList 할때 딱 한번만 join해서 가져오고 그뒤에 루프돌때는 DB에 안간다.

 

그래서 루프 돌때 team객체는 프록시 객체가 아니다. 그냥 실제 엔티티다.

 

 

이게바로 fetch 조인이다....실무에서 겁나 쓴다. 기억하자

'Developer' 카테고리의 다른 글

[JAP]fetch 조인 - 특징과 한계  (0) 2020.09.22
[JPA]JPQL - distinct 중복제거  (0) 2020.09.22
[JPA]JPQL 기본 함수  (0) 2020.09.21
[JPA]COALESCE와 NULLIF  (0) 2020.09.21
[JPA]조건식 - CASE  (0) 2020.09.21
  Comments,     Trackbacks
[JPA]사용자 정의 함수

하이버 네이트는 사용전 방언에 추가해야 된다.

사용하는 DB의 방언을 상속받고, 사용자 정의 함수를 등록한다.

 

패키지 하나 만들어서 DB의 방언을 상속받았다.

 

그리고 registerFunction을 해준다.

 

 

public class MyH2Dialect extends H2Dialect {
public MyH2Dialect() {
registerFunction("group_concat", new StandardSQLFunction("group_concat", StandardBasicTypes.STRING));
}
}

 

 

 

그다음

 

persistence 파일에 등록해준다.

 

그리고 사용.

 


Team team = new Team();
team.setName("teamA");
em.persist(team);

Member member = new Member();
member.setUsername("userA");
member.setAge(10);
member.setType(MemberType.ADMIN);

Member member1 = new Member();
member1.setUsername("userB");
member1.setAge(70);
member1.setType(MemberType.USER);

member.setTeam(team);
member1.setTeam(team);

em.persist(member);
em.persist(member1);


em.flush();
em.clear();

String query = "select group_concat(m.username) from Member m";
List<String> resultList = em.createQuery(query, String.class).getResultList();

for (String s : resultList) {
System.out.println("s = " + s);
}

System.out.println(" ======================================================= ");
tx.commit(); //이시점에 DB 쿼리가 날라감

  Comments,     Trackbacks
[JPA]JPQL 기본 함수

 

 

'Developer' 카테고리의 다른 글

[JPA]JPQL - distinct 중복제거  (0) 2020.09.22
[JAP]fetch 조인 - 한방쿼리  (0) 2020.09.22
[JPA]COALESCE와 NULLIF  (0) 2020.09.21
[JPA]조건식 - CASE  (0) 2020.09.21
[JPA]서브쿼리  (0) 2020.09.21
  Comments,     Trackbacks
[JPA]COALESCE와 NULLIF

COALESCE

하나씩 조회해서 NULL이 아니면 반환한다. NULL이면 명시한것을 반환

 

 

String query = "select coalesce(m.username,'이름없는 회원') " +
"from Member m ";
List<String> resultList = em.createQuery(query, String.class).getResultList();

 

 

NULLIF

두값이 같으면 null반환, 다르면 첫번째 값 반환

 

 

String query = "select nullif(m.username,'admin') " +
"from Member m ";
List<String> resultList = em.createQuery(query, String.class).getResultList();

'Developer' 카테고리의 다른 글

[JAP]fetch 조인 - 한방쿼리  (0) 2020.09.22
[JPA]JPQL 기본 함수  (0) 2020.09.21
[JPA]조건식 - CASE  (0) 2020.09.21
[JPA]서브쿼리  (0) 2020.09.21
[JPA]JOIN  (0) 2020.09.21
  Comments,     Trackbacks