티스토리 뷰
JPA 에서 엔티티간에 연관관계를 맺을시 연관관계의 주인이 되는 엔티티에는 @JoinColumn 애노테이션을 이용해 FK 를 설정한다. @JoinColumn 에는 여러 프로퍼티들이 있는데 이중 name 에는 해당 엔티티의 컬럼명을, referencedColumnName 에는 상대방 엔티티의 컬럼명을 적는다. referencedColumnName 을 명시적으로 넣어주지 않으면 디폴트 옵션으로 상대방의 primary key 에 해당하는 컬럼을 이용하게된다.
@Entity
@Table(name = "person")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "person_id")
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "child_id")
private Child child;
@Column(name = "person_name")
private String name;
}
@Entity
@Table(name = "child")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Child {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "child_id")
private Long id;
@Column(name = "child_name")
private String name;
}
Person 과 Child 는 아주 간단한 엔티티들이며 두 엔티티는 1:1 관계를 갖고있고, (명시적으로 설정되지는 않았지만) FK 는 child_id 를 이용하고있다.
public interface PersonRepository extends JpaRepository<Person, Long> {
@Override
@EntityGraph(attributePaths = "child")
List<Person> findAll();
}
@DataJpaTest
public class PersonRepositoryTest {
@Autowired
private PersonRepository personRepository;
@Autowired
private ChildRepository childRepository;
@Autowired
private EntityManager em;
@Test
void test() {
Child c = new Child(null, "child");
Person p = new Person(null, c, "person");
childRepository.save(c);
personRepository.save(p);
em.flush();
em.clear();
personRepository.findAll();
}
}
join fetch 를 이용하도록 @EntityGraph 를 이용했고, 테스트코드에서 찍히는 SQL 로그를 보면 join 을 이용한 쿼리가 실행되는걸 볼 수 있다. 즉 아무런 문제가 없다.
@Entity
@Table(name = "person")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Person {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "person_id")
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "person_code", referencedColumnName = "child_code")
private Child child;
@Column(name = "person_name")
private String name;
}
@Entity
@Table(name = "child")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Child {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "child_id")
private Long id;
@Column(name = "child_name")
private String name;
@Column(name = "child_code")
private String code;
}
child_id 를 이용해 맺었던 연관관계에서, code 를 이용해 연관관계를 맺도록 변경했다. code 는 테이블 내에서 unique 하지만 특정 테이블의 기본키는 아니다.
두 엔티티의 연관관계만 변경해주고 테스트코드를 그대로 실행시키면 에러가 발생하게된다.
class com.yong.firstspring.sample.Child cannot be cast to class java.io.Serializable
사실 에러메세지가 너무 간단하고 명확해 문제를 해결하기는 쉽다. 연관관계의 대상이 되는 Child 에 Serializable 인터페이스를 구현하도록하면 된다.
문제를 해결하는건 어렵지않은데 왜 Serializable 을 구현해야하는지 궁금했다. 꽤 다양한 정보를 찾아봤다고 생각하고있는데, 일단 JPA 와 하이버네이트 스펙상 Entity 에 Serializable 을 구현하는건 필수가 아니다. 다만 선택적으로 필요한 순간에는 구현하도록 되어있는데 그 필요한 순간이라는게 상당히 추상적이다. "원격으로 사용되는 경우 Serializable 을 구현하라" 정도로 요약할 수 있는데 이건 친절한 문서라고 보기 어렵지 않나 라는 생각이 든다. 결국 "직렬화해야할때는 Serializable 을 구현하세요" 정도의 매우 당연한 말이기때문이다.
- JPA document 발췌 If an entity instance is passed by value as a detached object, such as through a session bean’s remote business interface, the class must implement the Serializable interface. - Hibernate document 발췌 If an entity instance is to be used remotely as a detached object, the entity class must implement the Serializable interface. |
그러다가 그나마 공식적인걸 하나 찾을 수 있었는데 hibernate document ( https://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#entity-mapping-association )의 2.2.5.1 절에서 찾았다.
다만 여기도 왜 해야하는지까지는 나와있지 않다. 문서에 누락됐거나 내가 못찾은게 아니라면 JPA/Hibernate 문서와 조합해볼때 referencedColumnName 을 이용하는 경우엔 Hibernate 내부에서 직렬화를 하도록 구현되어있는게 아닐까 추측해볼 수 있다.
이걸 찾다가 느낀건데 JPA 나 Hibernate 는 문서보기가 참 힘들다. 위에 referencedColumnName 관련 문서도 여기저기 뒤지다가 우연히 발견한거지 Hibernate 공식 페이지에서 저 문서에 접근하는 법은 못찾았다;;
처음엔 이유가 궁금했던 것도 있고, 혹시 Hibernate 버그가 아닌가 싶어서 찾아봤던건데 여튼 몇시간 찾아본 결과 공식문서에 나와있는 구문을 발견했으니 기본키가 아닌 컬럼을 이용할때는 Serializable 을 구현하도록 해야겠다.
참고자료
https://www.inflearn.com/questions/16570
https://vladmihalcea.com/how-to-map-a-manytoone-association-using-a-non-primary-key-column/
'Java > jpa' 카테고리의 다른 글
spring boot 2.x hibernate 5.x 에서 매핑키만 있고 데이터는 없는 경우 전체 엔티티 조회 실패 (0) | 2024.04.30 |
---|---|
Entity Merge 시에는 동일한 객체를 반환하지 않는 이유 (0) | 2023.05.07 |
@DynamicUpdate 는 언제 써야할까 (0) | 2023.01.10 |
JPA 양방향 연관관계 매핑 (0) | 2020.04.25 |
JPA 조회조건 객체 매핑 (0) | 2017.12.15 |
- Total
- Today
- Yesterday
- Jackson
- TEST
- generics
- servlet
- JPA
- java
- Kotlin
- javascript
- mariadb
- frontend개발환경
- frontcode
- 정규표현식
- Design Pattern
- Git
- Spring
- MySQL
- toby
- DesignPattern
- java8
- OOP
- db
- EffectiveJava
- JavaScript Core
- code
- http
- clean code
- go-core
- backend개발환경
- spring cloud
- programming
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |