JPA Composite Primary Key
JPA Composite Primary Key
JPA에서 Primary Key가 단일 키 일때는 @ID annotation으로 쉽게 가능하였다.
하지만 실무에서는 복합키인 경우도 많고 JPA에서는 이 기능을 지원하기 때문에 학습해 보았다.
크게 두 가지 방식이 있는데 @IdClass를 사용하는 방법과 @EmbeddedId를 사용하는 방법에 대해 알아보겠다.
참고 : 복합키의 4가지 조건
복합키를 구성 하기 위한 필수 조건이 있다.
- @EmbeddedId or @IdClass 의 annotation을 붙여야한다.
- public 의 no-args constructor 가 있어야 한다.
- Serializable 를 implement 받아야 한다.
- equals() 와 hashCode() method를 override해야 한다. (Id Class도)
- (복합키의 일부 컬럼에 @GeneratedValue는 사용하지 못한다. 고 나와있지만 실제로 TEST 시 사용 가능한 경우도 있었다. )
1. @IdClass
참고 : https://www.objectdb.com/java/jpa/entity/id
참고 : https://www.objectdb.com/java/jpa/persistence/retrieve
우아한 기술 블로그 : https://woowabros.github.io/experience/2019/01/04/composit-key-jpa.html
간단히 Long ID와 String name으로 복합 키를 구성하는 테이블 Users를 만들어 보겠다.
@Entity
@Getter @Setter
@EqualsAndHashCode // 필수
@NoArgsConstructor
@AllArgsConstructor
@IdClass(UsersId.class) // UsersId라는 식별자 클래스에 @Id 속성이 다 있어야 한다.
public class Users implements Serializable {
@Id
@Column(name = "ID")
private Long id;
@Id
@Column(name = "NAME")
private String name;
private int age;
}
// UsersId Class
@Getter
@Setter
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class UsersId implements Serializable {
private Long id; // Users Class의 필드 이름이 꼭 같아야 한다.
private String name; // Users Class의 필드 이름이 꼭 같아야 한다.
}
//main
public static void logic(EntityManager em, EntityTransaction tx) throws Exception {
Users user1 = new Users(1L,"userA",31);
Users user2 = new Users(2L,"userB",39);
Users user3 = new Users(3L,"userC",17);
em.persist(user1);
em.persist(user2);
em.persist(user3);
UsersId id2 = new UsersId(2L, "userB");
final Users findUser = em.find(Users.class, id2);
System.out.println("findUser = " + findUser);
}
// 결과 : findUser = Users(id=2, name=userB, age=39)
IdClass의 필드 명과 Entity의 @Id 필드명이 다르면 Property of @IdClass not found in entity 에러가 발생한다.
IdClass 보다는 EmbeddedId 방법이 조금 더 객체지향적이다. Embeddable class를 재사용 할 수 있기 때문이다.
2. @EmbeddedId
Embeddable Object를 Id로 사용하는 방식이다.
@Embeddable
public class UsersId implements Serializable {
private Long id;
private String name;
}
UsersId는 마찬가지로 구성하였고 위의 4가지 조건은 만족해야한다.
@Embeddable만 추가하면 ID역할 뿐만 아니라 다른 엔티티에서 필드로도 활용 가능하다.
public class Users implements Serializable {
// 식별자 클래스를 별도로 만들 것이다.
// @Id
// @Column(name = "ID")
// private Long id;
// @Id
// @Column(name = "NAME")
// private String name;
@EmbeddedId
private UsersId usersId;
private int age;
}
Users 객체도 복잡한 구성 필요 없이 Id로 사용할 필드에 @EmbeddedId annotation만 추가하면 끝이다.
// Main
public static void logic(EntityManager em, EntityTransaction tx) throws Exception {
UsersId id1 = new UsersId(1L, "userA");
Users user = new Users(id1,33);
em.persist(user);
final Users findUser = em.find(Users.class, id1);
System.out.println("findUser = " + findUser);
}
결과 : findUser = Users(usersId={1,userA}, age=33)
3. How use IdClass and EmbeddedId
둘 다 장단점이 있다.
IdClass는 @Id annotation을 여러 개 사용하고 또 식별자 클래스에 각각의 필드명을 맞춰줘야 한다는 점.
EmbeddedId는 JPQL에서 객체 탐색을 더 많이 한다는 점이 있다.
해당 식별자 클래스가 의미가 있어 다른곳에서 사용하면 EmbeddedId, 의미 없이 복합키로서 존재하면 IdClass를 사용하면 될 것이다.
// IdClass
select u.name from Users u
// EmbeddedId
select u.UsersId.name from Users u
Error log
- Property of @IdClass not found in entity ••• [field name]
- IdClass에서의 field name과 entity의 field name이 다를 때 나타나는 에러
- org.hibernate.PropertyAccessException: Could not set field value [Value] value by reflection [IdClass]
- IdClass에서 field가 entity와 같은 타입의 변수여야 한다. (이름도 같아야 한다.)
- No part of a composite identifier may be null
- 복합키 중 하나가 null일 때 발생한다.
댓글남기기