[Spring Boot2][1] 4. 엔티티 매핑

sorzzzzy·2021년 9월 30일
0

Spring Boot - RoadMap 2

목록 보기
4/26
post-thumbnail

🏷 객체와 테이블 매핑


✔️ 엔티티 매핑 소개

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

✔️ @Entity

  • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수

주의 🤚🏻

  • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
  • final 클래스, enum, interface, inner 클래스 사용 X
  • 저장할 필드에 final 사용 X

✔️ @Entity 속성 정리

  • 속성 : name
    • JPA에서 사용할 엔티티 이름을 지정한다.
    • 기본값 : 클래스 이름을 그대로 사용(예 : Member)
    • 같은 클래스 이름이 없으면 가급적 기본값을 사용

✔️ @Table


➡️ @Table은 엔티티와 매핑할 테이블 지정



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


✔️ 데이터베이스 스키마 자동 생성

  • DDL을 애플리케이션 실행 시점에 자동 생성
  • 테이블 중심 ➡️ 객체 중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성
  • 이렇게 생성된 DDL은 개발 장비에서만 사용
  • 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

✔️ 데이터베이스 스키마 자동 생성 - 속성

hibernate.hbm2ddl.auto

주의 🤚🏻

  • **운영 장비에는 절대 create, create-drop, update 사용하면 안된다❗️&&
  • 개발 초기 단계는 create 또는 update
  • 테스트 서버에는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

✔️ DDL 생성 기능

  • 제약조건 추가 : 회원 이름은 필수, 10자 초과X

    • @Column(nullable = false, length = 10)
  • 유니크 제약조건 추가

    • @Table(uniqueConstraints = {@UniqueConstraint( name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"} )})
  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다!



🏷 필드와 컬럼 매핑

📌 요구사항 추가
1️⃣ 회원은 일반 회원과 관리자로 구분해야 한다.
2️⃣ 회원 가입일과 수정일이 있어야 한다.
3️⃣ 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다.


Member.java 수정

package hellojpa;

import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;

@Entity
public class 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;
}

✔️ 매핑 어노테이션 정리

hibernate.hbm2ddl.auto


✔️ @Column


✔️ @Enumerated


➡️ 자바 enum 타입을 매핑할 때 사용
➡️ 주의❗️ ORDINAL 사용 X


✔️ @Temporal


➡️ 날짜 타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용
➡️ LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)


✔️ @Lob

  • 데이터베이스 BLOB, CLOB 타입과 매핑
  • @Lob에는 지정할 수 있는 속성이 없다.
  • 매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
    • CLOB : String, char[], java.sql.CLOB
    • BLOB : byte[], java.sql. BLOB

✔️ @Transient

  • 필드 매핑 X
  • 데이터베이스에 저장 X, 조회 X
  • 주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때 사용
@Transient
private Integer temp;


🏷 기본 키 매핑


✔️ 기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue

✔️ 기본 키 매핑 방법

1️⃣ 직접 할당 : @Id 만 사용

2️⃣ 자동 생성(@GeneratedValue)

  • IDENTITY : 데이터베이스에 위임, MYSQL
  • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
    • @SequenceGenerator 필요
  • TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
    • @TableGenerator 필요
  • AUTO : 방언에 따라 자동 지정, 기본값

✔️ TABLE 전략

  • 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉 내내는 전략
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 성능이 떨어진다..^_^

📌 @TableGenerator - 속성


✔️ TABLE 전략 - 매핑

1️⃣ 테이블 생성

create table MY_SEQUENCES (
	sequence_name varchar(255) not null,
	next_val bigint,
	primary key ( sequence_name )
)

2️⃣ Member.java 수정

@Entity
@TableGenerator(
	name = "MEMBER_SEQ_GENERATOR",
	table = "MY_SEQUENCES",
	pkColumnValue = “MEMBER_SEQ", allocationSize = 1)

public class Member {
	@Id
	@GeneratedValue(strategy = GenerationType.TABLE,
			generator = "MEMBER_SEQ_GENERATOR")
	private Long id;


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

✔️ 기능 목록

  • 회원기능

    • 회원등록
    • 회원조회
  • 상품기능

    • 상품등록
    • 상품수정
    • 상품조회
  • 주문기능

    • 상품주문
    • 주문내역조회
    • 주문취소
  • 회원과 주문의 관계 : 회원은 여러 번 주문할 수 있다. (일대다)
  • 주문과 상품의 관계 : 주문 할 때 여러 상품을 선택할 수 있다. 반대로 같은 상품도 여러 번 주문될 수 있다.

✔️ 테이블 설계


✔️ 엔티티 설계와 매핑


✔️ 실습

✔️ JpaMain

package jpabook.jpashop;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.Order;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        // EntityManagerFactory
        // 웹 서버가 올라오는 시점, 디비가 생성되는 시점에 딱 하나만 생성됨
        // 쓰레드 간에 공유를 절대 해서는 안 된다
        // JPA의 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        // 트랜잭션 생성
        EntityTransaction tx = em.getTransaction();
        // 데이터베이스 트랜잭션 시작
        tx.begin();

        try {
            // 1. 주문을 찾는다
            Order order = em.find(Order.class, 1L);

            // 2. 그 주문을 한 멤버를 찾는다
            Member findMember = order.getMember();

            tx.commit();
        } catch (Exception e) {
            // 문제가 생겼을 경우 롤백
            tx.rollback();
        } finally {
            // 실제 애플리케이션이 완전히 끝나면 종료
            em.close();
        }
        emf.close();
    }
}

✔️ Member

package jpabook.jpashop.domain;

import javax.persistence.*;

import static javax.persistence.GenerationType.AUTO;

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

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }
}

✔️ Order

package jpabook.jpashop.domain;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "ORDERS")
public class Order {
    @Id @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

    // 누가 주문했는 지 알기 위함
    @Column(name = "MEMBER_ID")
    private Long memberId;

    private Member member;

    public Member getMember() {
        return member;
    }

    private LocalDateTime orderDate;

    // enum 타입은 무조건 string 으로
    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    public Long getId() {
        return id;
    }

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

    public Long getMemberId() {
        return memberId;
    }

    public void setMemberId(Long memberId) {
        this.memberId = memberId;
    }

    public LocalDateTime getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(LocalDateTime orderDate) {
        this.orderDate = orderDate;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public void setStatus(OrderStatus status) {
        this.status = status;
    }
}

나머지 클래스(OrderItem, Item)도 요구사항에 맞춰 개발❗️

pom.xmlpersistence.xml 파일은 지난번에 실습했던 파일과 동일❗️
➡️ 단, persistence.xml 의 h2 데이터베이스 부분 이름을 jpashop 으로 변경


➡️ jpashop 이라는 새로는 h2 데이터베이스를 만들고, 실행하면 성공적으로 잘 반영됨 👍🏻


그런데❗️

상품을 주문한 사람을 찾고 싶을 때 위의 코드대로라면,


// Order.java
private Member member;

public Member getMember() {
	return member;
}

⬆️ Order에서 멤버를 찾는 메소드를 하나 만들고,


// JpaMain.java
try {
	// 1. 주문을 찾는다
	Order order = em.find(Order.class, 1L);

	// 2. 그 주문을 한 멤버를 찾는다
	Member findMember = order.getMember();

	tx.commit();
}

⬆️ 메인 메소드에서 이와 같이 찾아야 하는데

이것은 뭔가 객체 지향스럽지 않다🤔
➡️ 객체보다는 관계형 데이터베이스에 의존하고 있음!!


✔️ 데이터 중심 설계의 문제점

  • 현재 방식은 객체 설계를 테이블 설계에 맞춘 방식
  • 테이블의 외래키를 객체에 그대로 가져옴
  • 객체 그래프 탐색이 불가능
  • 참조가 없으므로 UML도 잘못됨

➡️ 다음시간부터는 이를 해결할 수 있는연관 관계 매핑에 대해 배울 예정이다😀


profile
Backend Developer

0개의 댓글