Mapping

MINIMI·2023년 4월 10일

JPA

목록 보기
3/9
post-thumbnail

3-1. Entity

  • 엔티티와 테이블을 정확하게 매핑하는 것이 JPA의 핵심
  • 다양한 매핑 어노테이션 지원
@Test
	public void 테이블_만들기_테스트() {
		
		//given
		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");
		
		//when
		entityManager.persist(member);
		
		//then
		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가 우선.
  • 일반 컬럼은 유니코드 오름차순으로 생성
  • 테이블 자동 생성을 위한 xml 설정
<!-- DML 구문 수행 시 자동으로 스키마 생성(테이블 생성)을 위한 설정 추가 -->
			<property name="hibernate.hbm2ddl.auto" value="create"/>

3-2. Column

@Test
	public void 컬럼에서_사용하는_속성_테스트() {

		// given
		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");

		// when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		entityManager.persist(member);
		entityTransaction.commit();

		// then
		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", unique=true)
	@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을 사용하기 위해서는 java.sql.Date가 아닌 java.util.Date를 사용해야 한다.
//	@Temporal(TemporalType.TIMESTAMP)		//DATE + TIME 으로 날짜 및 시간이 나온다.
//	@Temporal(TemporalType.DATE)			//ORACLE에서는 TIMESTAMP와 차이가 없다.
	@Temporal(TemporalType.TIME)			//1970/01/01에 시간만 맞게 나온다.
	private Date enrollDate;
	
	@Column(name="MEMBER_ROLE")
	private String memberRole;
	@Column(name="STATUS")
	private String status;

3-3. PrimaryKey

  • 데이터베이스마다 기본 키를 생성하는 방식이 서로 다르므로 이 문제를 해결하기는 쉽지 않음
  • 문제 해결을 위해 3가지 방식 제공
    • IDENTITY
      • 기본 키 생성을 데이터베이스에 위임
    • SEQUENCE
      • 데이터베이스 시퀀스를 사용해서 기본 키 할당
    • TABLE
      • 키 생성 테이블 사용
  • 오라클 데이터베이스는 시퀀스를 제공하지만 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 식별자_매핑_테스트() {
		
		// given
		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");

		// 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);
	}
@Entity(name="sequence_member")
@Table(name="TBL_MEMBER_SECTION03_01")
@SequenceGenerator(
		name="MEMBER_SEQUENCE_GENERATOR",	//식별자 생성기 이름
		sequenceName="SEQ_MEMBER_NO",		//데이터베이스에 등록 되어 있는 시퀀스 이름
		initialValue=5,						//초기값
		allocationSize=1					//기본 값이 50으로 설정 되어 있고 시퀀스 증가와 별개로 메모리에서 식별자를 할당하여 매번 시퀀스를 호출하지 않도록 하는 성능 최적화를 위해 설정 된 값.
											//여러 요청이 동시에 접근해서 데이터를 등록할 때 기본 값이 충돌하지 않는 장점(동시성 문제 해결)이 있지만, 시퀀스가 한 번에 많이 증가하는 점을 염두에 두어야 함.
		)
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",	//PK 컬럼의 이름 지정
		// pkColumnName : 시퀀스 컬럼명 (기본 값은 SEQUENCE_NAME)
		// valueColumnName : 시퀀스 값 컬럼명 (기본 값은 NEXT_VAL)
		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.ORDINAL)	//enum의 상수 값을 사용한다는 의미 - 기본 값 
	//장점 : 데이터 베이스에 저장되는 데이터의 크기가 작다. 
	//단점 : 이미 저장된 enum의 순서를 변경할 수 없다.
	@Enumerated(EnumType.STRING)	//enum의 문자 그대로를 사용한다는 의미
	//장점 : 저장된 enum의 순서가 바뀌거나 enum이 추가 되어도 안전 
	//단점 : 데이터 베이스에 저장 되는 데이터의 크기가 ordinal에 비해 크다.
	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")
/* 1. 클래스 레빌 : 모든 필드에 대해서 적용 */
//@Access(AccessType.FIELD)
public class Member {
	
	/* 2. 필드 레벨 : 해당 필드의 접근 방식을 필드 접근으로 바꿀 수 있다. 
	 * @Id 어노테이션이 필드 레벨에 존재한느 경우 해당 필드는 @Access(AccessType.FILED)이다. 따라서 어노테이션을 생략해도 무방하다. */
	@Id
	@Column(name="MEMBER_NO")
//	@Access(AccessType.FIELD)
	private int memberNo;
	
	@Column(name="MEMBER_ID")
//	@Access(AccessType.FIELD)
	private String memberId;
	
	@Column(name="MEMBER_PWD")
//	@Access(AccessType.FIELD)
	private String memberPwd;
	
	@Column(name="NICKNAME")
//	@Access(AccessType.FIELD)
	private String nickname;

2) Property

@Entity(name="property_access_member")
@Table(name="TBL_MEMBER_SECTION05_02")
/* 1. 클래스 레벨 : 모든 필드에 대해서 적용 */
//@Access(AccessType.PROPERTY)
/* 클래스 레벨에 @Access(AccessType.PROPERTY)를 작성할 때 주의할 점은
 * @Id 어노테이션이 필드에 있다면 엔티티 매니저 자체를 생성하지 못하기 때문에 @Id 어노테이션을 getter 메소드 위로 옮겨야 한다. */
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;
	}

	//@Id
	public int getMemberNo() {
		System.out.println("getMembeNo()를 이용한 access 확인");
		
		return memberNo;
	}
    
    
    /* 3. 메소드 레벨 : 해당 메소드의 접근 방식만 변경한다. */
	@Access(AccessType.PROPERTY)
	public String getNickname() {
		System.out.println("getNickname()를 이용한 access 확인");
		return nickname + "님";
	}
@Test
	public void 프로퍼티_접근_테스트() {
		
		//given
		Member member = new Member();
		member.setMemberNo(1);
		member.setMemberId("user01");
		member.setMemberPwd("pass01");
		member.setNickname("홍길동");
		
		//when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		entityManager.persist(member);
		entityTransaction.commit();
		
		//then
		String jpql = "SELECT a.nickname FROM property_access_member a WHERE a.memberNo=1";
		String registedNickname = entityManager.createQuery(jpql, String.class).getSingleResult();
		assertEquals("홍길동님", registedNickname);
		
		
	}
	
	/* @Id의 위치에 따라 @Access 방식이 결정되며 이는 전역적인 설정이다.
	 * @Access(AccessType.PROPERTY) 설정을 getter에 지정하게 되면 특정 필드만 Getter 메소드로 접근할 수 있으며, 추가 로직이 필요한 경우 설정한다. */

3-6. CompositeKey

  • 복합키가 존재하는 테이블 매핑의 경우 별도의 방법 필요
  • @Embeddable
    • @Embeddabel 클래스에 복합 키를 정의하고 엔티티에 @EmbeddedId를 이용해 복합 키 클래스를 매핑
  • @IdClass
    • 복합키를 필드로 정의한 클래스를 이용해 엔티티 클래스에 복합키에 해당하는 필드에 @Id를 매핑
  • 두 방식 모두 복합키 클래스는 영속성 컨텍스트가 관리하지 않는다는 특징이 있음
  • 두 방식 모두 기능적 차이 존재 하지 않음
  • @Embeddable이 조금 더 객체 지향적 방법

1) Embeddable

@Test
	public void 임베디드_아이디_사용한_복합키_테이블_매핑_테스트() {
		
		//given
		Member member = new Member();
		member.setMemberPK(new MemberPK(1, "user01"));
		member.setPhone("010-1234-5678");
		member.setAddress("서울시 종로구");
		
		//when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		entityManager.persist(member);
		entityTransaction.commit();
		
		//then
		/* 제약 조건 딕셔너리뷰를 조회하여 복합키에 대한 제약조건 확인 */
        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 임베디드_아이디_사용한_복합키_테이블_매핑_테스트() {
		
		//given
		Member member = new Member();
		member.setMemberNo(1);
		member.setMemberId("user01");
		member.setPhone("010-1234-5678");
		member.setAddress("서울시 종로구");
		
		//when
		EntityTransaction entityTransaction = entityManager.getTransaction();
		entityTransaction.begin();
		entityManager.persist(member);
		entityTransaction.commit();
		
		//then
		/* 제약 조건 딕셔너리뷰를 조회하여 복합키에 대한 제약조건 확인 */
        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();
        }
        
        
        
	}
profile
DREAM STARTER

0개의 댓글