복합키 매핑 방법 JPA는 복합키 매핑을 다음과 같은 2가지 annotation으로 지원해준다.
1.@IdClass 2.@Embeddable
복합키를 매핑하는 class의 전제조건
composite primary key class 는 public 이여야만 한다.
no-arg constructor 가 존재해야만 한다.
equals,hashCode method를 overriding해야만 한다.
Serilizable interface 를 구현해야 한다.
@IdClass @IdClass 방식은 class의 필드를 entity에도 중복 보관한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder @IdClass(UserId.class) public class User { @Id private String userId; @Id private String password; private LocalDateTime createdAt; private LocalDateTime updatedAt; private LocalDateTime deletedAt; }
@AllArgsConstructor annotation이 entity에 추가된 이유는 lombok 빌더 떄문이다.(@Builder) 빌더 패턴은 모든 멤버변수를 생성자로 받을 수 있어야 한다, 자세한 내용은 이펙티브 자바 , builder pattern 내부 구현을 보면 알수 있는데 간략하게 정리하면 아래와 같이 빌더내 멤버변수를 생성하고자 하는 객체에 넘겨줄떄 생성자가 필요하기 떄문이다. 또는 빌더 자체를 넘겨주어서 빌더로부터 필드를 가져오는 방식도 존재한다.
1 2 3 4 public build () { new User(builder.userId , builder.password ... ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 User{ User(Builder builder){ this .userId = builder.getUserId; } public static class Builder { private String userId; public Builder userId () { this .userId = userId; return this ; } public build () { new User(builder); } } }
UserId를 보면 복합키에 사용될 필드를 보관하고 있고, serializable interface를 구현하고 equals & hascode를 overriding했다.
1 2 3 4 5 6 7 8 9 10 11 @EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Getter public class UserId implements Serializable { private String userId; private String password; }
repository 에서는 복합키 클래스를 Id Class로 명시해주면 된다.
1 2 @Repository public interface UserRepository extends JpaRepository <User , UserId > {}
이를 테스트해보면 정상작동여부를 확인할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test @DisplayName("@IdClass 정상작동여부 확인") public void user () { UserId userId = new UserId("tester" ,"1234" ); User user = new User.UserBuilder() .userId(userId.getUserId()) .password(userId.getPassword()) .createdAt(LocalDateTime.now()) .build(); User savedUser = userRepository.save(user); Optional<User> foundUser = userRepository.findById(userId); Assertions.assertThat(foundUser.isPresent()); Assertions.assertThat(foundUser.get() ).isEqualTo(savedUser); }
@EmbeddedClass @EmbeddedClass 방식은 필드를 복합키 클래스에서만 보관하고, entity에는 복합키 클래스만을 보관한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @EqualsAndHashCode @AllArgsConstructor @NoArgsConstructor @Getter @Embeddable public class UserId implements Serializable { private String userId; private String password; } @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Builder public class User { @EmbeddedId private UserId userId; }
이를 테스트해보면 마찬가지로 정상작동여부를 확인할수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test @DisplayName("@EmbeddedId 정상작동여부 확인") public void user () { UserId userId = new UserId("tester" ,"1234" ); User user = new User.UserBuilder() .userId(userId) .createdAt(LocalDateTime.now()) .build(); User savedUser = userRepository.save(user); Optional<User> foundUser = userRepository.findById(userId); Assertions.assertThat(foundUser.isPresent()); Assertions.assertThat(foundUser.get() ).isEqualTo(savedUser); }
@IdClass와 @EmbeddedClass간의 차이점 @EmbeddedClass에서는 entity class에 복합키 클래스만 명시해주는 반면, @IdClass는 복합키 클래스의 필드를 중복으로 다시 명시해주어야 한다 jpql query가 나갈떄 차이점도 존재한다. @IdClass
1 SELECT user.userId,user.password from User user
이와달리 @EmbeddedClass에서는 추가적인 alias가 사용되어 jpql query가 나간다.
1 SELECT user.userId.id , user.userId.password from User user