
알고 있어야할 어노테이션들 - JPA
JPA 관련
@Entity
클래스가 DB의 테이블과 매핑되도록
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
}
// @Id -> 기본키 선언
// @GeneratedValue -> DB가 자동 생성
// MySQL: IDENTITY -> MySQL은 시퀸스가 없음
//@GeneratedValue(strategy = GenerationType.IDENTITY)
// Oracle: SEQUENCE
//@GeneratedValue(strategy = GenerationType.SEQUENCE)@Column
필드의 컬럼 속성을 지정
@Entity
public class User {
@Column(nullable = false, length = 50, unique = true)
private String email;
}
// nullable == NOT NULL
// 컬럼 속성 지정
// @Column(
// name = "user_name", // 컬럼 이름
// nullable = false, // NOT NULL
// unique = true, // UNIQUE
// length = 50, // VARCHAR(50)
// columnDefinition = "TEXT" // 컬럼 타입 직접 지정
// )
// 다만 BLOB이나 TEXT 타입 지정시는 따로 @Lob 라는 어노테이션 명시 필요
// 조건 위반시 DataIntegrityViolationException 발생@Transient
JPA 매핑 제외하기
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
// DB에 저장 안 함 (계산 필드)
@Transient
private int age;
@Transient
private String tempData;
}@Enumerated(EnumType.STRING)
Enum 사용시 매핑
public enum UserRole {
ADMIN, USER, GUEST
}
@Entity
public class User {
@Id @GeneratedValue
private Long id;
// STRING 필수! (ORDINAL 금지)
//-> ORDINAL은 선언된 순서를 Integer로 변환하여 저장함
@Enumerated(EnumType.STRING)
@Column(length = 20)
private UserRole role;
}
// DB 저장: "ADMIN", "USER", "GUEST"@OneToMany, @ManyToOne, @OneToOne ***
연관 관계의 표시, 좀 더 정리했다
@OneToMany 는 주인이 아닌 쪽에 사용
@ManyToOne 이 제일 중요하고 모든 것을 사실 ManyToOne으로 정의 가능하다
DB 상에서 조인 관계를 생각해보면 대부분이 N : 1이기 때문
// User (1) : Order (N) -> 양방향
@Entity
public class User {
@Id
private Long id;
// 추가로 (cascade = CascadeType.XXX, orphanRemoval = true)
// 선언시 Order 도 Delete cascade로 전부 삭제
@OneToMany(mappedBy = "user") // 이 user는 주인이 아님을 명시 ***
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
private Long id;
// 이 user 가 주인임을 명시, 즉 FK가 있다면 관리
// Optional -> 연관관계에 따라 left join, inner join 선택됨
// true는 기본값, 객체의 null을 허용하므로 left join 조회 방식 쿼리 발생
// false 시 객체가 null이 아님을 명시하는 것이므로 안되므로
// inner join 활용됨
// Optional 은 JPA 가 쿼리를 생성할 때 영향을 끼침
// nullable 과 같은 옵션으로 사용하는게 좋음
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false) // 주인 명시 ***
private User user;
}
// --------
// Member (1) : Locker (1) -> 1대1 관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
// 1:1 관계
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "locker_id", unique = true) // -> 주인
private Locker locker;
}
// ========== 주인 아닌 쪽 ==========
@Entity
public class Locker {
@Id @GeneratedValue
private Long id;
private String number;
// 1:1 관계 (주인 아님)
@OneToOne(mappedBy = "locker") // -> mappedBy? 주인 아님
private Member member;
}
// ----------
// Member(N) : Team(1) -> 단방향 관계 *************
// 다수에서 단일을 참조하는 관계
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
}FetchType.LAZY와 EAGER 의 전략 선택이 있는데
FetchType.LAZY 만 우선 사용해야 한단 것을 알아두자Proxy의 개념을 알고 있어야 둘의 차이에 대해 기술할텐데..
정말 간단히 적어보면 원래 객체는 연관된 객체 간의 탐색이 자유롭지만
데이터베이스에 매핑된 엔티티 객체는 데이터베이스 처럼 조인을 해야
연관된 엔티티 객체를 탐색할 수 있다
이러면 연관 관계가 있다고 해도,
연관 관계 테이블을 사용하지 않을 경우에도 굳이 탐색을 위해서
모든 걸 조인 조회를 해야할까? 에서 시작된 실제 사용하는 시점에
데이터베이스에서 조회할 수 있도록 해주는 것이 Proxy 다
→ 실제 엔티티 객체 대신 데이터베이스 조회를 지연(LAZY)할 수 있는 가짜 객체비유적으로 풀어보자면,
책의 목차만 가져와서 이 책엔 이러한 내용이 있습니다 라고 말해주는 객체EAGER는 왜 사용하지 않을까?
대표적인 케이스만 설명하자면…
@Entity public class Post { @Id @GeneratedValue private Long id; private String title; @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "author_id") private Member author; @OneToMany(mappedBy = "post", fetch = FetchType.EAGER) private List<Comment> comments = new ArrayList<>(); } // -------------------- // postRepository.findAll(); 실행 // -> select * from posts; // EAGER 이므로 member의 author_id 들의 연관 관계를 즉시 표현해야하므로 // -> select author_id from members where id = :id; // 즉, 게시글의 작성자를 게시글 수 만큼 조회 // EAGER 이므로 post의 comment 들의 연관 관계도 즉시 표현해야하므로 // -> select * from comment where post_id = :post_id; // 즉, 게시글의 아이디로 게시글의 모든 댓글 정보를 조회EAGER는 위에서 언급한 연관관계이지만 연관관계가 맺어진 테이블을
사용하지 않아도 그 테이블의 모든 정보를 일일히 조회하여 갖고 있어야 한다
→ 단순하게 select 하나 했는데 N번의 조회가 더 이뤄질 수 있다는 말EAGER는 2001년에 개발되었는데 그 때는 데이터의 규모가
이렇게 까지 크지 않고 인터넷 속도도 느려 어느 정도 조절하며
사용이 가능했지만 현대에선 이런 연속된 조회로 DB 전체가 조회 될 수
있으니 소규모의 운영이 아닌이상 사장되어 있음
@JoinColumn
외래키 설정에 사용 (주인 쪽에 사용)
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(
name = "member_id", // 외래키 컬럼명
nullable = false, // NOT NULL
unique = false, // UNIQUE 여부
insertable = true, // INSERT 가능
updatable = true, // UPDATE 가능
columnDefinition = "BIGINT", // 컬럼 타입
foreignKey = @ForeignKey(
name = "fk_order_member", // FK 제약조건 이름
value = ConstraintMode.CONSTRAINT // FK 제약조건 생성
)
)
private Member member;
}@ForeignKey
외래키의 제약조건 설정
@ForeignKey (
name = "fk name" // fk 이름 설정
, value = ConstraintMode.CONSTRAINT // 물리적 FK
)
// ConstraintMode.CONSTRAINT -> 물리적 FK (FK가 실제 DB 생김)
// ConstraintMode.NO_CONSTRAINT -> 논리적 FK@EmbeddedId
복합 키 일 때 사용, 따로 클래스를 만들어줘야함
// ========== 복합 키 클래스 ==========
@Embeddable // 같이 사용해야함
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // 같이 사용해야함
// 복합키 일때는 Serializable 도 사용해야함
public class OrderProductId implements Serializable {
private Long orderId;
private Long productId;
}
// ========== Entity ==========
@Entity
public class OrderProduct {
@EmbeddedId
private OrderProductId id;
// @MapsId 복합키의 일부를 외래키로 매핑해주는 어노테이션
@MapsId("orderId") // id.orderId와 매핑
@ManyToOne
@JoinColumn(name = "order_id")
private Order order;
@MapsId("productId") // id.productId와 매핑
@ManyToOne
@JoinColumn(name = "product_id")
private Product product;
private Integer quantity;
}