3.컬렉션은 필드에서 초기화 하자.

a.컬렉션은 필드에서 바로 초기화 하는 것이 안전하다.

 

예제사진

Best Practice

=>현재 만들어져 있는 것이 bestpractice이다.

 

이유

-우선은 초기화에 대해 고민할 필요가 없다.

-null문제에서 안전하다.(*null 개념 공부할 것)

 

설명

nulllpointerexeception이 생길일이 없다. 잘못되어서 체크할 일이 없다.

 

b.필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.

-하이버네이트는 엔티티를 속화 할 때, 컬랙션을 감싸서 하이버네이트가 제공하는 내장 컬렉션으로 변경한다.

 getOrders()처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부 메커니 즘에 문제가 발생할 수 있다.

 

설명예제1)

java

Member member = new Member(); 

System.out.println(member.getOrders().getClass());

em.persist(team);

System.out.println(member.getOrders().getClass());

 

출력결과

class java,util.ArrayList //ArrayList가 나온다.

class org.hibernate.collection.internal.PersistentBag

 

설명

java

Member member = new Member(); //멤버객체 생성

System.out.println(member.getOrders().getClass());

 

출력결과 -> class java,util.ArrayList //ArrayList가 나온다.

 

java

Member member = new Member();

System.out.println(member.getOrders().getClass());

위의 구문 출력후 다시 밑에서부터 이어서 작성

em.persist(team);

출력결과->class org.hibernate.collection.internal.PersistentBag

 

em.persist(team); 설명

-persist로 영속을 한다라는 의미

-위의 코드 작성후, jpa입장에서는 db에 저장하겠다라고 선언 하고,

-영속성컨텍스트(컬렉션 : Orders)관리를 해주어야 한다.

-그다음에 다시 하기와 같이 작성해준다.

 

설명(출력결과->class org.hibernate.collection.internal.PersistentBag)

-기존 것을 가지고 감싸버린다. 하이버네이트가 컬렉션이 바뀌어버리면 추적을 해야 하므로,

 본인이 추적할 수 있는 형태/내장컬렉션(타입에 따라 다름, PersistentBag)로 바꾸어 버린다.

 문제는, 하이버네이트가 바꾸어 놓은 형태인상태에서 다시 누군가 컬렉션(Orders)부분을

 set으로 바꾸어 버린다면 하이버네이트가 원하는 메카니즘으로 돌아오지 않는다.

 

그래서 어떻게 해야 하냐면, 현재 위에 사진에서 작성한 대로 필드레벨에서 생성을 한다.

 

-컬렉션을 변경하지 말아야 한다. 객체생성할때의 그대로 사용하길 권유한다.

-하이버네이트가 관리하는 컬렉션으로 바뀌었고, 변경할 경우 하이버네이트가 원하는 메카니즘대로 동작을 안한다.

*하이버네이트가 엔티티를 펄시스트 하는 순간 컬렉터를 감싸거나 안에서 무슨일이 일어나면,

 하이버네이트가 제공하는 내장 컬렉션으로 변경이 된다.

-따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.

=>컬렉션을 필드에서 바로 초기화 해라!!!

 

 

4. 테이블 컬럼명 생성 전략

-스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다름

 

설명

-하기와 같이 @Table(name = “user“)로 설정을 하면 테이블명이 user로 바뀐다.

 

예제)

@Entity

@Table(name = "user")

@Getter @Setter

public class Member {

.....

}

 

만약 위의 @Table을 사용하지 않으면 테이블명이 어떻게 되나요?

 

1.하이버네이트 기존 기본매핑전략 구현

-엔티티의 필드명을 그대로 테이블 명으로 사용

(SpringPhysicalNamingStrategy)

)private LocalDateTime orderDate;

테이블명 -> orderdate 그대로 사용

 

2.스프링 부트 신규 기본매핑전략 구현 설정 (엔티티(필드) 테이블(컬럼))

a. 카멜 케이스 언더스코어(memberPoint -> member_point)

   예)private LocalDateTime orderDate;

   테이블명 -> order_date 로 변경

 

b. .() _(언더스코어)

)

 

c. 대문자 소문자

)

 

*테이블명 설정되는 로직 내용 확인

메뉴상단바 -> Navigate -> SearchEverywhere -> Class->

SpringPhysicalNamingStrategy 입력

 

적용 2단계 (공부필요)

1. 논리명 생성

-테이블명을 직접 적지 않으면 ImplicitNamingStrategy 사용하게 된다.

*spring.jpa.hibernate.naming.implicit-strategy

-테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용

 

스프링부트 기본설정

spring.jpa.hibernate.naming.implicit-strategy:

org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy

 

2. 물리명 적용

-spring.jpa.hibernate.naming.physical-strategy

-테이블이나 컬럼명의 채워져 있든 아니든 논리명 모두에게 적용이 된다. 즉, 실제 테이블에 적용하게 된다.

(username usernm 등으로 회사 룰로 바꿀 수 있음)

 

스프링부트 기본설정

spring.jpa.hibernate.naming.physical-strategy:

org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy

1.엔티티에는 가급적 Setter를 사용하지 말자(강조)

-Setter가 모두 열려있으면 변경 포인트가 너무 많아서,

 특정 엔티티하나가 어디서 수정된 것인지 알기가어렵다. 그래서 유지보수가 어렵다.

-Setter를 안열면 어떻게 알 수 있는지는에 대해서는 추후 웹어플리케이션 개발강의에서 비즈니스 메서드를 등록하면서

 코드를 가지고 보여줄 예정!

-예제에서는 Setter를 열어둔 이유는 그때 마다 보여줄 수 있는 실습내용이 있기에 열어둠!

 나중에 리펙토링으로 Setter 제거할 예정!

 실무에서는 가급적이면 setter를 열지말자!

 

 

2.모든 연관관계는 지연로딩으로 설정!

(엄청중요하다!, 수많은 장애를 극복할 수 있다!, 외워야 한다.)

a.즉시로딩을 절대로 사용하지 말자

-즉시로딩(EAGER)은 어떤 종류의 SQL이 실행될지 추측하기 어렵다.

-JPQL 실행시에는 N+1 이슈가 자주 발생할 수 있다.

-최악의 경우엔 하나 연관된 데이터(디비)를 전부 다 데리고 오기 때문에

개발시에 어려워진다.

*즉시로딩설정이 되있을시에 네이처로 전부 변경하고 튜닝을 할 수 있다.

 

그래서

-연관관계는 꼭 지연로딩(LAZY)으로 설정해야 한다.

-즉시로딩이 아니어도 원하는 데이터를 실시간으로 선택하여 가져올 수 있는 방법이 있다.

 연관된 엔티티를 함께 DB에서 조회해야 할 경우는 fetch join/엔티티 그래프 기능 사용권장

 

*즉시로딩이 있고 지연로딩이 있다.

즉시로딩

-멤버를 조회할 때 연관된 필요한 주문을 다른 조회한다.

로딩하는 시점(멤버를 조회)에 다른 연관된 주문들도 로딩하겠다는 의미

 

*지연로딩의 장점

order를 조회할 때 member를 이렇게 설정해놓을 시에

@ManyToOne(fetch = FetchType.EAGER)

@JoinColumn(name = "member_id")

private Member member;

-order를 조회할 때 member 조인을 해서 쿼리 한번에 같이 조회가능하도록 한다.

한건 조회할 때는 em.find()인 경우에만 가능하다.

 

 

3. 즉시로딩일 때에는 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다.

즉시로딩의 문제 예시

예제1

@ManyToOne(fetch = FetchType.EAGER)

@JoinColumn(name = "member_id")

private Member member;

 

설명

JPQL이 제공하는 쿼리를 가지고 주문조회를 할 경우

JPQL select o From order o;

-> SQL select * from order n + 1 ( SQL문 그대로 번역을 하게 된다.)

-그러면 sql이 갈 때에는 주문 100개 조회하여 데이터 100개를 가져오지만,

-이때 member(fetch = FetchType.EAGER)를 본다.

-그래서 데이터를 100100번을 가져오기 위해 단방 쿼리가 100개를 보내게 된다.

 이것을 n+1 문제라고 한다.

 

n+1이냐

-첫 번째 주문조회를 위해 쿼리를 날려서 가져온 쿼리결과가 100개이면

 SQL select * from order n + 1에서 n100개로 치환화시키기 때문이고,

 그만큼 멤버(member)를 가져오기 위한 쿼리를 100개 보내게 된다.

 

4.@XToOne(OneToOne, ManyToOne)..x시리즈 관계는 기본이 즉시로딩이므로

직접 지연로딩으로 설정해야 한다.

 

)

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

@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();

설명

@ManyToOne의 기본패치는 FetchType fetch() default EAGER;

- 얼핏 볼때에는 이것이 맞는 것 같다.

 

@OneToMany의 기본패치는 FetchType fetch() default LAZY;

 

-@OneToManyLAZY이기 때문에 따로 설정을 해줄 필요가 없지만

 @ManyToOne 디폴트가 fetch = FetchType.EAGER이므로

 직접 따로 (fetch = FetchType.LAZY)로 설정을 해주어야 한다.

*@OneToOne도 기본패치 디폴트가 fetch = FetchType.EAGER이다.

 

-설정을 해주지 않으면 연관된 하나 데이터를 가져오기 위해 쿼리 1개를 보내는 것 같지만

 연관된 데이터 조회를 위해 쿼리를 엄청나게 많이 보내게 되므로 일일이 찾아서 LAZY로 설정을 해주어야 한다.

*쿼리 종류 및 의미를 이해해야 한다.(내가부족한 것)

 

5. 실습-@ManyToOne을 찾아서 지연로딩(LAZY)로 설정을 해주자

 

a.fetch 설정하는 사진

b.그리고 나머지 @ManyToOne을 찾아서 바꿔준다.

-jpabook.jpashop에서 ctrl + shift + f를 하면 다음과 같은 화면이 나온다.

 

c.@OneToOneLAZY 지연로딩으로 변경해준다.

 

d.static import(좀더 깔끔하게 외관상 LAZY로 보여지게 하는 방법)

-FetchType위에서 alt + enter를 타입하면 위와 같이 변경이 된다.

 

*엔티티개발을 하면서 중간중간에 내가 작성한대로 생성이 되었는지 확인을 해라

*실행툴 안에 나오는 메시지에서 생성된 테이블 생성코드 그대로 작성해도 되는가 - 안된다.

 

)

create table orders (

order_id bigint not null,

order_date timestamp,

status varchar(255),

delivery_id bigint,

member_id bigint,

primary key (order_id)

);

 

-디테일하게 수정해야 할 것들이 있다. 수정하고 정리해서 사용한다.

 

1.Category 엔티티분석 및 카테고리 테이블분석

 

Category 엔티티

-id

-name

-items: List (다대 다 관계)

-parent: Category

-child: List

-상품과 다대다 관계를 맺는다. parent, child로 부모, 자식 카테고리를 연결한 다.

*계층 구조를 이렇게 매핑할 수 있다를 보여주기 위해 이렇게 설정 했다.

*자기자신을 셀프로 계층구조형태로 매핑을 하진 않았을 것 같아 이렇게 설정했다.

 

카테고리 테이블 분석

CATEGORY_ID

-PARENT_ID (FK)

-NAME

 

설명

카테고리와 상품 테이블 분석

-@ManyToMany를 사용해서 매핑한다.

(실무에서 @ManyToMany는 사용하지 말자. 여기서는 다대다 관계를 예제로 보여주기 위해 추가했을 뿐이다)

 

 

2.(Category_Item)중간테이블 및 카테고리와 상품 연관관계 맵핑

 

CATEGORY_ITEM

-CATEGORY_ID (FK)

-ITEM_ID (FK)

 

카테고리 엔티티

-id

-name

-items: List

-parent: Category

-child: List

 

아이템 엔티티

-id

-name

-price : int

-stockQuantity

-categories: List

 

a.카테고리클래스에서 상품과 맵핑

설명

@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id),
inverseJoinColumns = @JoinColumn(name = "item_id"))

 

@JoinTable

-객체는 컬렉션이 있어 다대 다관계가 가능하지만

 관계형 DB는 컬렉션관계를 양측에 가질 수 있는 것이 아니기 때문에 1대 다, 다대 1 관계의

 중간테이블을 이용한다.

 

@JoinTable어노테이션

-중간테이블에 있는 외래 키를 매핑 할 때 먼저 사용

 

joinColumns = @joinColumn(name="category_id)

-중간테이블에 있는 category_id 외래키 매핑

 

inverseJoinColumns = @JoinColumn(name = "item_id")

-중간테이블에 있는 item_id 외래키 매핑(item과 연관관계 매핑하기 위함)

 

b.Item클래스에서 카테고리와 맵핑

실습

설명

@ManyToMany(mappedBy ="items")

private List(Category> categories = new ArrayList<>();

 

*foreign key가 있는 items 클래스에 있는 item이 연관관계의 주인이다.

*mapped by - order_item 테이블에 있는 item필드에 의해 맵핑된 거울일 뿐이야라는 뜻

 

주의

실무에서 @ManyToMany는 사용하지 말자.

-실무에서는 다대 다관계로 연관관계를 풀어내지 말 것

-이러한 상태의 테이블만 구현이 가능하고, 다른 필드를 추가하는 것이 불가하다.

 

 

3. 카테고리 구조

 

카테고리가 계층구조로 내려간다.

위의 부모구조 아래에는 자식구조

 

실습

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

@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();

설명

-자식 카테고리는 여러개 가질 수 있다.

-셀프로 category안에서 이름만 다를 뿐이지 양방향 연관관계 매핑하듯이 해주면 된다.

 

자 이제 작성한대로 데이터베이스 테이블이 제대로 구성이 되었는지 확인해 보자.

그리고 실제 데이터베이스 테이블구성 및 데이터베이스 명령어 작성/실행

 

실습

create table orders (

order_id bigint not null,

order_date timestamp,

status varchar(255),

delivery_id bigint,

member_id bigint,

primary key (order_id)

);

-> 이것으로 실행시키면 원래 작성했던 코드대로 테이블 구성이 된다.

*JPAalter할 때 작성한 foreign키 컬럼을 전부 잡아준다.

 

참고

foreign key를 꼭 걸어야 되요 안 걸어야 되요?

-시스템마다 다르다.

-실시간 트래픽이 중요하고, 정확성보다는 잘 서비스가 되는 유연한 것이 중요하면

 foreign key를 빼고, 인덱스만 잘 잡아주면 된다.

-하지만, 돈과 관련된 중요한 것이라던가, 데이터가 항상 맞아야 하는 시스템이라면

 foreign key를 넣어주면 된다.

Delivery엔티티클래스 세팅

실습

설명

a. @Entity @Getter @Setter 세팅

 

b. 엔티티속성/필드 + 어노테이션 (실습사진참고)설정

-private Long id;

-private Order order;

-private Address address

-private DeliveryStatus status;

 

c.Orderdelivery 연관관계매핑

Order클래스 맵핑실습

설명

@OneToOne

-1:1 매핑이다.

*여기에서 고민해주어야 할 것이 있다.

 하나의 주문은 하나의 배송정보만, 하나의 배송은 하나의 주문정보만 가져야 한다

 이렇게 되어야 1:1 관계가 형성된다.

 

*JPA 에서는 1:1관계에서는 어디든 FOREIGN KEY를 주어도 된다 어디에 두느냐에 따라 장단점이 있다.

-선호하는 스타일은 access를 많이 하는 쪽에 foreign key를 두려고 한다.

-여기 시스템상 배송을 조회하는 것보다 주문을 보면서 배송을 확인한다.

 그래서 주문,Orderforeign key(delivery_id)를 두었다.

 

연관관계주인 정하기

order에도 delivery가 있고, delivery에도 order가 있다.

-Orderforeign key가 있으므로, delivery를 연관관계의 주인으로 정한다.

*1:1관계매핑은 기본편에서 확인할 것

 

그러므로 Order엔티티클래스에

@OneToOne

@JoinColumn(name = "delivery_id")

private Delivery delivery;

 

Delivery엔티티 클래스측에

@OnetoOne(mapped By = "delivery")로 설정

private Order order;

 

d. Address필드 재활용 및 @Embedded(내장값)어노테이션 설정

 

e. DeliveryStatus status 설정 및 DeliveryStatus Enum생성

@Enumerated(EnumType.STRING)어노테이션 설정

-@EnumeratedEnumType에 두가지 종류: ORDINAL, STRING

ORDINAL : 1, 2, 3, 4 숫자형태로 컬럼에 들어간다.

예를들어 1,2 사이에 다른 상태의 값이 생기면 3,4는 뒤로 밀리면서 4,5가 된다.

STRING : 숫자형태순서가 아닌, 중간에 값이 생겨도 밀려나거나 하는 법이 없다.

그래서 꼭 STRING을 써야 한다.

 

DeliveryStatus Enum생성

 

1.Album, Book, Movie 클래스 세팅

 

실습

 

설명

-Item Package로 옮긴 Album,Book,MovieItem클래스로부터 extends를 사용해

 상속받게 한다.

-@Entiyty @Getter @Setter 세팅

 

 

2.ItemAlbum, Book, Movie클래스 상속관계매핑 세팅

실습

a. 싱글테이블전략사용

-상속관계전략을 지정해주어야 한다.(중요!!)

-전략을 부분 클래스에 잡아주어야 하고, 싱글테이블전략을 사용한다.

 

*여기에서는 싱글테이블 전략을 사용

@Inheritance(strategy = InheritanceType.SINGLE_TABLE) 설정을 한다.

 

*@inheritance 타입에 전략 종류 3가지

a. (strategy = inheritanceType.Joined) - 가장 정교화된 스타일

b. (strategy = InheritanceType.TABLE_PER_CLASS)

- 상속받는 클래스를 각각 테이블화 하는 전략

c. (strategy = InheritanceType.SINGLE_TABLE) - 한테이블에 전부 넣는전략

 

#InheritanceType strategy 자세히 보는 방법

(strategy = InheritanceType)에서 Inheritance 우클릭해서

Find Usage->EnumInheritance Type 클릭

 

 

b.@DiscriminatorColumn(name= "dtype")

실습

설명

-ITEM: 앨범, 도서, 화 타입을 통합해서 하나의 테이블로 만들었다.

 dtype 컬럼으로 타입을 구분한다. 컬럼사용을 통해 저장 되어 있는 자식테이블을 구분할 수 있다.

-기본값은 dtype이다.

 

c.@DiscriminatorValue("B") -> Album, Music에도 동일하게 "A", "M"로 세팅

실습

설명

-싱글테이블일 때 book컬럼을 저장할 때 구분할 수 있는 무언가가 DB입장에서 필요하다.

 이 때 넣는 값이다.

-이때 book클래스에서 클래스에 @DiscriminatorValue("B") 로 세팅해준다.

 B값으로 안하고 기본값으로 두면 Book으로 값이 들어간다.

 

 

2. OrderItemItem 연관관계 설정

실습

 

설명

OrderItem ->private Item item 어노테이션 세팅

-@ManyToOne : 주문상품과 상품의 관계, 다대 1관계를 설정해주는 어노테이션

-@JoinColumn(name = "item_id")

*@JoinColumn 어노테이션은 외래 키를 매핑 할 때 사용

*name 속성에는 매핑 할 외래키(연관관계주인) 이름지정

-item_id = foregien key(외래키)

-상품테이블에서 상품에 대한 정보를 매핑

 

-다대일 단방향 관계다

엔티티분석

ORDER

-id

-member : Member

-orderitems : List

-delivery : Delivery

-orderDate : Date

-status : OrderStatus

 

Orderitem

-id

-item : Item

-order : Order

-orderPrice

-count

 

Delivery

-id

-order : Order

-address : Address

-status : DeliveryStatus

 

 

테이블 분석

ORDERS

-MEMBER_ID (FK)

-DELIVERY_ID (FK)

-ORDERDATE

-STATUS

 

ORDER_ITEM

-ORDER_ID(FK)

-ITEM_ID(FK)

-ORDERPRICE

-COUNT

 

DELIVERY

-DELIVERY_ID

-STATUS

-CITY

-STREET

-ZIPCODE

 

 

1. 주문엔티티 추가 개발

 

실습

a. Order클래스에서 

-private List<OrderItem> orderItems = new ArrayList<>();  (OrderItem클래스 생성)

-prviate Delivery delivery; (Delivery 클래스 생성)

-private LocalDateTime orderDate; (주문시간)

*자바8이전엔은 private Date date을 만들어주고, Dateimport 해야했으며,

 날짜관련 어노테이션 매핑을 해주었어야 했다.

 자바8에서는 LocalDateTime를 사용하면 hibernate가 자동으로 위의 행위들을 대신해준다.

-Private OrderStatus status; (@Enumerated(EnumType.STRING)어노테이션 설정해줄 것!)

*(OrderStatus 클래스생성 -> 이것으로 주문상태 Order, Cancel를 관리한다.)

 

OrderStatus클래스 생성

실습

 

b. orderItem 클래스 개발

실습

@ManyToOne@JoinColumn 어노테이션 사용에 대한 설명

orderorderItem 엔티티분석 (양방향연관관계)

-하나의 order과 여러개의 orderItem을 가질 수 있고

 orderItem은 하나의 order만 가질 수 있다.

 매핑에도 order클래스에도 orderitems클래스에도 order가 있다.

 

order과 orderItem 테이블분석

-orderItem( )foreign key가 들어간다.

 order_id가 들어간다. 하나의 주문에 여러개의 orderItem이 들어가기 때문에

-orderItem에서 order가 연관관계의 주인이 된다.

 orderItem에 이어 order측 세팅

 

@OneToMany(mappedBy = "order") -> Order클래스에서 확인

Private List<OrderItem> orderItems = new Arraylist<>();

-order에 의해서 맵핑이 된다는 의미

-@OneToMany : 회원과 주문의 관계, 1대 다 관계를 설정해주는 어노테이션

-@OneToMany (mapped by = "order")

*foreign key가 있는 OrderItem클래스에 있는 order가 연관관계의 주인이다.

*mapped by - orderItem 테이블에 있는 order필드에 의해 맵핑된 거울일 뿐이야라는 뜻

 

 

d.OrderItemprivate Item item과 연동되는 Item클래스 세팅

설명

public abstract class (추상클래스로 만든다. 구현체를 가지고 만들 것이기 때문에)

 

e.Item엔티티분석, 실습 및 Item package 생성 (위의 실습사진 참고)

ITEM 엔티티분석

-id

-name

-price: int

-stockQuantity

-categories : List

 

ITEM 테이블분석

ITEM_ID

-NAME

-PRICE

-STOCKQUANTITY

-DTYPE

-ARTIST

-ETC

-AUTHOR

-ISBN

-DIRECTOR

-ACTOR

 

-stockQuantity 매핑을 해주어야 하고, 밑에 Album, Book, Movie와 상속관계이다.

* 상속관계공부 필요

* private - 속성

* name, price, stockQuantity - 공통속성

 -상속할 Album, Book, Movie은 일일이 만들어주어야 하는 것들이다.

 

Item package 생성

설명

-Item package를 만들어 Item,Album, Book, Movie클래스에 옮긴다.

주문(order)엔티티클래스파일에서

실습

 

order_id

-DB컬럼의 id이름을 테이블명으로 가져와 테이블명_id형태로 설정해준다.

DBA포지션 엔지니어가 이러한 형태로 설정해주는 것을 원한다.

 

참고

엔티티의 식별자는 id를 사용하고 PK 컬럼명은 member_id를 사용했다. 엔티티는 타입(여기서는 Member)이 있으므로 id 필드만으로 쉽게 구분할 수 있다. 테이블은 타입이 없으므로 구분이 어렵다. 그리고 테이블은 관례상 테이블명 + id를 많이 사용한다. 참고로 객체에서 id 대신에 memberId를 사용해도 된다. 중요한 것은 일관성이다.

 

-@ManyToOne : 주문과 회원의 관계, 다대 1관계를 설정해주는 어노테이션

-@JoinColumn(name = "member_id")

*@JoinColumn 어노테이션은 외래 키를 매핑 할 때 사용

*name 속성에는 매핑 할 외래키(연관관계주인) 이름지정

-member_id = foregien key(외래키)

-주문회원테이블에서 주문회원에 대한 정보를 매핑

 

 

 

회원엔티티클래스(RealMember)에서

어노테이션 설정

-@OneToMany : 회원과 주문의 관계, 1대 다 관계를 설정해주는 어노테이션

-@OneToMany (mapped by = "member")

*foreign key가 있는 order클래스에 있는 member가 연관관계의 주인이다.

*mapped by - order 테이블에 있는 member필드에 의해 맵핑된 거울일 뿐이야라는 뜻

*여기에 무엇을 넣는다고 해서 order에 있는 member필드의 foreign key 값이 변경되지

 않는다.

*order엔티티클래스에 있는 order테이블에 있는 member필드 값을 변경하면

 member_idforeign key 값이 변경된다.

 

 

연관관계의 주인설정이 필요한 이유

연관관계 맵핑 분석을 다시 확인해보자

회원과 주문

-회원도 오더로 갈 수 있고, 오더도 회원으로 갈 수 있도록 설계 했다.(양방향 연관관계)

-일대다, 다대일의 양방향 관계다. 따라서 연관관계의 주인을 정해야 한다.

 관계형데이터베이스에서는 1대 다의 관계에서는 다에 무조건 외래키가 있게 된다.

 외래 키가 있는 주문을 연관관계의 주인으로 정하는 것이 좋다.

 

이유

-주문클래스에서 멤버값을 바꾸거나, 멤버클래스에서 오더값을 바꿀 수도 있기 때문에

 JPA는 혼란스러울 수 있다.

-회원엔티티로 본다면 Order도 멤버정보를 가지고 있고, Member도 주문정보를 가지고 있

 다. 데이터베이스에서 MemberOrder테이블에서 볼 때에 foreign keyOrder테이블에

 한 개 밖에 없는 상태이다.

-추후 일어날 수 있는 문제라고 한다면, 회원과 주문테이블 관계를 바꾸고 싶을 때 foreign

 key를 변경해야 하는 상황에 처하게 된다.

-Member테이블에도 orders라는 컬렉션 필드가 있는거고, Order테이블에도 member라는

 필드가 있어 JPA에서는 둘다 정보를 확인해서 값을 바꾸어야 하나라는 혼란에 빠진다.

 즉, 어디에 값이 바뀌었을 때 foreign key를 바꾸어야 하는지에 대한 혼란을 겪게 된다.

-극단적으로 Order엔티티에서는 member 값을 세팅했는데, Member엔티티에서 order 값을

 세팅 안한다던가, Member엔티티에서는 order값을 넣었는데, Order엔티티에서는 member

 값을 안넣었다던가 하는 경우가 생기면, JPA는 둘중에 무엇을 믿고 order테이블의 foreign

 key 값을 업데이트를 해야 하지?라고 혼란을 겪는다.

 

여기서 foreign keyupdate하게 하기 위해서 JPA에서 둘중에 하나를 선택하라고 약속을 했다. 엔티티에서의 객체는 변경 포인트가 두군데이지만, 테이블은 foreign key하나만 변경하면 되기 때문에, 쉽게 맞추기 위해 규칙을 정했다.

둘중에 하나를 주인이라는 개념으로 잡으면 되는데 이것이 연관관계의 주인이다.

Member엔티티에서 ordersOrder엔티티에서나 member중에 값이 바뀌었을 때에 foreign key를 바꿀 거야 라는 것을 결정을 하는 것이 연관관계의 주인이다.

 

그렇다면 연관관계의 주인은 누구로 하는가?

-foreign key가 가까운 테이블(Order)을 확인하고 Member_id를 연관관계주인이 있는 곳으

 로 맵핑을 하면 된다.

-Order엔티티의 member가 가까우니 여기를 연관관계주인으로 정한다.

-이렇게 하지 않으면 member의 무언가를 바꾸었는데, Order테이블에 무언가/foregin키가

 업데이트 되어버려서 있는 의도치 않은 일이 일어난다. 성능이슈가 생길 수도 있다.

 Member정보를 변경했는데, orders의 값이 바뀌지하고 헷갈리게 되어버린다.

-foreign key가 있는 Order테이블에 있는 member를 바꾸니까 order테이블에 있는 컬럼이

 바뀌는구나 하고 이해하기도 관리하기도 쉬워진다. 이렇게 규칙을 정하지 않으면 Member

 보를 변경했는데, orders의 값이 바뀌지하고 헷갈리게 된다.

들어가기에 앞서

-예제에서 쉽게 설명하기 위해서 엔티티클래스에 setter getter를 모두 열어두어

 단순하게 설계를 할 것이다.

-실무에서는 getter는 열고, setter는 꼭 필요할 때에만 사용하는 것을 추천한다.

 

1. 회원엔티티개발

 

실습

회원엔티티클래스 생성 및 작성

설명

회원엔티티개발

a. jpabook.jpashopPackagedomain이라고 만든다.

-여기에 핵심 엔티티들을 설치 할 것이다.

 

b. 회원엔티티 작성

-List, ArrayList import class를 해줌

 

c. Address, Order 클래스 파일 생성

 

c. 회원테이블 이름 매핑

-@Id, @generatedValue, @Columns 어노테이션 세팅

 

2.주소값 타입 개발

실습

주소값타입개발

설명

a. address주소 클래스에서 코드작성

@Embeddable 어노테이션 사용

-address 주소가 어딘가에 내장될 수 있게 하기 위해 사용

@Getter 어노테이션을 열어두기

 

b.회원엔티티(RealMember)클래스파일에서 내장타입관련 어노테이션 세팅

-Address변수에 내장타입을 포함했다라는 @Embedded 어노테이션으로 매핑을 해주었다.

-Address변수에 @Embedded 하나만 있어도 되지만,  대개 두가지 어노테이션을 많이 사용한다.

 

3. 주문(order)엔티티클래스파일에서

실습

주문엔티티클래스 생성 및 작성(주문과 회원관계 연관관계매핑)

설명

a.주문엔티티클래스파일에서

-@Entity, @Table 이름 설정을 했다.

-@Table(name = "orders")라고 해주지 않으면

  public class Order에서 Order의 관례로 이름이 Order가 되어 버리기 때문에 이름 설정을 해주어야 한다.

 

b.order_id

-DB컬럼의 id이름을 테이블명으로 가져와 테이블명_id형태로 설정해준다.

 DBA포지션 엔지니어가 이러한 형태로 설정해주는 것을 원한다.

 

참고

엔티티의 식별자는 id를 사용하고 PK 컬럼명은 member_id를 사용했다. 엔티티는 타입(여기서는 Member)이 있으므로 id 필드만으로 쉽게 구분할 수 있다. 테이블은 타입이 없으므로 구분이 어렵다.

그리고 테이블은 관례상 테이블명 + id를 많이 사용한다.

참고로 객체에서 id 대신에 memberId를 사용해도 된다. 중요한 것은 일관성이다.

 

c.@ManyToOne

-주문과 회원의 관계, 다대 1관계를 설정해주는 어노테이션

 

d.@JoinColumn(name = "member_id")

-@JoinColumn 어노테이션은 외래 키를 매핑 할 때 사용

 

e.name 속성에는 매핑 할 외래키(연관관계주인) 이름지정

-member_id = foregien key(외래키)

-주문회원테이블에서 주문회원에 대한 정보를 매핑

 

 

4. 회원엔티티클래스(RealMember)에서

실습

회원엔티티 연관관계 매핑

 

a.어노테이션 설정

@OneToMany

-회원과 주문의 관계, 1대 다 관계를 설정해주는 어노테이션

 

@OneToMany (mapped by = "member")

-foreign key가 있는 order클래스에 있는 member가 연관관계의 주인이다.

-mapped by - order 테이블에 있는 member필드에 의해 맵핑된 거울일 뿐이야라는 뜻

-여기에 무엇을 넣는다고 해서 order에 있는 member필드의 foreign key 값이 변경되지

 않는다.

-order엔티티클래스에 있는 order테이블에 있는 member필드 값을 변경하면

 member_idforeign key 값이 변경된다.

 

어노테이션 설명 참고

https://docs.oracle.com/javaee/6/api/index.html

어노테이션이 궁금하다면 java.persistence -> annotation type을 클릭하면 어노테이션 설명을 볼 수 있다.

 

1.4 연관관계 맵핑 분석

회원과 주문

-회원도 오더로 갈 수 있고, 오더도 회원으로 갈 수 있도록 설계 했다.(양방향 연관관계)

-일대다 , 다대일의 양방향 관계다. 따라서 연관관계의 주인을 정해야 한다.

 관계형데이터베이스에서는 1대 다의 관계에서는 다에 무조건 외래키가 있게 된다.

 외래 키가 있는 주문을 연관관계의 주인으로 정하는 것이 좋다.

 그러므로 ORDERS 엔티티에 있는 MEMBER가 연관관계의 주인이 되고,

 MEMBER엔티티의 ordersmapped by로 단순 읽기만 가능한 연관관계의 거울 즉 단순

 조회용으로만 이용한다.

-연관관계의 주인쪽에 값을 세팅해야 값이 변경된다.

 거울쪽값은 단순조회용이다.

 

-일대다, 다대일의 양방향 관계다. 따라서 연관관계의 주인을 정해야 하는데,

 외래 키가 있는 주문을 연관관계의 주인으로 정하는 것이 좋다.

 그러므로 Order.memberORDERS.MEMBER_ID 외래 키와 매핑한다.

 

*쉽게 얘기하면 주문이 한 개만 있는 것이 아니라 많아질 수 있다고 할 때에

주문쪽에서 PK생성으로 멤버를 참조하여야 하므로 1(MEMBER) (ORDERS)의 관계라고

할 수 있다. 주문측이 갑, 주인이 된다.

 

주문상품과 주문

-한번 주문할 때 주문수량과 주문수량에 따라 가격이 달라질 수 있기 때문에

 주문상품을 두게 된다.

-다른 사람이 주문한 정보가 주문상품에 있을 수 없다.

 주문한상품들이(OrderItem)에는 주문할 때 하나의 Order에만 연관관계가 걸린다.

 주문상품들의 정보들이 OrderItem : List에 오게 된다.

 그래서 다대(ORDERS_ITEM)1(ORDERS)관계가 된다.

 

->다대일 양방향 관계다. 외래 키가 주문상품에 있으므로 주문상품이 연관관계의 주인이다.

   그러므로 OrderItem.orderORDER_ITEM.ORDER_ID 외래 키와 매핑한다.

 

*연관관계 매핑 분석할 때에

-서비스의 특성과 일반적인 관계를 알고, 일반적으로 접근해볼 것

-외래키가 있는 곳이 연관관계의 주인

 

주문상품과 상품

-itemorderitem에서 의 정보를 맵핑 할 것이 없다.

-나를 주문한 orderitem을 찾아 할필요가 없고,

 orderitem을 찾고 싶을 때에 루트를 orderitem으로 쿼리를 찍으면 된다.

 즉, orderitem에서 item정보만 잘 맵핑해주면 된다.

->다대일 단방향 관계다. OrderItem.itemORDER_ITEM.ITEM_ID 외래 키와 매핑한다.

   주문과 배송

-일대일 양방향 관계다. Order.deliveryORDERS.DELIVERY_ID 외래 키와 매핑한다.

-주문정보에 배송정보가 들어가고 OrdersDelivery_ID(PK)가 넣어 두었다.

 

-DELIVERY_ID가 있는 외래키와 매핑하면 Orders가 연관관계의 주인이 된다.

 

카테고리와 상품

-@ManyToMany를 사용해서 매핑한다.

 (실무에서 @ManyToMany는 사용하지 말자. 여기서는 다대다 관계를 예제로 보여주기 위

 해 추가했을 뿐이다)

-편법이 아닌 정상적인 방법으로는 테이블관계는 ManyToMany로 표현할 수 있는 방법이

 없기 때문에, CATEGORY_ITEM 중간테이블로 풀어낸다..

 

카테고리와 상품관련하여 앞에서의 내용과 같은 내용

-객체에서는 카테고리가 아이템리스트를 가져도 되고

 아이템이 카테고리리스트를 가져도 된다.

 양쪽 컬렉션을 만들면 객체는 다대 다로 만들 수 있지만

 관계형데이터베이스는 일반적 설계로는 그렇게 할 수 없다.

 중간에 매핑 테이블(CATEGORY_ITEM)을 두어,

1(CATEGORY) (CATEGORY_ITEM) 관계 및 다대(CATEGORY_ITEM) 1(ITEM)로 풀어

낸다.

 

참고

-외래 키가 있는 곳을 연관관계의 주인으로 정해라.

 연관관계의 주인은 단순히 외래 키를 누가 관리하냐의 문제이지 비즈니스상 우위에 있다고

 주인으로 정하면 안된다..

-예를 들어서 자동차와 바퀴가 있으면, 일대다 관계에서 항상 다쪽에 외래 키가 있으므로

 외래 키가 있는 바퀴를 연관관계의 주인으로 정하면 된다.

 

주의 (연관관계를 잘못 설정하게 되면 생기는 현상)

-물론 자동차를 연관관계의 주인으로 정하는 것이 불가능 한 것은 아니지만,

 자동차를 연관관계의 주인으로 정하면 자동차가 관리하지 않는 바퀴 테이블의 외래 키 값이

 업데이트 되므로 관리와 유지보수가 어렵고, 추가적으로 별도의 업데이트 쿼리가 발생하는

 성능 문제도 있다. 자세한 내용은 JPA 기본편을 참고하자.

 

1.3 회원테이블분석

Member 테이블

Member_ID

NAME

|CITY : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

|STREET : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

|ZIPCODE : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

 

설명

-1:1 맵핑이다.

-이름과 임베디드 타입인 주소(Address), 그리고 주문(orders) 리스트를 가진다.

 

Delivery 테이블

Delivery_ID

STATUS

CITY : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

STREET : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

ZIPCODE : VALUE TYPE/임베디드 타입 그대로 필드로 내려온 것이다.

 

설명

-회원 엔티티의 Address 임베디드 타입 정보가 회원 테이블에 그대로 들어갔다.

이것은 DELIVERY 테이블도 마찬가지다.

 

ITEM 테이블

ITEM_ID(상품 ID)

-상속관계 맵핑 기본편에서 설명한 3가지 전략중 가장 단순한 싱글테이블 전략을 사용했다.

 싱글테이블전략은 한테이블에 DTYPE으로 구분해서 다 넣는다.

 일반적으로 성능이 잘 나온다.

-단점이라하면 필드에 ARTIST,ETC,AUTHOR,ISBN,DIRECTOR,ACTOR 전부 섞여서 싱글테

 이블에 들어간다.

-앨범, 도서, 화 타입을 통합해서 하나의 테이블로 만들었다. DTYPE 컬럼으로 타입을 구분한

 다.

 

Orders 테이블

ORDER_ID (주문 ID)

MEMBER_ID (Foreign Key, 회원 ID)

DELIVERY_ID (Foreign Key, 배송 ID)

 

참고

-테이블명이 ORDER가 아니라 ORDERS인 것은 데이터베이스가 order by 때문에 예약어로 잡고 있는 경우가 많다.

 그래서 관례상 ORDERS를 많이 사용한다.

 

주문상품테이블

주문상품 ORDER_ITEM

ORDER_ITEM_ID

ORDER_ID (FK)

ITEM_ID (FK)

ORDERPRICE (주문한 상품가격)

COUNT (주문한 상품 개수)

 

카테고리(CATEGORY)

CATEGORY_ID

PARENT_ID (FK)

NAME

CATEGORY_ITEM

CATEGORY_ID (FK)

ITEM_ID (FK)

 

설명

-객체에서는 카테고리가 아이템리스트를 가져도 되고

아이템이 카테고리리스트를 가져도 된다.

양쪽 컬렉션을 만들면 객체는 다대 다로 만들 수 있지만

관계형데이터베이스는 일반적 설계로는 그렇게 할 수 없다.

중간에 매핑 테이블(CATEGORY_ITEM)을 두어,

1(CATEGORY) (CATEGORY_ITEM) 관계 및 다대(CATEGORY_ITEM) 1(ITEM)로 풀어

낸다.

 

참고

*강의에서는 객체를 구분하기 위해 DB에 대문자를 사용했다.

-실제 코드에서는 DB에 소문자 + _(언더스코어) 스타일을 사용하겠다.

-데이터베이스 테이블명, 컬럼명에 대한 관례는 회사마다 다르다.

 보통은 대문자 + _(언더스코어)나 소문자 + _(언더스코어) 방식 중에 하나를 지정해서

 일관성 있게 사용한다. 강의에서 설명할 때는 객체와 차이를 나 타내기 위해 데이터베이스

 테이블, 컬럼명은 대문자를 사용했지만, 실제 코드에서는 소문자 + _(언더스코 어) 스타일을

 사용하겠다.

 

+ Recent posts