21.09.08 19:28 작성
·
642
0
안녕하세요 김영한님!
자유주재로 올렸다가 요청해주신것에 따라서 질문으로 옮겼습니다.
현재 spring data jpa로 진행중이며 간략한 엔티티 및 관계 정의는 아래와 같습니다.
Entity A [primary key A1, A2] / LectureType.class
IdClassA [A1, A2]
Entity B [primary key A1, A2, B1] / ManyToOne 단방향 관계, fetch lazy / ExamType.class
IdClassB [IdClassA, B1]
ex) BeforeAll로 A,B 더미 데이터를 저장
@Transactional
@Test
Brepository.findAll();
Brepository.findAll();
B Repository로 findall을 두번호출했을때 ( 다른 코드는 없습니다 )
identifier of an instance of B was altered from BIdClass@90c990a9 BIdClass@21b621d7 와 같이 예외가 발생했습니다. (키 값을 변경하려고 시도한적도 없습니다)
이와 관련해서 제가 개념을 잘못 익힌것인지, 검색 컨셉을 잘못잡은것인지 모르겠지만, 검색해도 잘 안나오더라구요.
각 엔티티의 주키를 string으로 직접 저장하는것 때문인지, 정확한 문제를 모르겠습니다.
공유 링크 : https://drive.google.com/file/d/1fnbHq8i1gcZC0eWFuCYolDnRzwOqio2x/view?usp=sharing
실행 방법 : spring data jpa 강의를 그대로 따라 한것이여서 강의에서 설명해주신것과 실행 방식이 다르지 않습니다. 다만 h2 데이터베이스를 별도로 다운받지 않고,
내장된 것을 사용중입니다. application.yml에 정보가 추가 되어 있습니다.
문제 발생되는곳 : test/java/study/datajpa/domain/exam/ExamTypeRepositoryTest 에서 exam_type_crud_check() 테스트 실행하면 에러 내용을 확인 할 수 있습니다.
하나더 궁금한 것이 있는데 id를 직접 넣어서 할때 강의해주신 Persistable<String>을 사용하면 된다고 하셨는데, 복합키 혹은 식별 관계에 있는 복합키일 경우 IdClass를 String 부분에 넣어주면 되는것일까요??
답변 2
1
2021. 09. 09. 22:28
안녕하세요. KANG MIN SOO님
저도 테스트해보았는데, 하이버네이트 JPQL 쿼리 파서 버그로 보입니다.
우선 어떤 상황에 발생하냐면 정확하게는 다음이 아니라
repository.findAll();
repository.findAll();
다음 상황에 발생합니다.
repository.findAll();
repository.flush();
그러니까 플러시 시점에 문제가 되는 것이지요.
중간에 하이버네이트가 ID 값을 바꾸어버립니다.
그 이유는 바로 다음 때문인데요.
실패
select
exam.exam_type_category as exam_typ1_0_,
exam.lecture_type_category_id as lecture_0_0_,
exam.lecture_type_category_id as lecture_4_0_,
exam.lecture_type_level_id as lecture_5_0_,
exam.created_date as created_2_0_,
exam.exam_type_name as exam_typ3_0_
from
exam_type exam
하이버네이트는 쿼리 시점에 좀 불필요하지만, 연관된 컬럼도 한번 더 조회하는 특징이 있습니다. 그런데 잘 보면 lecture_type_category_id, lecture_type_level_id를 두번 조회해야 하는데, lecture_type_level_id 조회가 한번 빠진 것이 보입니다.
컬럼 이름을 바꾸어서 실행하면 성공하는데 다음을 보시면
-------------
성공
select
exam.exam_type_category as exam_typ1_0_,
exam.a_lecture_type_category_id as a_lectur0_0_,
exam.b_lecture_type_level_id as b_lectur0_0_,
exam.a_lecture_type_category_id as a_lectur4_0_,
exam.b_lecture_type_level_id as b_lectur5_0_,
exam.created_date as created_2_0_,
exam.exam_type_name as exam_typ3_0_
from
exam_type exam
제가 임의로 컬럼이름을 바꾸었는데 보시면 잘 조회하는 것을 확인할 수 있습니다.
a_lecture_type_category_id, b_lecture_type_level_id
a_lecture_type_category_id, b_lecture_type_level_id
이렇게 JPQL 조회 시점에 문제가 발생하는데 이 때문에, 하이버네이트 내부에서 식별자를 다시 생성하고 바꾸는 이상한 과정이 진행됩니다.
이 문제는 사실 일반적으로 발생하는 것은 아니고 제가 확인해보았을 때 다음에서 발생합니다.
1. @IdClass + 2개 이상의 컬럼을 조합한 PK를 연관관계로 사용할 때만 발생
2-1. db 컬럼명의 앞 8자리가 같으면 오류(다음의 경우 오류)
PK1: aaaaaaaax
PK2: aaaaaaaaz
2-2. 숫자 앞의 문자가 같으면 오류(숫자를 기준으로 토큰을 짜르는 것 같습니다. 다음의 경우 오류)
aaa1
aaa2
그래서 다음의 경우 db 컬럼명의 앞 8자리가 같기 때문에 오류가 발생했고,
lecture_type_category
lecture_type_level
다음의 경우 오류가 발생하지 않았습니다.
lt_category
lt_level
정리하면 JPQL 파서와 관련된 하이버네이트 버그로 보입니다. 불편하시겠지만 JPQL 관련 이슈는 해결이 쉽지 않아서 이 부분을 회피하는 방법을 선택하셔야 할 것 같습니다. (아마도 하이버네이트 6 버전이 나와야 해결될 것 같아요)
JPA에서 가장 좋은 식별자 전략은 가급적 복합키를 사용하지 않고, 비즈니스에 의존하지 않는 시퀀스, Identity같은 임의의 단일 값을 사용하는 것입니다.
도움이 되셨길 바래요^^
0
2021. 09. 09. 20:04
이것저것 해보다가 특정 부분을 수정했더니 테스트가 정상적으로 진행되는 부분이 있어서 답변하시는데 좀 더 수월하시지않을까 하고 추가 정보를 올립니다.
기존에는 엔티팅의 id 컬럼 명칭을 아래와 같이 했습니다. 변경전
public class LectureType {
@Column(name = "lecture_type_category")
@Id
private String category;
@Column(name = "lecture_type_level")
@Id
private String level;
@Column(name = "lecture_type_name")
private String name;
@Column(name = "lecture_type_description")
private String description;
@Builder
public LectureType(String category, String level, String name, String description) {
this.category = category;
this.level = level;
this.name = name;
this.description = description;
}
}
public class ExamType {
@Column(name = "exam_type_category")
@Id
private String category;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "lecture_type_category", referencedColumnName = "lecture_type_category"),
@JoinColumn(name = "lecture_type_level", referencedColumnName = "lecture_type_level")
})
@Id
private LectureType lectureType;
@Column(name = "exam_type_name")
private String name;
@Builder
public ExamType(String category, LectureType lectureType, String name) {
this.category = category;
this.lectureType = lectureType;
this.name = name;
}
}
컬럼명칭을 변경했습니다. 변경후
public class LectureType {
@Column(name = "lt_category")
@Id
private String category;
@Column(name = "lt_level")
@Id
private String level;
@Column(name = "lecture_type_name")
private String name;
@Column(name = "lecture_type_description")
private String description;
@Builder
public LectureType(String category, String level, String name, String description) {
this.category = category;
this.level = level;
this.name = name;
this.description = description;
}
}
public class ExamType {
@Column(name = "exam_type_category")
@Id
private String category;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumns({
@JoinColumn(name = "lt_category", referencedColumnName = "lt_category"),
@JoinColumn(name = "lt_level", referencedColumnName = "lt_level")
})
@Id
private LectureType lectureType;
@Column(name = "exam_type_name")
private String name;
@Builder
public ExamType(String category, LectureType lectureType, String name) {
this.category = category;
this.lectureType = lectureType;
this.name = name;
}
}
변경후에는 테스트가 정상적으로 통과했습니다.
2021. 09. 09. 23:46
김영한님이 말씀하셨듯이 일반적이지 않아서
같은 조건인데 어떤 경우는 되고 어떤 경우는 안되서
제가 학습하는 부분에 대해 이해를 잘못하고 있는가 했습니다.
이해하기 쉽게 답변 주셔서 너무 감사드립니다!