JAVA/JPA 학습기록

[JPA] 실전예제1_요구사항 분석과 기본매핑까지 수강했다

kth990303 2021. 4. 17. 20:48
반응형

오늘 심심해서 프로그래머스에서 이것저것 볼 거 없나 구경하다가

이런 글을 발견했다.

programmers.co.kr/job_positions/4500

 

비바리퍼블리카(토스) - [토스뱅크(가칭)] Server Developer (Product) | 프로그래머스

개발자 커리어 플랫폼 프로그래머스에서 비바리퍼블리카(토스)의 채용공고를 확인하세요. 합격하면 사이닝보너스 50만원을 드립니다.

programmers.co.kr

네카라쿠배당토직야... 등등의 대기업에 해당되는 토스의 채용공고였다.

여기서 가장 눈에 들어오는 것은 아래 사진 부분이었다.

실제로 내가 지금 공부하고 있는 부분이 Gradle을 이용한 Spring Framework, MySQL에 ORM을 JPA로 사용하고 있는 상황이었어서 (이번 JPA 포스팅은 h2 데이터베이스지만..) 뭔가 되게 반가운 공고였다.

 

물론, 아직은 새발의 피의 헤모글로빈만큼의 실력이지만

좋은 방향으로 공부하고 있다는것만으로도 나에게 큰 의욕이 되었다.

 

이 글 보고 친구들에게 톡하고 막 공부 열심히 하자고 글썼는데...ㅋㅋㅋ 친구들한테 동기부여가 됐을지 모르겠다.


아무튼 그래서 오늘은 JPA에 대해 공부해보았다.

실전예제를 통해 기본 매핑을 해보고, 왜 테이블관계를 매핑해야 하는지 알아볼 수 있는 포스팅이 될 것이다.

 

github.com/kth990303/Jpa_Basic/tree/5ed03a43761d79ffa88757738241c30c0b1d473a

 

kth990303/Jpa_Basic

Jpa_Basic Inflearn Pracitce. Contribute to kth990303/Jpa_Basic development by creating an account on GitHub.

github.com

2021.04.17. 오늘 자 포스팅부분까지만 적용된 코드의 github 링크이다.

 

이 강의 들으면서 너무 힘들었던 점이, 

h2 database에서 test 데이터베이스 생성은 잘 됐었는데, jpashop 데이터베이스 생성은 강의처럼 잘 되지 않았다.

3시간동안 한참 헤매고 결국 해결하지 못한 나는, 

어차피 test 데이터베이스로 진행해도 크게 문제가 없다 생각해 test 데이터베이스에서 진행중이다.

 

환경세팅을 배우려고 강의듣는 게 아닌, JPA를 배우려고 강의를 듣는 것이므로,

h2 데이터베이스 환경세팅에 너무 오래 시간을 잡아먹는 것은 좋지 않다고 판단했기 때문이다.

아래는 내 질문글이다.

 

www.inflearn.com/questions/195459?re_comment_id=105689

 

h2 jpashop 데이터베이스 생성이 안됩니다. - 인프런 | 질문 & 답변

안녕하세요... 3시간 째 데이터베이스 생성에만 애를 먹고 있어서 질문드립니다. 인프런 질문들, 구글링을 다해봤는데 안돼서 애를 먹고 있는 상황입니다. 먼저, 코드실행결과는 다른 인프런 질

www.inflearn.com


도메인, 테이블 설계

회원은 여러 물품을 주문할 수 있고,

주문과 상품 사이의 다대다 관계를 막기 위해 ORDER_ITEM 중간 테이블을 삽입한 그림이다.

 

참고로, 이번 포스팅에는 테이블끼리의 연관관계 매핑은 진행하지 않고

PK, FK 이용만 해볼 것이다.


Member

package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import static javax.persistence.GenerationType.AUTO;

@Entity
public class Member {
    @Id @GeneratedValue(strategy = AUTO)
    @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;
    }
}

 

와... lombok 없으니까 이렇게 불편하네...

Getter, Setter 짜는 시간 평생동안 모으면 이렇게 될거 같단 생각이 ㅋㅋ alt + insert기능 있다 해도 lombok을 이용해보자. 신세계를 겪게 된다.

 

여기서 잠깐!

@Entity, @Id, @GeneratedValue, @Column는 뭘까?


매핑 어노테이션

@Entity

JPA를 사용해서 테이블 매핑에 이용할 클래스에 붙여주는 어노테이션이다. @Entity가 붙은 어노테이션은 JPA의 관리 하에 들어간다. 이 어노테이션이 붙으면 기본생성자가 필수적으로 들어가야 하며, Enum, final 클래스, interface엔 붙이지 않는다.

 

@Entity(name="Members") 와 같이 이름을 임의로 지정할 수도 있으며, 웬만해선 기본값인 클래스명을 사용한다.

 

@Id

PK (Primary Key) 매핑이다. 이 어노테이션이 붙으면 JPA에서 PK로 인식해준다.

 

@GeneratedValue(strategy = AUTO)

역시 기본키 매핑에 이용되는데, strategy모드는 총 4가지가 있다.

AUTO, IDENTITY, SEQUENCE, TABLE.

 

MySQL이랑 h2 db에선 주로 IDENTITY를 쓰는 듯 하다. 

자세한 내용은 여기서 확인가능하다. 이 분도 글을 보니 김영한님의 인프런 강좌를 듣는 분이신듯하다.

gmlwjd9405.github.io/2019/08/12/primary-key-mapping.html

 

[JPA] 기본키(PK) 매핑 방법 및 생성 전략 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

@Column

사실 아직은 컬럼명을 데이터베이스 관례에 맞게 Column name을 따로 지정해주는 정도밖에 모르겠다.

솔직히 그냥 필드명 그대로 쓰면 되지, 왜 Column name을 따로 쓰는진 잘 모르겠다.

대신, Column length=10과 같이 길이를 제한할 수도 있고, nullable=false와 같이 null 허용 여부 또한 정해줄 수 있다.

즉, DDL 생성 기능을 조정할 수 있다.

 


Orders

package jpabook.jpashop.domain;

import javax.persistence.*;
import java.time.LocalDate;
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 LocalDateTime orderDate;

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

 

역시 PK는 @Id, @GeneratedValue (strategy=AUTO 가 없으면 기본값인 AUTO로 적용)로 지정해주었고

FK (Foreign Key)는 memberId이고, 컬럼 매핑을 통해 인식할 수 있게 해주었다. 

외래 키는 엔티티에서 인식해주기 위해 컬럼 매핑을 해주어야 하는 듯 하다.

 

status 필드의 자료형이 OrderStatus인데, 여기서 OrderStatus는 Enum 클래스로 아래와 같다.

package jpabook.jpashop.domain;

public enum OrderStatus {
    ORDER, CANCEL;
}

앞에서 언급했듯이, enum 클래스에는 Entity를 붙일 필요가 없다.

주문 진행 / 주문 취소 여부를 나타내준다.

 

다시 Order 클래스로 돌아와서, @Enumerated가 뭔지 알아보자


매핑 어노테이션2

@Enumerated(EnumType.STRING)

모두 알다시피 db엔 enum타입이 없다. 따라서 우린 JPA에게 enum 타입을 인지시켜주기 위해 이 어노테이션을 쓰게 되는데, EnumType에는 두 가지가 있다. ORDINAL, STRING.

먼저 ORDINAL은 0번째, 1번째...와 같이 index로 지정해주는 것이고, STRING은 enum 클래스에 지정돼있는 ORDER, CANCEL과 같이 String형으로 지정해주는 것이다.

강좌에선 ORDINAL 형은 아예 쓰지 않는것을 권장하고 있다.

ORDINAL 형을 쓰면 enum 타입이 무엇인지 한눈에 파악하기 힘들기 때문이며, 데이터베이스 관리에도 STRING이 더 큰 이점이 있다고 한다. 아래 글을 참고하자.

stackoverflow.com/questions/6789342/jpa-enum-ordinal-vs-string

 

JPA Enum ORDINAL vs STRING

It's possible to define enumerations in JPA using either @Enumerated(EnumType.ORDINAL) or @Enumerated(EnumType.STRING) I wonder what are advantages and disadvantages of those two definitions? I

stackoverflow.com


OrderItem

package jpabook.jpashop.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class OrderItem {
    @Id @GeneratedValue
    @Column(name="ORDER_ITEM_ID")
    private Long id;

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

    private int orderPrice;
    private int count;

    public Long getId() {
        return id;
    }

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

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getItemId() {
        return itemId;
    }

    public void setItemId(Long itemId) {
        this.itemId = itemId;
    }

    public int getOrderPrice() {
        return orderPrice;
    }

    public void setOrderPrice(int orderPrice) {
        this.orderPrice = orderPrice;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

 

다대다 관계를 막기 위해 중간에 삽입한 테이블이므로 외래 키가 2개이다.

외래 키가 2개이므로 @Column 매핑을 통해 FK를 지정해주는 부분 또한 2개임을 알 수 있다.


Item

package jpabook.jpashop.domain;

import javax.persistence.*;

@Entity
public class Item {
    @Id @GeneratedValue
    @Column(name="ITEM_ID")
    private Long id;

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

    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 int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public int getStockQuantity() {
        return stockQuantity;
    }

    public void setStockQuantity(int stockQuantity) {
        this.stockQuantity = stockQuantity;
    }
}

JpaMain 

package jpabook.jpashop;

import javassist.compiler.ast.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 emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em=emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try{
            Order order=em.find(Order.class, 1L);
            Long memberId=order.getMemberId();
            
            Member member=em.find(Member.class, memberId);
            
            tx.commit();
        } catch(Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        emf.close();
    }
}

위와 같이 메인 메소드를 작성하고 실행하면 도메인 테이블들이 쫙 생성이 될 것이다.

테이블이 잘 생성이 되었다. test 데이터베이스이므로 연습용 Member1 table이 있는데 무시해도 좋다.


문제점

혹시 try문에서 무언가 귀찮은 점을 눈치챘는가?

try문을 다시 한 번 살펴보자.

	try{
            Order order=em.find(Order.class, 1L);
            Long memberId=order.getMemberId();

            Member member=em.find(Member.class, memberId);

            tx.commit();
        }

Order 도메인에서 주문서를 find로 확인하고 있고,

이 주문서에 대응하는 외래키를 찾아낸 후,

다시 이 외래키를 이용해 em.find로 Member를 확인하는 

귀찮은 과정을 거치고 있다.

 

즉, 데이터 중심 설계를 진행하다보니, 객체지향적인 이점을 놓쳐버린 셈이다. (사실 만들 땐 몰랐는데 강의에서 언급해줘서 알았다 ㅎㅎ...)

 

이렇게 테이블의 외래키를 객체에 그대로 가져오는 코드는 좋지 않은 코드라 하며,

객체 그래프 탐색이 불가능한 코드이며,

참조가 없으므로 UML도 잘못되었다고 한다.

 

이 문제점을 개선하기 위해 우린 실전예제2 에선 테이블끼리 연관관계 매핑을 할 예정이라고 한다.


일단 JPA에 대해 대략적인 기능들, PK, FK 매핑해주는 기능들은 배웠지만

아직 나는 배가 고프다.

 

그래서 더 강의를 들어봐야겠다.

 

나중에 스프링으로 현재 진행하고 있는 프로젝트 완성되고, 규모가 커지면 redis, kafka같은 대용량 트래픽 처리 경험도 쌓아야겠다.

열심히 해서 좋은 개발자 돼야지~ 헿ㅎㅎㅎ

갈길이 멀다.

반응형