3-1. Entity
- 엔티티와 테이블을 정확하게 매핑하는 것이 JPA의 핵심
- 다양한 매핑 어노테이션 지원
@Test
public void 테이블_만들기_테스트() {
Member member = new Member();
member.setMemberNo(1);
member.setMemberId("user01");
member.setMemberPwd("pass01");
member.setNickname("홍길동");
member.setPhone("010-1234-5678");
member.setAddress("서울시 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
entityManager.persist(member);
Member foundMember = entityManager.find(Member.class, member.getMemberNo());
assertEquals(member.getMemberNo(), foundMember.getMemberNo());
}
- commit 하지 않았기 때문에 DML은 rollback 되어 있지만, DDL은 autocommit 구문이기 때문에 테이블은 생성 되어 있음
- 생성 된 자료형은 java object의 자료형을 따름
- 크기를 따로 설정하지 않았을 경우 숫자는 number(10,0), 문자열은 varchar2(255 char)로 설정
- 생성 되는 컬럼의 순서는 PK가 우선.
- 일반 컬럼은 유니코드 오름차순으로 생성
<property name="hibernate.hbm2ddl.auto" value="create"/>
3-2. Column
@Test
public void 컬럼에서_사용하는_속성_테스트() {
Member member = new Member();
member.setMemberNo(1);
member.setMemberId("user01");
member.setMemberPwd("pass01");
member.setNickname("홍길동");
member.setPhone("010-1234-5678");
member.setAddress("서울시 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityTransaction.commit();
Member foundMember = entityManager.find(Member.class, member.getMemberNo());
assertEquals(member.getMemberNo(), foundMember.getMemberNo());
String jpql = "SELECT TO_CHAR(A.enrollDate, 'YYYY/MM/DD HH:mm:ss') FROM section02_member A WHERE A.memberNo = 1";
String dateTime = entityManager.createQuery(jpql, String.class).getSingleResult();
System.out.println(dateTime);
}
- 컬럼 매핑 시 @Column 어노테이션에 사용 가능한 속성들
- name
- insertable
- 엔티티 저장 시 이 필드도 같이 저장(true 기본값)
- updatable
- 엔티티 수정 시 이 필드도 같이 수정(true 기본값)
- table
- 하나의 엔티티를 두 개 이상의 테이블에 매핑할 때 사용(현재 클래스가 매핑된 테이블)
- nullable
- null 값 허용 여부 설정. not null 제약조건에 해당(true 기본값)
- unique
- 한 컬럼에 unique 제약조건 설정
- 기본값은 없으며, 두 개 이상 컬럼에 unique 제약조건을 설정하기 위해서는 클래스 레벨에서 @Table의 uniqueConstraints 속성에 설정.
- columnDefinition
- 데이터베이스 컬럼 정보를 직접 기입
- 필드의 자바 타입과 방언 정보를 사용해서 적절한 컬럼 타입을 생성
- length
- 문자 길이 제약조건
- String 타입에서만 사용(255 기본값)
- 주로 name과 nullable을 사용하며, insertable과 updateable은 정보를 읽기만 하고 실수로 변경하게 될 것을 미연에 방지하고자 설정.
- 다만 애플리케이션 레벨에서 DDL 구분을 직접 사용하지 않는 것이 더 바람직.
@Column(name="PHONE", nullable=false)
@Column(name="PHONE", columnDefinition="varchar2(200) default '010-0000-0000'")
private String phone;
@Column(name="EMAIL")
private String email;
@Column(name="ADDRESS")
private String address;
@Column(name="ENROLL_DATE")
@Temporal(TemporalType.TIME)
private Date enrollDate;
@Column(name="MEMBER_ROLE")
private String memberRole;
@Column(name="STATUS")
private String status;
3-3. PrimaryKey
- 데이터베이스마다 기본 키를 생성하는 방식이 서로 다르므로 이 문제를 해결하기는 쉽지 않음
- 문제 해결을 위해 3가지 방식 제공
- 오라클 데이터베이스는 시퀀스를 제공하지만 MYSQL은 시퀀스 대신 자동으로 숫자를 증가시켜주는 AUTO_INCREMENT기능을 제공.
- AUTO_INCREMENT같은 기능은 IDENTITY 설정으로, SEQUENCE를 이용하는 곳에서는 SEQUENCE 설정으로 기본 키 사용을 매핑할 수 있음.
-> 따라서 SEQUENCE나 IDENTITY전략은 사용하는 데이터베이스에 의존하게 된다.
- @Id 적용이 가능한 자바 타입
- 자바 기본형
- 자바 Wrapper 타입
- String
- java.util.Date
- java.sql.Date
- java.math.BigDecimal
- java.math.BigInteger
1) 시퀀스 전략
@Test
public void 식별자_매핑_테스트() {
Member member = new Member();
member.setMemberId("user01");
member.setMemberPwd("pass01");
member.setNickname("홍길동");
member.setPhone("010-1234-5678");
member.setAddress("서울시 종로구");
member.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member.setMemberRole("ROLE_MEMBER");
member.setStatus("Y");
Member member2 = new Member();
member2.setMemberId("user02");
member2.setMemberPwd("pass02");
member2.setNickname("유관순");
member2.setPhone("010-1234-5678");
member2.setAddress("서울시 종로구");
member2.setEnrollDate(new java.sql.Date(System.currentTimeMillis()));
member2.setMemberRole("ROLE_MEMBER");
member2.setStatus("Y");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityManager.persist(member2);
entityTransaction.commit();
String jpql = "SELECT A.memberNo FROM sequence_member A";
List<Integer> memberNoList = entityManager.createQuery(jpql, Integer.class).getResultList();
memberNoList.forEach(System.out::println);
}
@Entity(name="sequence_member")
@Table(name="TBL_MEMBER_SECTION03_01")
@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;
2) 테이블 전략
@Entity(name="sequence_table_member")
@Table(name="TBL_MEMBER_SECTION03_02")
@TableGenerator(
name="MEMBER_SEQ_TABLE_GENERATOR",
table="TBL_MY_SEQUENCES",
pkColumnValue="MY_SEQ_MEMBER_NO",
allocationSize=1
)
public class Member {
@Id
@Column(name="MEMBER_NO")
@GeneratedValue(strategy = GenerationType.TABLE, generator="MEMBER_SEQ_TABLE_GENERATOR")
private int memberNo;
3-4. Enum
@Column(name="MEMBER_ROLE")
@Enumerated(EnumType.STRING)
private RoleType memberRole;
3-5. Access
- JPA가 엔티티 데이터에 접근하는 방식을 지정할 수 있으며 두 가지 방식이 있음
- 필드 접근
- 필드에 직접 접근하여 필드 접근 권한이 private 이어도 접근 가능
- 프로퍼티 접근
- 접근자(Getter)를 이용하여 접근하는 방식
- 만약 @Access를 설정하지 않으면 @Id의 위치를 기준으로 접근 방식 설정
- 기본적으로 JPA에서는 필드 접근 사용 ( @Access(AccessType.Field) 속성은 생략 가능
- 다른 로직을 처리하거나 값을 검증하는 추가 로직을 수행해야 하는 경우에는 프로퍼티 접근 방식을 혼용해서 사용
1) Field
@Entity(name="field_access_member")
@Table(name="TBL_MEMBER_SECTION05_01")
public class Member {
@Id
@Column(name="MEMBER_NO")
private int memberNo;
@Column(name="MEMBER_ID")
private String memberId;
@Column(name="MEMBER_PWD")
private String memberPwd;
@Column(name="NICKNAME")
private String nickname;
2) Property
@Entity(name="property_access_member")
@Table(name="TBL_MEMBER_SECTION05_02")
public class Member {
@Id
@Column(name="MEMBER_NO")
private int memberNo;
@Column(name="MEMBER_ID")
private String memberId;
@Column(name="MEMBER_PWD")
private String memberPwd;
@Column(name="NICKNAME")
private String nickname;
public Member() {}
public Member(int memberNo, String memberId, String memberPwd, String nickname) {
super();
this.memberNo = memberNo;
this.memberId = memberId;
this.memberPwd = memberPwd;
this.nickname = nickname;
}
public int getMemberNo() {
System.out.println("getMembeNo()를 이용한 access 확인");
return memberNo;
}
@Access(AccessType.PROPERTY)
public String getNickname() {
System.out.println("getNickname()를 이용한 access 확인");
return nickname + "님";
}
@Test
public void 프로퍼티_접근_테스트() {
Member member = new Member();
member.setMemberNo(1);
member.setMemberId("user01");
member.setMemberPwd("pass01");
member.setNickname("홍길동");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityTransaction.commit();
String jpql = "SELECT a.nickname FROM property_access_member a WHERE a.memberNo=1";
String registedNickname = entityManager.createQuery(jpql, String.class).getSingleResult();
assertEquals("홍길동님", registedNickname);
}
3-6. CompositeKey
- 복합키가 존재하는 테이블 매핑의 경우 별도의 방법 필요
- @Embeddable
- @Embeddabel 클래스에 복합 키를 정의하고 엔티티에 @EmbeddedId를 이용해 복합 키 클래스를 매핑
- @IdClass
- 복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @Id를 매핑
- 두 방식 모두 복합키 클래스는 영속성 컨텍스트가 관리하지 않는다는 특징이 있음
- 두 방식 모두 기능적 차이 존재 하지 않음
- @Embeddable이 조금 더 객체 지향적 방법
1) Embeddable
@Test
public void 임베디드_아이디_사용한_복합키_테이블_매핑_테스트() {
Member member = new Member();
member.setMemberPK(new MemberPK(1, "user01"));
member.setPhone("010-1234-5678");
member.setAddress("서울시 종로구");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityTransaction.commit();
String nativeSQL = "SELECT UC.CONSTRAINT_NAME, UC.CONSTRAINT_TYPE, UCC.COLUMN_NAME " +
"FROM USER_CONSTRAINTS UC " +
"JOIN USER_CONS_COLUMNS UCC ON (UC.CONSTRAINT_NAME = UCC.CONSTRAINT_NAME) " +
"WHERE UCC.TABLE_NAME = 'TBL_MEMBER_SECTION06_01'" +
"AND UC.CONSTRAINT_TYPE = 'P'";
List<Object[]> pkColumnList = entityManager.createNativeQuery(nativeSQL).getResultList();
for(Object[] pkColumn : pkColumnList) {
for(Object info : pkColumn) {
System.out.print(info + " ");
}
System.out.println();
}
}
@Entity(name="embedded_member")
@Table(name="TBL_MEMBER_SECTION06_01")
public class Member {
@EmbeddedId
private MemberPK memberPK;
@Column(name="PHONE")
private String phone;
@Column(name="ADDRESS")
private String address;
@Embeddable
public class MemberPK implements Serializable{
@Column(name="MEMBER_NO")
private int memberNo;
@Column(name="MEMBER_ID")
private String memberId;
2) IdClass
@Entity(name="idclass_member")
@Table(name="TBL_MEMBER_SECTION06_02")
@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;
public class MemberPK implements Serializable{
private int memberNo;
private String memberId;
@Test
public void 임베디드_아이디_사용한_복합키_테이블_매핑_테스트() {
Member member = new Member();
member.setMemberNo(1);
member.setMemberId("user01");
member.setPhone("010-1234-5678");
member.setAddress("서울시 종로구");
EntityTransaction entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
entityManager.persist(member);
entityTransaction.commit();
String nativeSQL = "SELECT UC.CONSTRAINT_NAME, UC.CONSTRAINT_TYPE, UCC.COLUMN_NAME " +
"FROM USER_CONSTRAINTS UC " +
"JOIN USER_CONS_COLUMNS UCC ON (UC.CONSTRAINT_NAME = UCC.CONSTRAINT_NAME) " +
"WHERE UCC.TABLE_NAME = 'TBL_MEMBER_SECTION06_02'" +
"AND UC.CONSTRAINT_TYPE = 'P'";
List<Object[]> pkColumnList = entityManager.createNativeQuery(nativeSQL).getResultList();
for(Object[] pkColumn : pkColumnList) {
for(Object info : pkColumn) {
System.out.print(info + " ");
}
System.out.println();
}
}