크게 4가지로 분류한다.
@Entity
어노테이션으로 테이블과 매핑한다.
JPA를 사용해서 테이블과 매핑할 클래스는 필수로 붙여야한다.
@Entity가 붙은 클래스는 JPA가 관리하는 것으로 엔터티라 부른다.
해당 클래스를 엔티티로 설정하기 위한 어노테이션
프로젝트 내에 다른 패키지에도 동일한 엔티티가 존재하는 경우 해당 엔티티를 식별하기 위한 중복되지 않은 name을 지정해주어야한다.
@Entity 속성 :
주의사항
- 기본 생성자 필수로 작성
- final 클래스, enum, interface, inner class에서는 사용할 수 없다.
- 저장할 필드에 final을 사용하면 안된다.
@Test
public void 테이블_만들기_테스트() {
//given
Member member = new Member();
member.setMemberNo(1);
member.setMemeberID("user01");
member.setMemberPwd("pass01");
member.setNickName("홍길동");
member.setPhone("010-1234-1234");
member.setAddress("서울 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
//when
entityManager.persist(member);
//then
Member foundMember = entityManager.find(Member.class, member.getMemberNo());
assertEquals(member.getMemberNo(), foundMember.getMemberNo());
}
@Table
은 엔터티와 매핑할 테이블을 지정.
생략하면 매핑한 엔터티의 이름을 테이블 이름으로 사용한다.
@Table 속성 :
<property name="hibernate.hbm2ddl.auto" value="create"/>
@Column
은 객체 필드를 테이블 컬럼에 매핑.
@Column 속성 :
주로 name과 nullable을 사용하며, insertable과 updateable은 정보를 읽기만 하고 실수로 변경하게 될 것을 미연에 방지하고자 설정한다.
다만 애플리케이션 레벨에서 DDL 구분을 직접 사용하지 않는 것이 더 바람직하다.
이 필드는 매핑하지 않는다.
데이터베이스에 저장하지 않고 조회도 하지 않는다.
객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.
@Temporal
은 날짜 타입을 매핑할 때 사용.
@Temporal을 사용하기 위해서는 java.sql.Date가 아닌 java.util.Date를 사용해야한다.
- @Temporal(TemporalType.TIMESTAMP)
: DATE + TIME으로 날짜 및 시간이 나온다.
- @Temporal(TemporalType.DATE)
: ORACLE에서는 TIMESTAMP와 차이가 없다.
- Temporal(TemporalType.TIME)
: 1970/01/01에 시간만 맞게 나온다.
영속성 컨텍스트는 엔터티를 식별자 값으로 구분하므로 엔터티를 영속 상태로 만들려면 식별자 값이 반드시 있어야한다.
@Id
@Column(name="id")
private String id;
데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 데이터베이스 오브젝트이다. 이 시퀀스를 사용하여 기본키 생성.
@SequenceGenerator
는 데이터베이스 시퀀스를 사용해서 기본 키 할당.
클래스 상단과 클래스 내부 값에도 적어준다.
@SequenceGenerator 속성 :
name : 식별자 생성기 이름
sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름
initialValue : 시퀀스의 시작값 설정
allocationSize : 시퀀스 증가 값
기본 값이 50으로 설정되어 있고 시퀀스 증가와 별개로 메모리에서 식별자를 할당하여 매번 시퀀스를 호출하지 않도록 하는 성능 최적화를 위해 설정된 값이다.
여러 요청이 동시에 접근해서 데이터를 등록할 때 기본 값이 충돌하지 않는 장점(동시성 문제 해결)이 있지만, 시퀀스가 한 번에 많이 증가하는 점을 염두에 두어야한다.
주의 :
@SequenceGenerator의 name 속성과 @GeneratedValue의 generator의 이름이 같아야한다.
@Entity(name="sequence_member")
@Table(name="TBL_MEMBER_SECTION03_SUBSECTION01")
@SequenceGenerator(
name="MEMBER_SEQUENCE_GENERATOR",
sequenceName="SEQ_MEMBER_NO",
initialValue=5,
allocationSize=1
)
public class Member {
@Id
@Column(name="MEMBER_NO")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="MEMBER_SEQUENCE_GENERATOR")
private int memberNo;
@Column(name="MEMBER_ID")
private String memeberID;
생략......
@Test
public void 식별자_매핑_테스트() {
//given
Member member = new Member();
member.setMemeberID("user01");
member.setMemberPwd("pass01");
member.setNickName("홍길동");
member.setPhone("010-1234-1234");
member.setAddress("서울 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
Member member2 = new Member();
member2.setMemeberID("user02");
member2.setMemberPwd("pass02");
member2.setNickName("유관순");
member2.setPhone("010-1234-1234");
member2.setAddress("서울 종로구");
member2.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member2.setMemberRole("ROLE_MEMBER");
member2.setStatus("Y");
//when
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityManager.persist(member2);
entityTransaction.commit();
//then
String jpql = "SELECT A.memberNo FROM sequence_member A";
List<Integer> memberNoList = entityManager.createQuery(jpql, Integer.class).getResultList();
memberNoList.forEach(System.out::println);
}
키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략.
테이블을 사용하므로 모든 데이터베이스에 사용할 수 있다.
@TableGenerator
은 시퀀스 대신 테이블을 사용하는 것을 제외하면 시퀀스 전략과 내부 동작 바식이 같다.
클래스 상단과 클래스 내부 값에도 적어준다.
속성 :
name : 식별자 생성기 이름
table : 키 생성 테이블 이름
pkColumnValue : pk 컬럼의 이름 지정
pkColumnName : 시퀀스 컬럼명 (기본 값은 SEQUENCE_NAME)
valueColumnName : 시퀀스 값 컬럼명 (기본 값은 NEXT_VAL)
allocationSize : 증가 값
주의 :
@TableGenerator name 속성과 @GeneratedValue의 generator의 이름이 같아야한다.
@Entity(name="sequence_table_member")
@Table(name="TBL_MEMBER_SECTION03_SUBSECTION02")
@TableGenerator(
name="MEMBER_SEQ_TABLE_GENERATOR",
table="TBL_MY_SEQUENCES",
pkColumnValue="MY_SEQ_MEMBER_NO",
// pkColumnName : 시퀀스 컬럼명 (기본 값은 SEQUENCE_NAME)
// valueColumnName : 시퀀스 값 컬럼명 (기본 값은
allocationSize=1
)
public class Member {
@Id
@Column(name="MEMBER_NO")
@GeneratedValue(strategy = GenerationType.TABLE, generator="MEMBER_SEQ_TABLE_GENERATOR")
private int memberNo;
@Column(name="MEMBER_ID")
private String memeberID;
.....생략
@Test
public void 식별자_매핑_테스트() {
//given
Member member = new Member();
member.setMemeberID("user01");
member.setMemberPwd("pass01");
member.setNickName("홍길동");
member.setPhone("010-1234-1234");
member.setAddress("서울 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
Member member2 = new Member();
member2.setMemeberID("user02");
member2.setMemberPwd("pass02");
member2.setNickName("유관순");
member2.setPhone("010-1234-1234");
member2.setAddress("서울 종로구");
member2.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member2.setMemberRole("ROLE_MEMBER");
member2.setStatus("Y");
//when
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityManager.persist(member2);
entityTransaction.commit();
//then
String jpql = "SELECT A.memberNo FROM sequence_table_member A";
List<Integer> memberNoList = entityManager.createQuery(jpql, Integer.class).getResultList();
memberNoList.forEach(System.out::println);
}
@Enumerated
은 자바의 enum 타입을 매핑할 때 사용.
public enum RoleType {
ADMIN, MEMBER
}
@Enumerated 속성 :
ADMIN : 0 MEMBER : 1
장점
: 데이터 베이스에 저장되는 데이터 크기가 작다.
단점
: 이미 저장된 enum의 순서를 변경할 수 없다.
@Column(name="MEMBER_ROLE")
@Enumerated(EnumType.ORDINAL)
private RoleType memberRole;
장점
: 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다.단점
: 데이터 베이스에 저장되는 데이터의 크기가 ordinal에 비해 크다. @Column(name="MEMBER_ROLE")
@Enumerated(EnumType.STRING)
private RoleType memberRole;
JPA가 엔터티 데이터에 접근하는 방식을 지정
선언 위치
1. 클래스 레벨 : 모든 필드에 대해서 적용
2. 필드 레벨 : 해당 필드의 접근 방식을 필드 접근으로 바꿀 수 있다.
@Id 어노테이션이 필드 레벨에 존재하는 경우 해당 필드는 @Access(AccessType.FIELD)이다. 따라서 어노테이션을 생략해도 무방하다.
만약 @Access를 설정하지 않으면 @Id의 위치를 기준으로 접근 방식이 설정된다.
public class Member {
@Id
@Column(name="MEMBER_NO")
private int memberNo;
public class Member {
private int memberNo;
@Id
public int getMemberNo() {
return memberNo;
}
기본적으로 JPA에서는 필드 접근을 사용한다.
따라서 @Access(AccessType.FIELD)속성은 생략 가능하다.
하지만 다른 로직을 처리하거나 값을 검증하는 추가 로직을 수행해야하는 경우에는 프로퍼티 접근 방식을 혼용해서 사용하기도한다.
/* 1. 클래스 레벨 : 모든 필드에 대해서 적용 */
//@Access(AccessType.FIELD)
public class Member {
/* 2. 필드 레벨 : 해당 필드의 접근 방식을 필드 접근으로 바꿀 수 있다.
* @Id 어노테이션이 필드 레벨에 존재하는 경우 해당 필드는 @Access(AccessType.FIELD)이다. 따라서 어노테이션을 생략해도 무방하다. */
@Id
@Column(name="MEMBER_NO")
//@Access(AccessType.FIELD)
private int memberNo;
@Column(name="MEMBER_ID")
//@Access(AccessType.FIELD)
private String memeberID;
클래스 레벨에 @Access(AccessType.PROPERTY)를 작성할 때 주의할 점은
@Id 어노테이션이 필드에 있다면 엔티티 매니저 자체를 생성하지 못하기 때문에 @Id 어노테이션을
getter 메소드 위로 옮겨야한다.
/* 메소드 레벨 - 해당 메소드의 접근 방식만 변경한다. */
@Id
public int getMemberNo() {
return memberNo;
}
복합키가 존재하는 테이블 매핑의 경우 별도의 방법이 필요하다.
@Embeddable : @Embeddable 클래스에 복합키를 정의하고 엔티티에 @Embeddable을 이용해 복합 키 클래스를 매핑한다.
@IdClass : 복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @Id를 매핑한다.
두 방식 모두 복합키 클래스는 영속성 컨텍스트가 관리하지 않는다는 특징이 잇으며, 큰 기능적 차이도 존재하지 않는다.
다만 @Embeddable이 조금 더 객체 지향다운 방법이고,
@IdClass는 관계형 데이터 베이스에 가까운 방법이다.
@Embeddable 클래스에 복합키를 정의하고 엔티티에 @Embeddable을 이용해 복합 키 클래스를 매핑한다.
@Embeddable
public class MemberPK implements Serializable{ //implements Serializable : 직렬화 처리도 되어있어야한다.
@Column(name="MEMBER_NO")
private int memberNo;
@Column(name="MEMBER_ID")
private String memberId;
...생략
}
public class Member {
@EmbeddedId
private MemberPK memberPK;
@Column(name="PHONE")
private String phone;
@Column(name="ADDRESS")
private String address;
...생략
}
복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @Id를 매핑한다.
public class MemberPK implements Serializable{
private int memberNo;
private String memberId;
public MemberPK() {}
...생략
}
@IdClass(MemberPK.class)
public class Member {
@Id
@Column(name="MEMBER_NO")
private int memberNo;
@Id
@Column(name="MEMBER_ID")
private String memberId;
@Column(name="PHONE")
private String phone;
@Column(name="ADDRESS")
private String address;
...생략
}