Springboot

JPA 복합키 작성기초

25G 2023. 8. 14. 18:14

대표 PK를 사용하는 게 유리할 때

  • 클라이언트 쪽에서 복합키의 정보를 모두 알지 않아도 간편하게 리소스 관리가 가능함 (ex: 책ID, 사용자ID)
  • 데이터를 식별할 수 있는 컬럼의 조합이 유니크하지 않을 때 (ex: 이름+저자+출판사로는 책 식별 불가할 수 있음. 동일한 명칭의 개정판이 나오기 때문)
  • 식별가능한 복합키의 값이 변경 가능할 때 (책 이름, 저자의 필명, 출판사는 언제든 변경 가능함)
  • 주의할 점*

인덱스가 기본적으로 하나 추가됨 (대표PK + 복합컬럼 인덱스)

복합키를 사용하는 경우

특정 데이터를 식별하는 게 의미가 없는 도메인 (ex: 걸음기록과 같은 통계성 데이터)
사용 예) 걸음기록 저장
조회 : 사용자별 최근 일주일간 평균 걸음 수

위와같은 통계성, 기록성 데이터는 id가 존재하더라도 그 id를 이용해 데이터를 조작하거나 하지 않습니다.
그래서 이런경우에는 id key를 추가하는 대신 사용자 + 기록일시 기중으로 복합키를 구성합니다.
조회시에도 사용자+ 기록일시에 대한 조회기간으로 필터링되니까 PK 인덱스도 아주 잘 탈 수 있다.

PK 복합키 순서에 따라 인덱스가 타지 않을 수 있다.

JPA는 복합키를 생성할때 컬럼명의 알파벳 순으로 생성한다. Entity Class에 정의된 순서로 생성되는게 아니기때문에
조회할 때 기대했던 PK Index가 타지 않을 가능성이 높다.

PK 인덱스 실행조건 mysql

db에 테이블을 생성할때 자동으로 인덱스생성

복합키 설계

일반적으로 복합키는 카디널리티(열)가 높은쪽에서 낮은쪽으로 구성한다.

JPA는 복합키를 알파벳 순으로 생성한다.

@Getter
@NoArgsConstructor
@IdClass(UserStepId.class)
@Entity
public class UserStep {
@Id
@ManyToOne(targetEntity = User.class, fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
@Id
@Column(nullable = false, columnDefinition = "timestamp")
private ZonedDateTime recordedAt;
@Id
private String record_type;
private Long count;
}

직접 DB에 DDL 작성 or 컬럼을 알파벳 순서로 명명 or 에노테이션 만들기

JPA 복합키를 사용하는 방법 2가지

대부분의 엔티티에는 @Id 한개를 사용하지만 복합키는 아니다
복합키 설정방법

  1. @Embeddable 이용(좀더 객체지향적임)
  2. @IdClass 이용 (DB방식에 가까움)@Embeddable 이용방법

@EmbeddedId를 사용하기 위한 식별자 클래스를 생성할 때는 다음과 같은 조건이 만족해야 됩니다.

  • 식별자 클래스에 @Embeddable어노테이션 추가
  • 디폴트 생성자가 존재 (위의 코드는 Lombok의 @NoArgsConstructor어노테이션 추가로 자동 생성)
  • 식별자 클래스의 접근 지정자는 public
  • Serializable을 상속
  • equals, hashCode 구현 (위의 코드는 Lombok의 @Data어노테이션 추가로 자동 생성)
  • 컬럼명과 변수명이 다를 경우 @Column어노테이션 사용
  1. 학생 테이블의 복합키를 담고 있는 식별자 클래스 studentId생성

@Data
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class StudentID implements Serializable {
@Column(name = "student_id")
private String studentId;

private String name;

}


2.엔티티 클래스 Student 생성
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "student")
public class Student implements Serializable {

    @EmbeddedId
    private StudentID stduentID;

    @Column(name = "school_id")
    private int schoolId;
    private int score;
}

엔티티 클래스에서 식별자 클래스를 매핑하기 위해 사용되는 것은 @EmbeddedId 어노테이션입니다.
위와 같이 식별자 클래스를 변수로 선언한 뒤 해당 변수에 어노테이션만 추가해주면 됩니다.

  1. 레파지토리 생성

public interface StudentRepository extends JpaRepository<Student, StudentID> { // 제네릭 타입: <엔티티 클래스, 엔티티 클래스의 기본키>

}

## @IdClass 이용방법
0. Mysql 에 학생 테이블 생성 및 데이터 생성
```mysql
create table student (
    student_id varchar(30),
    name varchar(30),
    school_id int,
    score int,
    primary key(student_id, name)
);

insert into student values ('1234', '김고기', 3, 89);
insert into student values ('2345', '정덮밥', 1, 89);
insert into student values ('3456', '박찌개', 2, 89);
insert into student values ('4567', '문초밥', 3, 89);
insert into student values ('5678', '이족발', 4, 89);
insert into student values ('6789', '진짬뽕', 2, 89);
  1. 학생 테이블의 복합키를 담고 있는 식별자 클래스 StudentId 생성
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class StudentID implements Serializable {
     private String studentId;
     private String name;
    }
    @IdClass를 사용하기 위한 식별자 클래스를 생성할 때는 다음과 같은 조건이 만족해야 됩니다.
  • 식별자 클래스의 변수명과 엔티티에서 사용되는 변수명이 동일
  • 디폴트 생성자가 존재 (위의 코드는 Lombok의 @NoArgsConstructor어노테이션 추가로 자동 생성)
  • 식별자 클래스의 접근 지정자는 public
  • Serializable을 상속
  • equals, hashCode 구현 (위의 코드는 Lombok의 @Data어노테이션 추가로 자동 생성)
  1. 엔티티 클래스 Student 생성
  2. @Data @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "student") @IdClass(StudentID.class) public class Student implements Serializable { @Id @Column(name = "student_id") private String studentId; @Id private String name; @Column(name = "school_id") private int schoolId; private int score; }
엔티티 클래스에는 @IdClass라는 어노테이션을 이용하여 식별자 클래스를 매핑해줘야 됩니다.
그리고 위에서 말씀드린 것처럼 식별자 클래스의 변수명과 동일해야 합니다.

3. Repository 생성
```java
public interface StudentRepository extends JpaRepository<Student, StudentID> { // 제네릭 타입: <엔티티 클래스, 엔티티 클래스의 기본키>

}

상속받는 JpaRepository의 제네릭 타입에는 엔티티 클래스와 엔티티 클래스의 기본키가 들어가면 됩니다.
이전 포스팅에서도 그랬듯이 기본키가 하나일 때는 기본키의 타입을 넣어주면 되지만 복합키 같은 경우는 식별자 클래스를 기본키가 들어가야 되는 부분에 넣어주시면
됩니다.

IdClass vs @EmbeddedId

두 방식의 차이점은 단순하게 "엔티티 클래스를 작성할 때 귀찮기 vs 변수를 사용할 때 귀찮기"라고 생각합니다.
@IdClass 같은 경우는 식별자 클래스에 있는 모든 변수들을 그대로 엔티티 클래스에 작성해야 되기 때문에 엔티티 클래스를 작성할 때 @EmbeddedId보다 다소 시간이 소모됩니다.
하지만 생성된 클래스들을 사용할 때 @IdClass는 단순하게 student.getName()이라고 하면 될 것을 @EmbeddedId는 student.getStudentID().getName()과 같이 코드가 길어지게 됩니다.
또한 JPQL쿼리를 작성할 때도 식별자 클래스까지 넣어야 되기 때문에 길이가 길어지게 됩니다.
결론적으로 취향대로 사용하면 된다.

'Springboot' 카테고리의 다른 글

Springboot DataJPA 선택적으로 수정하기  (0) 2023.08.14
Spring boot-Cache Control Headers 하는법  (0) 2023.08.14
Springboot JUnit and Mockito  (0) 2023.08.14
@Deprecated와 @deprecated  (0) 2023.08.14
@Transactional원리  (0) 2023.08.14