#대표 PK를 사용하는 게 유리할 때
인덱스가 기본적으로 하나 추가됨 (대표PK + 복합컬럼 인덱스)
특정 데이터를 식별하는 게 의미가 없는 도메인 (ex: 걸음기록과 같은 통계성 데이터)
사용 예) 걸음기록 저장
조회 : 사용자별 최근 일주일간 평균 걸음 수
위와같은 통계성, 기록성 데이터는 id가 존재하더라도 그 id를 이용해 데이터를 조작하거나 하지 않습니다.
그래서 이런경우에는 id key를 추가하는 대신 사용자 + 기록일시 기중으로 복합키를 구성합니다.
조회시에도 사용자+ 기록일시에 대한 조회기간으로 필터링되니까 PK 인덱스도 아주 잘 탈 수 있다.
JPA는 복합키를 생성할때 컬럼명의 알파벳 순으로 생성한다. Entity Class에 정의된 순서로 생성되는게 아니기때문에
조회할 때 기대했던 PK Index가 타지 않을 가능성이 높다.
db에 테이블을 생성할때 자동으로 인덱스생성
일반적으로 복합키는 카디널리티(열)가 높은쪽에서 낮은쪽으로 구성한다.
@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 에노테이션 만들기
대부분의 엔티티에는 @Id 한개를 사용하지만 복합키는 아니다
복합키 설정방법
1. @Embeddable 이용(좀더 객체지향적임)
2. @IdClass 이용 (DB방식에 가까움)
@EmbeddedId를 사용하기 위한 식별자 클래스를 생성할 때는 다음과 같은 조건이 만족해야 됩니다.
@Data
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class StudentID implements Serializable {
@Column(name = "student_id")
private String studentId;
private String name;
}
2.엔티티 클래스 Student 생성
@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 어노테이션입니다.
위와 같이 식별자 클래스를 변수로 선언한 뒤 해당 변수에 어노테이션만 추가해주면 됩니다.
public interface StudentRepository extends JpaRepository<Student, StudentID> { // 제네릭 타입: <엔티티 클래스, 엔티티 클래스의 기본키>
}
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);
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StudentID implements Serializable {
private String studentId;
private String name;
}
@IdClass를 사용하기 위한 식별자 클래스를 생성할 때는 다음과 같은 조건이 만족해야 됩니다.
@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라는 어노테이션을 이용하여 식별자 클래스를 매핑해줘야 됩니다.
그리고 위에서 말씀드린 것처럼 식별자 클래스의 변수명과 동일해야 합니다.
public interface StudentRepository extends JpaRepository<Student, StudentID> { // 제네릭 타입: <엔티티 클래스, 엔티티 클래스의 기본키>
}
상속받는 JpaRepository의 제네릭 타입에는 엔티티 클래스와 엔티티 클래스의 기본키가 들어가면 됩니다.
이전 포스팅에서도 그랬듯이 기본키가 하나일 때는 기본키의 타입을 넣어주면 되지만 복합키 같은 경우는 식별자 클래스를 기본키가 들어가야 되는 부분에 넣어주시면
됩니다.
두 방식의 차이점은 단순하게 "엔티티 클래스를 작성할 때 귀찮기 vs 변수를 사용할 때 귀찮기"라고 생각합니다.
@IdClass 같은 경우는 식별자 클래스에 있는 모든 변수들을 그대로 엔티티 클래스에 작성해야 되기 때문에 엔티티 클래스를 작성할 때 @EmbeddedId보다 다소 시간이 소모됩니다.
하지만 생성된 클래스들을 사용할 때 @IdClass는 단순하게 student.getName()이라고 하면 될 것을 @EmbeddedId는 student.getStudentID().getName()과 같이 코드가 길어지게 됩니다.
또한 JPQL쿼리를 작성할 때도 식별자 클래스까지 넣어야 되기 때문에 길이가 길어지게 됩니다.
결론적으로 취향대로 사용하면 된다.