JPA-05 EntityMapping

yj k·2023년 4월 9일
0

jpa

목록 보기
5/14

JPA는 엔터티와 테이블을 매핑하기위한 어노테이션을 지원한다.

크게 4가지로 분류한다.

  • 객체와 테이블 매핑 : @Entity, @Table
  • 기본 키 매핑 : @Id
  • 필드와 컬럼 매핑 : @Column

@Entity

@Entity 어노테이션으로 테이블과 매핑한다.
JPA를 사용해서 테이블과 매핑할 클래스는 필수로 붙여야한다.
@Entity가 붙은 클래스는 JPA가 관리하는 것으로 엔터티라 부른다.

해당 클래스를 엔티티로 설정하기 위한 어노테이션
프로젝트 내에 다른 패키지에도 동일한 엔티티가 존재하는 경우 해당 엔티티를 식별하기 위한 중복되지 않은 name을 지정해주어야한다.

@Entity 속성 :

  • name : JPA에서 사용할 엔터티 이름을 지정

주의사항

  • 기본 생성자 필수로 작성
  • 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());
		
	}

DDL은 autocommit 구문이기 때문에 테이블은 생성되어있다. 생성된 자료형은 java object의 자료형을 따르며 크기를 따로 설정하지 않을 경우 숫자는 number(10, 0), 문자열은 varchar2(255 char)로 설정된다. 생성되는 컬럼의 순서는 PK가 우선이며, 일반 컬럼은 유니코드 오름차순으로 생성된다.

@Table

@Table은 엔터티와 매핑할 테이블을 지정.
생략하면 매핑한 엔터티의 이름을 테이블 이름으로 사용한다.

@Table 속성 :

  • name : 매핑될 테이블의 이름을 작성한다. 생략하면 자동으로 클래스의 이름을 테이블의 이름으로 사용한다.
  • 해당 테이블이 없을 시 자동 생성도 가능.
    xml파일에 설정 코드 추가 필요
<property name="hibernate.hbm2ddl.auto" value="create"/>

@Column

@Column은 객체 필드를 테이블 컬럼에 매핑.

@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 구분을 직접 사용하지 않는 것이 더 바람직하다.


@Transient

이 필드는 매핑하지 않는다.
데이터베이스에 저장하지 않고 조회도 하지 않는다.
객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.


@Temporal

@Temporal은 날짜 타입을 매핑할 때 사용.
@Temporal을 사용하기 위해서는 java.sql.Date가 아닌 java.util.Date를 사용해야한다.

  • @Temporal(TemporalType.TIMESTAMP)
: DATE + TIME으로 날짜 및 시간이 나온다.
  • @Temporal(TemporalType.DATE)
: ORACLE에서는 TIMESTAMP와 차이가 없다.
  • Temporal(TemporalType.TIME)
: 1970/01/01에 시간만 맞게 나온다.

Primary Key(기본키 매핑)


영속성 컨텍스트는 엔터티를 식별자 값으로 구분하므로 엔터티를 영속 상태로 만들려면 식별자 값이 반드시 있어야한다.


기본 키 직접 할당 전략으로 entityManager.persist()로 엔터티를 저장하기 전에 application에서 기본키를 직접 할당하는 방법이 있다. @Id 어노테이션으로 긱본 키를 application에서 직접 할당하는 방법이 있다.
@Id
@Column(name="id")
private String id;

이외에 기본 키를 데이터베이스가 생성해주는 값으로 사용하는 전략(방법)

  1. Identity : 기본 키 생성을 데이터베이스에 위임한다.(오라클 사용 불가능)
  2. Sequence : 데이터베이스 시퀀스를 사용해서 기본키 할당.
  3. Table : 키 생성 테이블을 사용.

Sequence 전략

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 데이터베이스 오브젝트이다. 이 시퀀스를 사용하여 기본키 생성.

@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);
	}

Table 전략

키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략.
테이블을 사용하므로 모든 데이터베이스에 사용할 수 있다.

@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

@Enumerated은 자바의 enum 타입을 매핑할 때 사용.

public enum RoleType {
	
	ADMIN, MEMBER

}

@Enumerated 속성 :

  • EnumType.ORDINAL :
    enum의 상수 값을 사용한다는 의미(기본 값)
    enum에 정의된 순서대로 값이 데이터베이스에 저장된다.

    ADMIN : 0 MEMBER : 1

장점 : 데이터 베이스에 저장되는 데이터 크기가 작다.
단점 : 이미 저장된 enum의 순서를 변경할 수 없다.

	@Column(name="MEMBER_ROLE")
	@Enumerated(EnumType.ORDINAL)
	private RoleType memberRole;
  • EnumType.STRING :
    enum의 문자 그대로를 사용한다는 의미
    장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가되어도 안전하다.
    단점 : 데이터 베이스에 저장되는 데이터의 크기가 ordinal에 비해 크다.
	@Column(name="MEMBER_ROLE")
	@Enumerated(EnumType.STRING)
    private RoleType memberRole;

@Access

JPA가 엔터티 데이터에 접근하는 방식을 지정

  1. 필드 접근(AccessType.FIELD) : 필드에 직접 접근하며 필드 접근 권한이 private여도 접근할 수 있다.
  2. 프로퍼티 접근(AccessType.PROPERTY) : 접근자(getter)를 이용하여 접근하는 방식이다.

선언 위치
1. 클래스 레벨 : 모든 필드에 대해서 적용
2. 필드 레벨 : 해당 필드의 접근 방식을 필드 접근으로 바꿀 수 있다.
@Id 어노테이션이 필드 레벨에 존재하는 경우 해당 필드는 @Access(AccessType.FIELD)이다. 따라서 어노테이션을 생략해도 무방하다.

@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)

클래스 레벨에 @Access(AccessType.PROPERTY)를 작성할 때 주의할 점은
@Id 어노테이션이 필드에 있다면 엔티티 매니저 자체를 생성하지 못하기 때문에 @Id 어노테이션을
getter 메소드 위로 옮겨야한다.

	
	/* 메소드 레벨 - 해당 메소드의 접근 방식만 변경한다. */
	@Id
	public int getMemberNo() {

		return memberNo;
	}

복합키가 존재하는 테이블 매핑

복합키가 존재하는 테이블 매핑의 경우 별도의 방법이 필요하다.


  1. @Embeddable : @Embeddable 클래스에 복합키를 정의하고 엔티티에 @Embeddable을 이용해 복합 키 클래스를 매핑한다.

  2. @IdClass : 복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @Id를 매핑한다.

두 방식 모두 복합키 클래스는 영속성 컨텍스트가 관리하지 않는다는 특징이 잇으며, 큰 기능적 차이도 존재하지 않는다.
다만 @Embeddable이 조금 더 객체 지향다운 방법이고,
@IdClass는 관계형 데이터 베이스에 가까운 방법이다.


@Embeddable

@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;
    
    ...생략
    
}

@IdClass

복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @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;
    
    ...생략
}    

0개의 댓글