테이블 생성
autoDDL options
Entity
@Entity(name = "member") // 엔티티 객체로 만들기 위한 어노테이션, 다른 패키지에 동일한 이름 존재 불가
@Table(name="tbl_member") // 데이터베이스에 매핑될 테이블 이름 설정
public class Member {
@Id
@Column(name="member_no") // 데이터베이스에 대응되는 컬럼명 지정(타입 및 여러 제약조건 설정 가능)
private int memberNo;
@Column(name="member_id")
private String memberId;
...
@Column(name="status")
private String status;
...
@Column(name="member_no") // 데이터베이스에 대응되는 컬럼명 지정(타입 및 여러 제약조건 설정 가능)
private int memberNo;
...
@Column(name="phone", columnDefinition = "varchar(200)", unique = true, nullable = false)
private String phone;
@Column(name="enroll_date")
@Temporal(TemporalType.TIMESTAMP) // datetime
@Temporal(TemporalType.DATE) // date
@Temporal(TemporalType.TIME) // time
private Date enrollDate;
pk
id(identity)
table
Sequence 객체를 활용(only for Oracle)
복합키
Embeddable Type: 하나의 값의 묶음이자 불변 객체로 다루는 타입
불변 객체
equals, hashCode를 반드시 오버라이딩 해야 함
[Member.java]
@Entity(name = "member_section05_subsection01")
@Table(name="tbl_member_section05_subsection01")
public class Member {
@EmbeddedId
private MemberPK memberPK;
...
}
[MemberPK.java]
@Embeddable
public class MemberPK implements Serializable {
@Column(name="member_no")
private int memberNo;
@Column(name="member_id")
private String memberId;
public MemberPK() {
}
public MemberPK(int memberNo, String memberId) {
this.memberNo = memberNo;
this.memberId = memberId;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass())
return false;
MemberPK memberPK = (MemberPK) object;
return memberNo == memberPK.memberNo && Objects.equals(memberId, memberPK.memberId);
}
@Override
public int hashCode() {
return Objects.hash(memberNo, memberId);
}
@Override
public String toString() {
return "MemberPK{" +
"memberNo=" + memberNo +
", memberId='" + memberId + '\'' +
'}';
}
}
idclass
[Member.java]
@IdClass(MemberPK.class)
public class Member {
@Id
@Column(name="member_no")
private int memberNo;
@Id
@Column(name="member_id")
private String memberId;
...
}
[MemberPK.java]
public class MemberPK implements Serializable {
private int memberNo;
private String memberId;
public MemberPK() {
}
public MemberPK(int memberNo, String memberId) {
this.memberNo = memberNo;
this.memberId = memberId;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
MemberPK memberPK = (MemberPK) object;
return memberNo == memberPK.memberNo && Objects.equals(memberId, memberPK.memberId);
}
@Override
public int hashCode() {
return Objects.hash(memberNo, memberId);
}
}
Enum
use
[Member.java]
...
@Column(name="member_role")
@Enumerated(EnumType.ORDINAL) // DB에 숫자로 값이 들어감(ex: 0 또는 1)
@Enumerated(EnumType.STRING) // DB에 문자열로 값이 들어감(ex: ADMIN 또는 MEMBER)
private RoleType memberRole;
...
[RoleType.java]
public enum RoleType {
ROLE_ADMIN,
ROLE_MEMBER // SpringSecurity에 적용
}
access level
@Entity(name="menu_and_category")
@Table(name="tbl_menu")
public class MenuAndCategory {
@Id
@Column(name="menu_code")
private int menuCode;
@Column(name="menu_name")
private String menuName;
@Column(name="menu_price")
private int menuPrice;
// JoinColumn에 쓰이는 컬럼명은 FK 제약조건이 걸린 자식 테이블의 컬럼명 작성
@JoinColumn(name="category_code")
@ManyToOne
private Category category;
@Column(name="orderable_status")
private String orderableStatus;
Collection(OneToMany)
Anti-Pattern에 가까움
N+1 문제 발생
use
@Entity
public class CategoryAndMenu {
@Id
@Column(name="category_code")
private int categoryCode;
...
// JoinColumn에 쓰이는 컬럼명은 FK 제약조건이 걸린 자식 테이블의 컬럼명
@JoinColumn(name="category_code")
@OneToMany // 두 엔티티 간의 전체 카디널리티(N:1)를 고려하여 작성
private List<Menu> menu; // 하나의 메뉴는 하나의 카테고리를 지님
}
- N+1 문제: 부모 엔티티가 관련된 모든 자식 엔티티를 가져옴
- 따라서 하나의 엔티티 조회 시, 하위의 여러 엔티티 갯수만큼 쿼리 발생
- 사실, JPA 패러다임은 엔티티 매니저에게 모든 작업을 맡기기에, 로그를 파헤치는 식의 접근은 JPA로서 안티패턴일 수 있다.
- Lazy Join / Fetch Join으로 해결 가능하나 여러 단점들이 있다.
객체 그래프 탐색을 통한 조회
객체 그래프(객체간의 연관관계를 표현) 탐색:
- 객체 그래프의 연관관계를 활용하여 탐색
@Entity
@Table(name="tbl_menu")
public class Menu {
@Id
@Column(name="menu_code")
private int menuCode;
...
@JoinColumn(name="category_code")
@ManyToOne
private Category category;
...
public Category getCategory() {
return category;
}
@Override
public String toString() {
return "Menu{" +
"menuCode=" + menuCode +
", menuName='" + menuName + '\'' +
", menuPrice=" + menuPrice +
", category=" + category + // HotSpot
", orderableStatus='" + orderableStatus + '\'' +
'}';
}
}
@Entity
@Table(name="tbl_category")
public class Category {
@Id
@Column(name="category_code")
private int categoryCode;
@OneToMany(mappedBy="category") // 연관관계의 주인: mappedBy
private List<Menu> menuList;
...
public List<Menu> getMenuList() {
return menuList;
}
@Override
public String toString() {
return "Category{" +
"categoryCode=" + categoryCode +
", categoryName='" + categoryName + '\'' +
", refCategoryCode=" + refCategoryCode +
", menuList=" + menuList + // HotSpot
'}';
}
}
순환참조 발생
java.lang.StackOverflowError양방향 연관관계는 지양되는 방식이며 순환참조 주의(toString())
연관관계의 주인인 엔티티는 한번에 join문으로 관계를 맺은 엔티티를 조회
부모-자식 관계에서 자식 테이블이 연관관계의 주인
- 따라서 자식이 관계를 끊으면, 연관관계가 사라짐