사용자 도구

사이트 도구


java:hibernate:gotchas

차이

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

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
java:hibernate:gotchas [2017/11/30 22:17]
kwon37xi [쿼리 로그를 남겨라]
java:hibernate:gotchas [2023/07/03 14:54] (현재)
kwon37xi [to-many 관계에 대해서 fetch join은 안하는게 낫다]
줄 1: 줄 1:
-====== Hibernate/JPA Gotchas ======+====== Hibernate/JPA Gotcha ======
 하이버네이트/JPA에 관해 항상 기억해둬야 할 사항들을 정리해 둔다. 하이버네이트/JPA에 관해 항상 기억해둬야 할 사항들을 정리해 둔다.
  
줄 12: 줄 12:
 따라서 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이 작동하지 않는다.**
줄 93: 줄 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.1512049649.txt.gz · 마지막으로 수정됨: 2017/11/30 22:17 저자 kwon37xi