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를 넣어주면 된다.

+ Recent posts