사용자 도구

사이트 도구


java:hibernate:gotchas

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
java:hibernate:gotchas [2017/11/30 22:11]
kwon37xi [OpenSessionInView 패턴 사용시 HTML 주석을 사용치 말라]
java:hibernate:gotchas [2023/07/03 14:54] (현재)
kwon37xi [to-many 관계에 대해서 fetch join은 안하는게 낫다]
줄 1: 줄 1:
-====== Hibernate/JPA Gotchas ======+====== Hibernate/JPA Gotcha ======
 하이버네이트/JPA에 관해 항상 기억해둬야 할 사항들을 정리해 둔다. 하이버네이트/JPA에 관해 항상 기억해둬야 할 사항들을 정리해 둔다.
  
줄 5: 줄 5:
 hashCode와 equals를 항상 구현해야 한다. hashCode와 equals를 항상 구현해야 한다.
  
-이때 DB 프라이머리키(ID)가 아닌 **항상 변치 않고 해당 객체를 대표하는 의미를 가질 수 있는 비즈니스 키(business key)를 hashCode와 equals의 대상으로 삼아야 한다.**+이때 DB Primary Key(ID)가 아닌 **항상 변치 않고 해당 객체를 대표하는 의미를 가질 수 있는 비즈니스 키(business key)를 hashCode와 equals의 대상으로 삼도록 노력한다.**
 이유는 Set등에 신규 객체를 넣을 경우 신규 객체는 아직 프라이머리키가 지정되지 않은 상태이기 때문에 ID가 모두 ''null'' 혹은 ''0''이며 이 경우 Set에 저장할 때 equals가 항상 true 여서 이전의 값을 뒤에 저장한 값이 계속 덮어써버리는 현상이 발생하기 때문이다. 이유는 Set등에 신규 객체를 넣을 경우 신규 객체는 아직 프라이머리키가 지정되지 않은 상태이기 때문에 ID가 모두 ''null'' 혹은 ''0''이며 이 경우 Set에 저장할 때 equals가 항상 true 여서 이전의 값을 뒤에 저장한 값이 계속 덮어써버리는 현상이 발생하기 때문이다.
 +
 +하지만 실제로 해보면 인공키를 hashCode의 기준으로 삼을 수 밖에 없는 경우도 많다. Set/Map 과 함께 사용할 때 주의를 기울여야한다.
  
 따라서 ID를 equals/hashCode의 기준으로 삼을 경우 매우 주의해야 한다. 관련 참조 - [[http://community.jboss.org/wiki/EqualsAndHashCode|Hibernate equals and hashCode]] 따라서 ID를 equals/hashCode의 기준으로 삼을 경우 매우 주의해야 한다. 관련 참조 - [[http://community.jboss.org/wiki/EqualsAndHashCode|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 를 지정하는 식으로 한다.**
 +    * [[java:hibernate:performance|Hibernate Performance Tuning]] ''hibernate.default_batch_fetch_size=30''
 +    * ''default_batch_fetch_size''를 지정하면 N+1 이 아니라 N 번 조회할 것은 in 쿼리로 batch_size 만큼의 인자를 넣어서 한 방에 조회한다.
 +  * [[java:hibernate:configuration|Hibernate Configurations]] - ''hibernate.query.fail_on_pagination_over_collection_fetch=true'' 옵션을 켜서 미리 에러를 내고 확인해서 고쳐두는게 좋다.
 ===== One-To-One 과 Many-To-One 관계를 조심하라 ===== ===== One-To-One 과 Many-To-One 관계를 조심하라 =====
 One-To-One과 Many-To-One 관계에서 One 측은 ''not null''이라고 명시하지 않는이상 **Lazy Loading이 작동하지 않는다.** One-To-One과 Many-To-One 관계에서 One 측은 ''not null''이라고 명시하지 않는이상 **Lazy Loading이 작동하지 않는다.**
줄 30: 줄 42:
 항상 **템플릿 엔진의 주석(JSP의 경우 <%-- --%>)을 사용하라.** 항상 **템플릿 엔진의 주석(JSP의 경우 <%-- --%>)을 사용하라.**
  
-===== 쿼리 로그를 남겨라 =====+===== 개발 환경에서는 쿼리 로그를 남겨라 =====
 <code properties> <code properties>
 hibernate.show_sql=true hibernate.show_sql=true
줄 38: 줄 50:
 [[java:hibernate:log|Hibernate Log 남기기]]를 참조한다. [[java:hibernate:log|Hibernate Log 남기기]]를 참조한다.
  
 +단, 실 운영환경에서는 로그를 남기는 순간 매우 느려지므로 로그 레벨을 높여야 한다.
 ===== 쿼리에 진짜로 ":"로 들어간다면? ===== ===== 쿼리에 진짜로 ":"로 들어간다면? =====
 Native SQL에 '':''를 넣어야 할 필요가 있다면 Escape을 해줘야 한다. Native SQL에 '':''를 넣어야 할 필요가 있다면 Escape을 해줘야 한다.
줄 90: 줄 103:
   * [[https://hibernate.atlassian.net/browse/HHH-2775|[HHH-2775] true and false are not escaped in where attributes (@Where) - Hibernate JIRA]]   * [[https://hibernate.atlassian.net/browse/HHH-2775|[HHH-2775] true and false are not escaped in where attributes (@Where) - Hibernate JIRA]]
  
 +===== SQL / JPQL / HQL Keyword "in", "by" 같은 Keyword 가 JPA Entity package 에 들어가면 파싱 오류 발생 =====
 +  * [[https://stackoverflow.com/questions/29230286/spring-jpa-in-word-in-package-name-of-entity-class-results-in-jpql-error|java - Spring JPA - "in" word in package name of Entity Class - Results in JPQL Error]]
 +  * [[https://hibernate.atlassian.net/browse/HHH-11784|[HHH-11784] HQL query fails if entity is inside a package which starts with "in" or "by" - Hibernate JIRA]] 6.0 에서 해결 될 수도 있음.
 +  * Entity 의 package name 에 ''in''이 들어 있을 경우, 이를 가지고 full package name으로 JPQL을 생성하면 ''in''을 JPQL Keyword로 인식해서 동작하지 않는 현상이 발생했다. 보통, join 같은 복잡한 쿼리에서 발생했다.
 +  * **패키지 이름에서 ''in'' 같은 JPQL, SQL Keyword 단어가 안들어가게** 할 것.
 +  * [[https://www.mkyong.com/hibernate/how-to-use-database-reserved-keyword-in-hibernate/|How to use database reserved keyword in Hibernate?]] : ''@Column(name="`desc`")'' 처럼 DB에 적합한 escape 문자 써 줄것.
 +
 +===== null id in entry =====
 +> HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: null id in ''com.mycompany.XXX'' entry (don't flush the Session after an exception occurs)
  
 +  * [[https://stackoverflow.com/questions/10855542/org-hibernate-assertionfailure-null-id-in-entry-dont-flush-the-session-after|mysql - org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs) - Stack Overflow]]
 +  * 핵심은 **"동일 트랜잭션 안에 저장이 실패한, 혹은 나도 모르게 직접 쿼리를 통해  DB에서 삭제된 Entity가 EntityManager 1차 캐시에 남아있는 상황"은 존재해서는 안된다.**
 +  * 하나의 트랜잭션에서
 +    * 쓰기 작업시 에러가 발생해서 ''XXX'' 엔티티가 저장이 안됐는데, 이를 ''try/catch'' 블록에서 잡아서 그 후속 작업을 진행했거나,
 +    * 혹은, entity 삭제가 아니 직접 쿼리로 엔티티를 삭제하거나 한 상황에서
 +    * ''Session/EntityManager'' 입장에서는 잘못된 Entity 가 여전히 1차 캐시에 존재하는 상태이기 때문에
 +    * 나중에 flush 가 일어날 때 잘못된 Entity 혹은 DB상에 존재하지도 않지만 기존에 존재했던 것처럼 표시된 Entity 때문에 오류가 발생하게 된다.
 +  * 쓰기 작업과 그 후속 읽기 작업 트랜잭션을 분리해서 ''Session/EntityManager''가 쓰레기 엔티티를 갖지 않게 하거나
 +  * 혹은 직접 쿼리를 통한 삭제 작업의 경우에는 ''session.clear()'' 혹은 ''entityManager.clear()''를 통해 쓰레기 Entity 를 1차 캐시에서 모두 제거해야 한다.
 ===== 참조 ===== ===== 참조 =====
   * [[http://www.devinprogress.info/2011/08/hibernate-gotchas.html|Development in progress...: Hibernate Gotchas!]]   * [[http://www.devinprogress.info/2011/08/hibernate-gotchas.html|Development in progress...: Hibernate Gotchas!]]
   * 기타 직접 겪은 것들을 정리한다.   * 기타 직접 겪은 것들을 정리한다.
java/hibernate/gotchas.1512049282.txt.gz · 마지막으로 수정됨: 2017/11/30 22:11 저자 kwon37xi