사용자 도구

사이트 도구


java:hibernate:gotchas

문서의 이전 판입니다!


Hibernate/JPA Gotchas

하이버네이트/JPA에 관해 항상 기억해둬야 할 사항들을 정리해 둔다.

hashCode와 equals를 항상 구현한다

hashCode와 equals를 항상 구현해야 한다.

이때 DB Primary Key(ID)가 아닌 항상 변치 않고 해당 객체를 대표하는 의미를 가질 수 있는 비즈니스 키(business key)를 hashCode와 equals의 대상으로 삼도록 노력한다. 이유는 Set등에 신규 객체를 넣을 경우 신규 객체는 아직 프라이머리키가 지정되지 않은 상태이기 때문에 ID가 모두 null 혹은 0이며 이 경우 Set에 저장할 때 equals가 항상 true 여서 이전의 값을 뒤에 저장한 값이 계속 덮어써버리는 현상이 발생하기 때문이다.

하지만 실제로 해보면 인공키를 hashCode의 기준으로 삼을 수 밖에 없는 경우도 많다. Set/Map 과 함께 사용할 때 주의를 기울여야한다.

따라서 ID를 equals/hashCode의 기준으로 삼을 경우 매우 주의해야 한다. 관련 참조 - Hibernate equals and hashCode

to-many 관계에 대해서 fetch join은 안하는게 낫다

  • to-many 관계에 대해서 fetch join 을 하면 부모측이 자식측(many)의 갯수만큼 중복으로 결과 갯수가 늘어난다.
  • 이를 해결하려면 distinct 를 꼭 해야한다.
  • to-many 관계에 대해서 limit 을 걸게 되면 그 일반 SQL에서는 자식의 갯수에 대한 limit 이 돼 버린다. 이렇게 되면 실제 조회를 원하는 데이터(parent)의 갯수가 의도대로 나오지 않게 된다.
    • 때문에 하이버네이트는 SQL에 limit을 걸리 않고 limit 없이 전체 데이터를 조회한 뒤에 메모리에서 parent 데이터를 원하는 limit 갯수만큼 끊어서 반환한다.
    • 이는 엄청난 성능저하로 이어진다.
  • 결과적으로 특별한 이유가 없다면 to-many 관계에 대해서는 fetch join을 하지 말고 항상 Hibernate.initialize() 등으로 초기화를 하되 batch size 를 지정하는 식으로 한다. * Hibernate Performance Tuning hibernate.default_batch_fetch_size=30 ===== One-To-One 과 Many-To-One 관계를 조심하라 ===== One-To-One과 Many-To-One 관계에서 One 측은 not null이라고 명시하지 않는이상 Lazy Loading이 작동하지 않는다. 이에 관해서는 JPA One-To-One을 참조한다. ===== 관계 필드에 존재하지 않는 엔티티에 대한 값을 넣지 말라 ===== A → B 관계가 있을 때 A에 있는 B를 가리키는 컬럼에 존재하지 않는 B에 대한 값(특히 0)을 넣으면 A 를 조회할 때 A 자체가 조회가 안된다(Foreign Key가 안 걸려 있으면 존재하지 않는 값 지정이 가능함). 존재하지 않는 값은 null로 넣어야지 0이나 공백 같은 값을 사용하면 안된다. ===== toString 메소드를 조심하라 ===== toString 메소드에서 레이지 로딩으로 지정된 필드를 출력하도록 해서 불필요하게 레이지 로딩 필드가 미리 읽혀지는 문제가 발생할 수 있다. toString 에서 출력값을 주의 깊게 선별해야 한다. 특히 lombok을 사용한다면 매우 주의하라. ===== OpenSessionInView 패턴 사용시 HTML 주석을 사용치 말라 ===== 무엇보다 가급적 OpenSessionInView 패턴을 사용하지 말라. 처음엔 편하지만 나중에는 불명확한 쿼리 실행으로 튜닝이 오히려 어렵고 복잡해진다. toString 메소드에서와 같은 현상이 OpenSessionInView 패턴 사용시 뷰에서 발생할 수 있다. 더이상 필요없는 부분 특히 하이버네이트 도메인 객체를 호출하는 부분을 주석처리할 때 HTML 주석을 사용하지 말라. HTML주석은 서버사이드는 그대로 동작한다. 따라서 레이지 로딩으로 지정한 값을 HTML 주석 부분에서 호출하면 그대로 값이 로딩되어 출력된다. 항상 템플릿 엔진의 주석(JSP의 경우 <%– –%>)을 사용하라.**

개발 환경에서는 쿼리 로그를 남겨라

hibernate.show_sql=true
hibernate.format_sql=true

위 프라퍼티 옵션으로 쿼리 로그를 남길 수 있다. 그러나 hibernate.show_sql=false로 두고 되도록 Log4j 옵션을 사용하는 것이 좋다. Hibernate Log 남기기를 참조한다.

단, 실 운영환경에서는 로그를 남기는 순간 매우 느려지므로 로그 레벨을 높여야 한다.

쿼리에 진짜로 ":"로 들어간다면?

Native SQL에 :를 넣어야 할 필요가 있다면 Escape을 해줘야 한다. java - How can I use MySQL assign operator(:=) in hibernate native query? \\: 형태로 Escape 한다.

SELECT k.`news_master_id` AS id, @ROW \\:= @ROW + 1 AS rownum 
    FROM keyword_news_list k 
    JOIN (SELECT @ROW \\:= 0) r 
    WHERE k.`keyword_news_id` = :kid
ORDER BY k.`news_master_id` ASC

Lazy 자식 컬렉션 2중 insert 문제(children collection insert twice)

  • Lazy @OneToMany 컬렉션에 add(자식Entity)를 하면서 entityManger.merge(부모Entity)를 호출 할 경우 두번 insert 되는 문제가 있다.
  • 이 현상은 @OneToMany 컬렉션이 Lazy Loading일 경우 컬렉션의 내용이 로딩이 안된 상태에서 add를 하면 발생하는 것 같다.
  • 재현
    • 부모 엔티티를 읽어온다. (A a = em.find(A.class, id))
    • 부모측 컬렉션에 자식을 추가한다.(a.getBs.add(new B()))
    • 명시적으로 merge를 호출하여 저장한다. (em.merge(a))
  • 따라서 자식 엔티티를 부모에 추가할 때는
    • 미리 Lazy 컬렉션을 로딩하거나, (Hibernate.initialize(a.getBs())
    • 아니면 부모 엔티티를 수정하지 말고, 자식 엔티티에 부모 엔티티의 관계를 맺고, 따로 insert 하거나
    • 아니면 entityManager.merge(entity)를 수행하지 말고 트랜잭션 종료시에 자동 저장되도록 처리한다.

JPQL Positional Parameter Bug

@Where 에서 true, false 등에 대해 잘못된 반응

  • @OneToMany@org.hibernate.annotations.Where(clause = “deleted = false”)와 같은 조건을 주었을 때, SQL이 잘못 생성된다.
    SELECT ... FROM address address0_ 
    WHERE  ( address0_.deleted=address0_.FALSE) -- 이 부분
      AND address0_.contact_id=?
  • true, false 등이 Dialect에 keyword로 등록이 되어있지 않아서 생기는 현상이다.
  • 조건을 delete = 0 형태로 주거나 DB 종류에 따라 문자열 'true/false'를 true/false로 인식하면 문자열로 사용하는 방식으로 회피 가능하다.
  • 혹은 사용중인 Dialect 에 true, false를 추가한다.
    public class ImprovedHSQLDialect extends HSQLDialect {
     
        public ImprovedHSQLDialect() {
            super();
            registerKeyword("true");
            registerKeyword("false");
            // registerKeyword("unknown");
        }
    }

참조

java/hibernate/gotchas.1578975667.txt.gz · 마지막으로 수정됨: 2020/01/14 13:21 저자 kwon37xi