엔티티 개발

guswls·2023년 2월 3일

JPA 활용 1편

목록 보기
3/9
post-thumbnail

이번 포스팅은 김영한 이사님의 스프링 부트 JPA 활용 1편을 듣고 제작되었습니다.

저번 강의에서 설계한 것을 바탕으로 이번엔 엔티티 클래스를 개발할 것이다.

참고로 이번 실습은 모든 클래스의 GetterSetter를 열고 실습을 진행할 예정인데 실무에선 가급적 Getter는 모두 열어두고 Setter는 필요한 경우에만 여는 것이 좋다고 한다.

1. Member 구현(1)

@Entity
@Getter @Setter
public class Member {

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

    private String name;

	@Embedded
    private Address address;

    private List<Order> orders = new ArrayList<>();
}
  • id는 컬럼명을 별도로 지정을 해서 테이블 내에 member_id로 생성되도록 세팅했다.
  • @Embedded 어노테이션을 통해 Address를 내장타입으로 사용하도록 세팅한다.

2. Address 구현

@Embeddable
@Getter
public class Address {
    private String city;
    private String street;
    private String zipcode;
}
  • Address의 경우 값타입으로 사용되기 때문에 테이블에 내장될 수 있도록
    @Embeddable어노테이션을 사용하였다.

참고

내장 타입을 사용할 때는 @Embaddable 이나 @Embedded 둘 중 하나만 있어도 사용가능하지만 보통은 양쪽에 해준다.


3. Order 구현 (1)

@Entity
@Table(name="orders")
@Getter @Setter
public class Order {

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

    @ManyToOne
    @JoinColumn(name="member_id")
    private Member member;
}
  • 테이블 명을 Order로 그대로 사용하기 보단 관례상 Orders를 사용한다. 이유는 order_by와 겹칠 수 있기 때문이다.
  • OrderMember는 다대일 관계이므로 @ManyToOne으로 연관관계를 지정해주어야 한다.
  • @joinColumn을 사용해서 Member와의 Foreign Keymember_id로 생성해준다.

4. Member 구현 (2)

@Entity
@Getter @Setter
public class Member {
			.
            .
            .
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();
}
  • MemberOrder는 일대다 관계이므로 @OneToMany로 연관관계를 지정해준다.
  • 테이블 상 Foreign KeyOrder에 존재하므로 MemberOrder의 연관관계 주인은 Order가 된다.
    따라서 Member에는 연관관계의 거울이라는 의미로 mappedBy를 추가해야한다.

5. Order 구현 (2)

@Entity
@Table(name="orders")
@Getter @Setter
public class Order {

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

    @ManyToOne
    @JoinColumn(name="member_id")
    private Member member;

    private List<OrderItem> orderItems = new ArrayList<>();

    private Delivery delivery;

    private LocalDateTime orderDate; //주문 시간

	@Enumerated(EnumType.STRING)
    private OrderStatus status; //주문 상태[ORDER, CANCEL]
}
  • OrderItemDelivery의 연관관계 세팅은 추후에 한다.
  • orderDate는 자바8에서 지원하는 LocalDateTime을 사용한다. 이 타입을 사용하면 Hibernate에서 자동으로 시간을 지정해준다.
  • OrderStatus는 주문 상태로 ORDERCANCEL두 가지의 상태를 가지는 열거형으로 구현한다.
  • 열거형을 컬럼값으로 사용할 때는 @Enumerated 에서 타입을 지정해주어야 한다. 디폴트는 ORDINAL인데 무조건 STRING으로 사용해주어야 한다.
public enum OrderStatus {
    ORDER, CANCEL
}

6. OrderItem 구현 (1)

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

    private Item item;

    @ManyToOne
    @JoinColumn(name="order_id")
    private Order order;

    private int orderPrice; // 주문 가격
    private int count; // 주문 수량
}
  • OrderItemOrderItem의 다대다 매핑을 위한 중간 테이블이다.
  • OrderItemOrder의 경우 다대일관계이므로 @ManyToMany어노테이션으로 연관관계를 지정해준다.
  • Order에 대해서 @JoinColumn을 통해서 Foreign Key를 가져온다. 연관관계의 주인은 OrderItem이 된다.

7. Order 구현 (3)

@Entity
@Table(name="orders")
@Getter @Setter
public class Order {
				.
                .
                
    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();
				
                .
                .
}
  • OrderOrderItem은 일대다 관계이며 연관관계의 주인은 OrderItem이므로 mappedBy를 세팅해준다.

8. Item 구현 (1)

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {

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

    private String name;
    private int price;
    private int stockQuantity;
}
  • Item을 부모 클래스로 사용하기 위해서 추상클래스로 만들어준다.
  • 상속관계를 표현하기 위해서 @Inheritance를 사용하고 그 전략으로 싱글테이블 전략을 사용한다. 싱글 테이블 전략은 앞서 설명했듯 하나의 테이블에 모든 정보가 들어가있게 된다.
  • @DiscriminatorColumn어노테이션을 통해 구분자를 설정한다. 여기선 dtype이라는 이름을 사용한다.
@Entity
@DiscriminatorValue("B")
@Getter
@Setter
public class Book extends Item{
    private String author;
    private String isbn;
}
@Entity
@DiscriminatorValue("M")
@Getter
@Setter
public class Movie extends Item{
    private String director;
    private String actor;
}
@Entity
@DiscriminatorValue("A")
@Getter
@Setter
public class Album extends Item{
    private String artist;
    private String etc;
}
  • Book, Movie, Album을 클래스로 따로 만들어준다.
  • @DiscriminatorValue로 구분할 때 사용할 값을 선택한다.

9. OrderItem 구현 (2)

@Entity
@Getter @Setter
public class OrderItem {
		.
        .
    @ManyToOne
    @JoinColumn(name="item_id")
    private Item item;
    	.
        .
}
  • OrderItemItem은 다대일 관계이므로 @ManyToOne으로 매핑한다.
  • 단방향 연관관계이므로 OrderItem에서만 Foreign Key를 가지고 있으면 된다.

10. Delivery 구현 (1)

@Entity
@Getter @Setter
public class Delivery {
    @Id @GeneratedValue
    @Column(name="delivery_id")
    private Long id;

    @OneToOne(mappedBy = "delivery")
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status; //READY, COMP
}
  • DeliveryOrder는 1대1 연관관계이므로 @OneToOne을 사용해야 한다.
  • 1대1연관관계의 경우 Foreign Key를 어디에 놔도 상관은 없지만 구조상 액세스가 상대적으로 더 많이 일어나는 쪽에 두는 것이 좋다. 따라서 연관관계의 주인은 Order이고 여기엔 mappedBy로 세팅해주어야 한다.
  • 마찬가지로 배송상태인 DeliveryStatus를 열거형으로 사용하였다.
public enum DeliveryStatus {
    READY, COMP
}

11. Order 구현 (4)

@Entity
@Table(name="orders")
@Getter @Setter
public class Order {
		.
        .
    @OneToOne
    @JoinColumn(name="delivery_id")
    private Delivery delivery;
    	.
        .

}
  • OrderDelivery의 일대일 연관관계에서 주인은 Order이므로 여기서 Foreign Key를 가진다.

12. Category 구현 (1)

@Entity
@Getter @Setter
public class Category {
    @Id
    @GeneratedValue
    @Column(name="category_id")
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(name="category_item",
        joinColumns = @JoinColumn(name = "category_id"),
        inverseJoinColumns = @JoinColumn(name="item_id")
    )
    private List<Item> items = new ArrayList<>();
}
  • 설계 단계에서 CategoryItem의 관계를 다대다 관계로 풀어냈지만 따로 중간테이블을 생성하진 않았다.
  • 예제 느낌에서 @ManyToMany를 사용하자.

13. Item 구현 (2)

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="dtype")
@Getter @Setter
public abstract class Item {
			.
            .
            .
    @ManyToMany(mappedBy = "items")
    private List<Category> categories = new ArrayList<>();
}
  • ItemCategory의 연관관계 주인을 Category로 하자.

14. Category 구현 (2)

@Entity
@Getter @Setter
public class Category {
    @Id
    @GeneratedValue
    @Column(name="category_id")
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(name="category_item",
        joinColumns = @JoinColumn(name = "category_id"),
        inverseJoinColumns = @JoinColumn(name="item_id")
    )
    private List<Item> items = new ArrayList<>();

    @ManyToOne
    @JoinColumn(name="parent_id")
    private Category parent;

    @OneToMany(mappedBy = "parent")
    private List<Category> child = new ArrayList<>();
}
  • 설계에서 Category는 부모와 자식이 있는 계층구조로 설계하였다.
  • Categoryparent의 관계는 ManyToOne이다.
  • Categorychild의 관계는 OneToMany이다.
  • 쉽게 생각해서 같은 엔티티에 대해서 양방향 맵핑을 맺어준 것과 같은 원리이다.

총정리

이렇게 해서 엔티티 구현을 모두 완료하였다. 개념을 안듣고 바로 실습을 진행하였지만 설명을 너무 잘해주시고 특히 설계단계를 확실히 하고 넘어가서 이해가 더 잘될 수 있었다.

특히 새롭게 알게 된 점도 있었다.

전에 장고로 프로젝트를 할때 포스팅과 멤버의 다대다기능을 활용하여 중복 추천 방지 및 추천 게시글 추적같은 기능을 구현한 적이 있었는데 굉장히 애를 먹었던 적이 생각났다.

장고에서도 JPA에서의 @ManyToMany와 같이 중간테이블을 자동으로 생성하여 다대다관계를 생성해주는 옵션이 있어 그걸 사용했던 적이 있었다. 그 때 어떤 일 때문에 중간테이블에 컬럼을 추가해서 사용해야하는 일이 있었는데 결국 추가를 하지 못해서 값을 다른 테이블에 넣고 참조의 참조를 하여 힘들게 필요한 값을 가져왔던 적이 있었다.

그때 생각했던 의문이 "다대다 관계에서 컬럼값을 두 테이블 간의 pk 이외에 다른 값을 왜 못넣지?"하고 생각했던 적이 있었던 것 같다. 사실 그때는 DB에 대한 지식도 거의 없고 말 그대로 맨땅에 헤딩을 하고 있었던 터라 그랬던 점이 더욱 크게 다가왔던 것 같다.

그러다 이번에 JPA활용1편 강의에서 바로 다대다관계는 다대일 일대다 관계로 풀어서 써야된다고 말씀하시는 걸 듣고 바로 궁금증이 해결될 수 있었던 것 같다.

이번 강의는 JPA에 국한되지 않고 설계적인 측면에서도 도움을 많이 얻을 수 있어서 매우 유익했던 것 같다.

profile
안녕하세요

0개의 댓글