개인 공부/JPA

04 엔티티 매핑

hanyugyeong 2023. 9. 20. 16:30
반응형
SMALL

JPA는 다양한 매핑 어노테이션을 지원하는데 크게 4가지로 분류할 수 있다. 

* 객체와 테이블 매핑:@Entity,@Table 

* 기본 키 매핑  : @Id

* 필드와 컬럼 매핑 : @Column

* 연관관계 매핑 : @ManyToOne, @JoinColumn

4.1 @Entity

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

 

@Entity 적용 시 주의사항은 다음과 같다 

* 기본 생성자는 필수다. 

* final 클래스, enum,interface, inner 클래스에는 사용할 수 없다. 

* 저장할 필드에 final을 사용하면 안 된다. 

4.2 @Table

속성 기능  기본값
name 매핑할 테이블 이름  엔티티 이름을 사용한다. 
catalog catalog 기능이 있는 데이터베이스에서 catalog를 매핑한다.  
schema schema 기능이 있는 데이터베이스에서 
schema를 매핑한다.
 
uniqueConstraint(DDL)  DDL 생성 시에 유니크 제약 조건을 만든다.  

4.3 다양한 매핑 사용

회원 관리 프로그램에 다음 요구사항이 추가되었다. 

1. 회원은 일반 회원과 관리자로 구분해야 한다. 

2. 회원 가입일과 수정일이 있어야 한다. 

3. 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다. 

package hellojpa;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name="MEMBER")
public class Member {

	@Id @GeneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;

	@Column(name="NAME")
	private String username;

	private Integer age;

	//== 추가 ==
	@Enumerated(EnumType.STRING)
	private RoleType roleType;

	@Temporal(TemporalType.TIMESTAMP)
	private Date createdDate;

	@Temporal(TemporalType.TIMESTAMP)
	private Date lastModifiedDate;

	@Lob
	private String description;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public Integer getAge() {
		return age;
	}

	public void setAge(Integer age) {
		this.age = age;
	}

	public RoleType getRoleType() {
		return roleType;
	}

	public void setRoleType(RoleType roleType) {
		this.roleType = roleType;
	}

	public Date getCreatedDate() {
		return createdDate;
	}

	public void setCreatedDate(Date createdDate) {
		this.createdDate = createdDate;
	}

	public Date getLastModifiedDate() {
		return lastModifiedDate;
	}

	public void setLastModifiedDate(Date lastModifiedDate) {
		this.lastModifiedDate = lastModifiedDate;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}
}

1) roleType: 자바 enum을 사용해서 회원의 타입을 구분했다. 일반 회원 USER, 관리자는 ADMIN이다. 이처럼 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야 한다. 

2) createdDate, lastModifiedDate: 자바의 날짜 타입은 @Temporal을 사용해서 매핑한다. 

3) description: 회원을 설명하는 필드는 길이 제한이 없다.데이터베이스의 VARCHAR 타입 대신에 CLOB 타입으로 저장해야 한다. 

4.4 데이터베이스 스키마 자동 생성 

JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다. 

클래스의 매핑 정보를 보면 어떤 테이블에 어떤 칼럼을 사용하는지 알 수 있다. 

@Entity
@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint( //추가
	name = "NAME_AGE_UNIQUE",
	columnNames = {"NAME","AGE"})})
public class Member {

	@Id @GeneratedValue
	@Column(name = "MEMBER_ID")
	private Long id;

	@Column(name="NAME")
	private String username;

	private Integer age;

	//== 추가 ==
	@Enumerated(EnumType.STRING)
	private RoleType roleType;

	@Temporal(TemporalType.TIMESTAMP)
	private Date createdDate;

	@Temporal(TemporalType.TIMESTAMP)
	private Date lastModifiedDate;

	@Lob
	private String description;
}
 alter table MEMBER 
       add constraint NAME_AGE_UNIQUE unique (NAME, age)

@Column의 length와 nullable 속성을 포함해서 이런 기능들은 단지 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

4.5 DDL 생성 기능 

회원 이름은 필수로 입력되어야 하고, 10자를 초과하면 안 된다는 제약조건이 추가되었다. 

@Column(name="NAME", nullable = false, length = 10) //추가
private String username;

DDL의 NAME 칼럼을 보면 not null 제약조건이 추가되었고, vachar(10) 으로 문자의 크기가 10자리로 제한된 것을 확인할 수 있다. 

유니크 제약조건을 만들어 주는 @Table의 uniqueConstraints 속성을 알아보자

@Table(name="MEMBER", uniqueConstraints = {@UniqueConstraint( //추가
	name = "NAME_AGE_UNIQUE",
	columnNames = {"NAME","AGE"})})
alter table MEMBER 
       add constraint NAME_AGE_UNIQUE unique (NAME, age)

4.6 기본 키 매핑 

JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다. 

* 직접 할당: 기본 키를 애플리케이션에서 직접 할당한다. 

* 자동 생성: 대리 키 사용 방식 

- IDENTITY : 기본 키 생성을 데이터베이스에 위임한다. 

- SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다. 

- TABLE : 키 생성 테이블을 사용한다. 

4.6.1 기본 키 직접 할당 전략 

기본 키를 직접 할당하려면 다음 코드와 같이 @Id로 매핑하면 된다. 

@Id
@Column(name = "id")
private Long id;

4.6.2 IDENTITY  전략 

IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. 

출력된 값 1은 저장 시점에 데이터베이스가 생성한 값ㅇ르 JPA가 조회한 것이다. 

private static void logic(EntityManager em){
     Board board = new Board();
      em.persist(board);
      System.out.println("board.id = "+board.getId());
}

4.6.3 SEQUENCE 전략 

SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다. 

@Entity
@SequenceGenerator(
	name="BOARD_SEQ_GENERATOR",
	sequenceName = "BOARD_SEQ", //매핑할 데이터베이스 시퀀스 이름
	initialValue = 1, allocationSize = 1
)
public class Board {
	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE,
					generator = "BOARD_SEQ_GENERATOR")
	private Long id;
 }

@SequenceGenerator

@SequenceGenerator를 분석해보자 

속성 기능 기본값
name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정한다. 1
allocationSize 시퀀스 한 번 호출에 증가하는 수 50
catalog, schema 데이터베이스 catalog, schema 이름   

4.6.4 TABLE 전략 

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

create table MY_SEQUENCES (
	sequence_name varchar(255) not null,
    next_val bigint,
    primary key (sequence_name)
)
@Entity
@TableGenerator(
	name="BOARD_SEQ_GENERATOR",
	table = "MY_SEQUENCES",
	pkColumnValue = "BOARD_SEQ", allocationSize = 1
)
public class Board {
	@Id
	@GeneratedValue(strategy = GenerationType.TABLE,
					generator = "BOARD_SEQ_GENERATOR")
	private Long id;

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}
}

4.6.5 AUTO 전략 

데이터베이스의 종류도 많고 기본 키를 만드는 방법도 다양하다. GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. 

@Entity
public class Board {
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private Long id;
}

AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. 

4.6.6 기본 키 매핑 정리 

* 직접 할당: em.persist() 를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다. 

* SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. 

* TABLE: 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. 

* IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. 

4.7 필드와 칼럼 매핑: 래퍼런스 

JPA가 제공하는 필드와 칼럼 매핑용 어노테이션들을 레퍼런스 형식으로 정리해보았다. 

4.7.1 @Column

@Column은 객체 필드를 테이블 칼럼에 매핑한다. 가장 많이 사용되고 기능도 많다. 

insertable, updatable 속성은 데이터베이스에 저장되어 있는 정보를 읽기만 하고 실수로 변경하는 것을 방지하고 싶을 때 사용한다. 

4.7.2 @Enumerated

자바의 enum 타입을 매핑할 때 사용한다. 

속성 기능 기본값
value *EnumType.ORDINAL: enum 순서를 데이터베이스에 저장
*EnumType.STRING : enum 이름을 데이터베이스에 저장
EnumType.ORDINAL

EnumType.String을 권장한다. 

4.7.3 @Temporal

날짜 타입을 매핑할 때 사용한다.

 

@Temporal 속성 정리 

속성 기능 기본값
 value * TemporalType.DATE : 날짜, 데이터베이스 date 타입과 매핑 
* TemporalType.TIME : 시간, 데이터베이스 time 타입과 매핑 
* TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑 
TemporalType은 필수로 지정해야 한다.

4.7.4 @Lob

데이터베이스 BLOB, CLOB 타입과 매핑한다. 

4.7.5 @Transient

이 필드는 매핑하지 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다. 

@Transient
private Integer temp;

4.7.6 @Access

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

* 필드 접근: AccessType.FIELD로 지정한다. 필드에 직접 접근한다. 필드 접근 권한이 private이어도 접근할 수 있다. 

* 프로퍼티 접근 : AccessType.PROPERTY로 지정한다. 접근자(Getter)를 사용한다. 

@Access를 설정하지 않으면 @Id의 위치를 기준으로 접근 방식이 설정된다. 

@Id가 필드에 있으므로 @Access(AccessType.FLELD)로 설정한 것과 같다. 

@Entity
@Access(AccessType.FIELD)
public class Member {

    @Id
    private String id; 
    
    private String date1;
    private String date2;
    
}

@Id가 프로퍼티에 있으므로 @Access (AccessType.PROPERTY)로 설정한 것과 같다. 따라서 @Access는 생략해도 된다.

@Entity
@Access(AccessType.PROPERTY)
public class Member {

    private String id;

    private String date1;
    private String date2;

    @Id
    public String getId() {
        return id;
    }
    @Column
    public String getDate1() {
        return date1;
    }
    public void setId(String id) {
        this.id = id;
    }

4.8 정리

객체와 테이블 매핑, 기본 키 매핑, 필드와 칼럼 매핑에 대해 알아보았다. 

 

실전 예제 1. 요구사항 분석과 기본 매핑 

1.회원(Member) 엔티티 

@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name="MEMBER_ID")
    private Long id;

    private String name;

    private String city;
    private String street;
    private String zipcode;
}

2.주문(Order)엔티티

@Entity
@Table(name="ORDERS")
public class Order {

    @Id @GeneratedValue
    @Column(name="ORDER_ID")
    private Long id;

    @Column(name="MEMBER_ID")
    private Long memberId;

    @Temporal(TemporalType.TIMESTAMP)
    private Date orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;
}

3. 주문 상태(OrderStatus)

public enum OrderStatus {
    ORDER,CANCEL
}

4. 주문상품(OrderItem)

@Entity
@Table(name="ORDER_ITEM")
public class OrderItem {

    @Id @GeneratedValue
    @Column(name="ORDER_ITEM_ID")
    private Long id;

    @Column(name="ITEM_ID")
    private Long itemId;

    @Column(name="ORDER_ID")
    private Long orderId;

    private int orderPrice;
    private int count;
}

5. 상품(Item)

@Entity
public class Item {

    @Id @GeneratedValue
    @Column(name="ITEM_ID")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;
}

데이터 중심 설계의 문제점 

객체는 참조를 사용해서 연관관계를 조회할 수 있다. 따라서 참조를 사용하는 것이 객체지향적인 방법이다. 

1. 데이터 중심으로 조회할때 

Order order = em.find(Order.class, 1);
//외래 키로 다시 조회 
Member member = em.find(Member.class,order.getMemberId());

2. 객체지향으로 조회할때 

Order order = em.find(Order.class, 1);
Member member = order.getMember();//참조 사용
반응형
LIST