1. 회원 기능 테스트

 

테스트 내용

-회원가입기능이 작동해야 한다.

-회원가입할 때 같은 이름이 있으면 예외가 발생해야 한다.

 

테스트케이스

1.MemberService 클래스에서

a.하기 MemberService에서 ctrl + shift + t < create test < ok를 한다.

 

public MemberService {

......

}

 

b. public void 회원가입( ) throws Exception{

//given(이렇게 주어졌을 때)

 

//when(이렇게 하면)

 

//then(이렇게 된다)

}

 

public void 회원_중복_예외() throws Exception{

//given

 

//when

 

//then

}

 

 

테스트 범위

-정말 순수한 단위테스트 보다 jpa가 메모리모드로 실제 디비까지 돌아가는 것을 보기 위해

 완전히 스프링과 integration해서 테스트를 할 것이다.

 

그래서 하기 두가지를 세팅할 것이다.

 

@RunWith(SpringRunner.class)

@SpringBootTest

@Transactional 데이터를 변경해야 하기 때문에 하는 설정,

public class MemberServiceTest {

 

//테스트라 참조해서 테스트할 것이 없기 때문에 하기처럼 작성

@Autowired MemberService memberService;

@Autowired MemberRepository memberRepository;

}

////alt + enter를 누르고 suppress인가를 누르니 memberRepository가 보라색으로 변경됨

 

*@Transanctional

-데이터를 변경해야 하기 때문에 하는 설정

-같은 Transaction안에서 같은 엔티티에서 id(pk)이 같으면 같은 영속성 컨텍스트에서

 똑같은 값이 관리가 된다.

-Transactional이 있어야 롤백이 가능하다.

-insert SQLDB에 날라가지 않는다. 영속성 컨텍스트가 flush가 되지 않는다.

 

참고: 테스트 케이스 작성

Given, When, Then (http://martinfowler.com/bliki/GivenWhenThen.html) >

필수는 아니지만 위에 것을 기본으로 해서 다양하게 응용하는 것을 권장한다고 한다.

 

 

*축약어를 통해 코드구문생성하기

설정경로

- File->Setting->Editer->Live Templates -> tdd 단축단어 생성 -> tdd

 

내용설정

@Test

public void () throws Exception {

//given

 

//when

 

//then

}

 

=> tdd를 사용하면 위에 구문이 자동생성된다.

 

 

@Rollback(false)

-Transacntional을 롤백이 기본적으로 되므로 직접 눈으로 확인하고 싶고

 DB에 값이 들어가는 것을 확인하고 싶을 때에는

 @Rollback(false)public void 회원가입() throws Exception 에 적용하자.

 

-데이터베이스 트랜잭션이 커밋을 하는 순간 flush가 되면서

  jpa 영속성 컨텍스트가 있는 member객체가 insert문이 만들어지면서 DB insert가 나가게 된다.

 

사진

 

em.flush();

assertEquals(member, memberRepository.findOne(savedId));

-insert문이 db로 가는 것을 실행시킬 때 눈으로 볼 수 있고,

 transactionrollback 되도록 하는 설정

 

try catch return

-중복회원 및 회원가입을 다시 테스트

*데이터베이스에 앞서 insertDB로 날려 값(PRIMARY KEY)이 생성 되었다면

 실행시 중복회원에서 에러가 발생하니 조심할 것

 

사진

회원 기능 테스트 완료

 

 

 

 

2. 회원기능테스트 기술적 정리

a.RunWith(SpringRunner.class)

-jUnit 실행할 때 스프링이랑 엮어서 실행을 하겠다.

 

@SpringBootTest

-스프링부트위에서 테스트를 하려면 꼭 필요하다.

-@Autowired 실행이 실패한다.

-컨테이너 안에서 테스트를 한다.

 

b.@Transactional

-테스트가 끝나면 Rollback을 한다.

-서비스클래스나 레포지터리에 붙여서 돌리면 Rollback하지 않는다.

 

c. tdd 단축키 만들어서 테스트구문 생성하기

 

d. 테스트를 완전 격리된 환경에서 하는 방법

-실제 외부에 있는 데이터베이스를 사용했다.

-디비를 외부에 설치해서 테스트해야 할 경우가 생긴다. 테스트가 끝나고

 데이터가 초기화 되는 것이 좋다.

-테스트를 완전 격리된 환경에서 하는 방법이 있다.

-자바안에 데이터베이스를 만들어 테스트 하는 방법이 있는데,

 메모리 데이터베이스를 사용하는 방법이다. 안에 않고 가상의 데이터베이스를 만들어서

 스프링부트를 사용하면 이러한 방법을 무료로 이용할 수 있다.

 

 

종류

1. main

- 실제 개발하는 운영소스 (java, resouces)

-기본적으로 운영로직은 java, resources가 운영권을 가진다.

 

2. test

-테스트하는 운영소스 (java, resources:directory resources생성해주어야 함)

-테스트 운영로직의 우선권은 test안에 java, resources가 우선권을 가진다.

-application.yml을 java->resource로부터 가져온다면 application.yml이 우선권을 가져

 main에 있는 application.yml은 무시되고 test안에 있는 application.yml이 실행된다.

 

(application.yml 복사 test->resources(directory)->application.yml(복사 from main:java)

 

 

 

JVM내에서 실행 및 메모리모드로 데이터베이스실행 테스트

-jvm 안에서 실행되고 memory모드로 데이터베이스를 띄워주기 위해

 application.ymlspingurl을 인메모리 url로 바꾸어 준다.

 

주소는 h2.database->cheat sheet->In-Memory->첫번째 라인

 

사진 h2.database 사이트

 

사진

 

*하지만, spring, jpa 설정 내용이 전혀 없고 logging,level 설정 내용만으로도

 스프링부트의 기능으로 인해 인메모리 모드로 실행이 된다.

 극단적으로 얘기해서 내용이 전혀 없어도 인메모리 모드로 실행이 된다.

목차

회원 도메인 개발

-구현 기능

-회원 등록

-회원 목록 조회

 

순서

-회원 엔티티 코드 다시 보기

-회원 리포지토리 개발

-회원 서비스 개발

-회원 기능 테스트

 

회원도메인개발

1. 회원엔티티 코드 다시보기

 

2. MemberRepository

사진

a.레포지터리 패키지 생성

b.MemberRepository 클래스 생성

c. @Repository 어노테이션 세팅

*스프링에서제공하는 Repository 어노테이션을 설정해주면, 

component 스캔에의해서 스프링빈으로 자동으로 관리가된다.

 

*스프링빈

-자바 객체

-스프링 컨테이너(Spring Container)에 의해서 만들어진 자바 객체를스프링 빈이라고 부른다.

-스프링 빈과 자바 일반 객체와의 차이점은 없다굳이 다른점이라고 한다면, 컨테이너에 의해 만들어졌다.

 

*스프링 컨테이너가 나오게 된 배경

A a = new A( );

A a1 = new A( );

A a2 = new A( );

각각의 객체를 생성해주도록 코드를 작성하여 메모리를 3번 낭비했었는데,

메모리를 효율적으로 관리해주고자 메모리를 1번만 낭비를 하게 하기 위해 스프링 컨테이너가 탄생했다.

스프링 컨테이너에 스프링빈을 넣어 객체를 하나만 생성하여 공통 객체역할로 이 객체를 가지고 3개의 객체를 @Autowired를 활용해 생성한다.

 

예)

@Autowired

a a;

 

@Autowired

a1 a1; 

 

@Autowired

a2 a2; 

 

 

-@PersistenceContext 설정 및 private EntityManager em;

스프링이 엔티티 매니저를 만들어서 em에 인젝션 주입을 해준다.

 

d. 조회하는로직을 작성한다.(save, find)

save

-기본 엔티티매니저 사용은 persist 영속성 컨텍스트에 멤버엔티티객체를 넣고

후에 트랜잭션이 커밋 되는 시점에 디비에 인서트쿼리가 날라가면서 반영된다.

 

find메서드 사용

-단건조회

return em.find(Member.class : 첫번째 타입, id :primary key를 넣어준다.)

 

 

e. 회원전체 로직을 작성한다.(find all)

-createQuery다음에""를타입할것. 첫번째 jpql를 사용 뒤에는 반환타입

-하기코드를합친다.

( return result;에서 단축키 ctrl + alt + n < inline all < refactor )

List<Member> result = em.createQuery("select m from Member m", Member.class) .getResultList(); return result;

 

확인

*jpqlsql 차이점

-기능적으로 동일하고 결국 sql로 번역한다.

-sqlfrom의 대상이 테이블대상으로 쿼리를 한다.

-jpqlfrom의 대상이 엔티티객체를 대상으로 쿼리를 한다. (사진에 있는 Member를 조회한다.)

 

f. parameter binding에 의해서 특정 회원만 찾는 코드

-setParameterintPosition으로설정

1. orderorderItem에서의 cascade사용

 

a.cascade = CascadeType.ALL 미사용시

public class Order {

.....

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)

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



persiste(orderItemA)

persiste(orderItemB)

persiste(orderItemC)

persiste(order) //엔티티당 각각 persist를 호출 해주어야 한다.

}

 

b.cascade = CascadeType.ALL 사용시

public class Order {

.....

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)

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



persiste(order) 
//만 사용해주면 된다. cascade는 persiste를 전파한다. 
//orderItem 안에 있는 컬렉션(orderItemA,B,C)들 모두를 호출해준다. 
//CascadeType.All이기 때문에 삭제될 때에도 모두 삭제 된다.
}

설명

-OrderorderItemscascade옵션을 사용함

-orderItems에 데이터를 넣어두고 order를 저장하면 orderorderItems가 같이 저장된다.

 orderItemsjpa에 개수만큼 저장하고, 그다음에 컬렉션에 넣은다음에 order를 또 jpa

 persist를 해서 저장을 해주어야 한다.

 

모르는 단어

*persist

*컬렉션

*persist

 

 

2. orderdelivery에서의 cascade 사용

casacade 설정법

실습

public class order{

.............

@OneToOne(fetch = Lazy, cascade = cascadeType.ALL)
@JoinColumn(name = "delivery_id")
private Delivery delivery;
}

설명

a. cascade 미사용시

-order, delivery 각각 persist를 해주어야 한다.

 

b. cascade 사용시

-order, delivery를 한번에 persist를 한다.

모든엔티티는 기본적으로 persist를 저장하고 싶으면 각자 해주어야 하는데,

delivery 값만 세팅후 orderpersist를 하면 cascade를 활용해서

위에 작성된 코드들을 한번에 persist호출이 된다.

 

 

3. 양방향 연관관계 편의 메서드

 

비즈니스 두 개의 로직 원자적으로 묶는 메서드를 구현한다.

public static void main(String[] args) {

Member member = new Member();

Order order = new Order();

 

member.getOrders().add(order);

order.setMember(member);

 

->양방향 연관관계세팅하려면 ordermember가 있으면

   member가 주문을 하면 member class안에 list order에 넣어주어야 한다.

   양방향에서 값을 넣어주어야

   order.getMember, member.getOrder를 통해

   로직을 통해 왔다갔다하려면 값을 세팅해주어야 한다.

 

실습1

실습2

*핵심적으로 컨트롤 하는 쪽에 메서드를 작성하는 것이 좋다.

 

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

엔티티분석

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의 값이 바뀌지하고 헷갈리게 된다.

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에 소문자 + _(언더스코어) 스타일을 사용하겠다.

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

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

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

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

 사용하겠다.

 

1-2. 회원 엔티티 분석

1.회원(Member)엔티티

 

회원(Member)

id: Long

-회원엔티티에 공통속성은 id가 다 있다.

-id=pk generating 해주는 id값 데이터베이스

 pk(id)long 값으로 해놓았다.

 

name: String

-이름(string)

 

address: Address

-임베디드 타입(내장값타입)인 주소(Address : 설계도 하기 그림참고-value type address)

 

orders : List

-그리고 주문(orders) 리스트를 가진다.

 

2.주문 엔티티

 

주문(Order)

id

member : Member(회원정보)

orderItems : list (주문목록)

delivery : Delivery(배송정보)

orderDate : Date(주문한날짜)

status : OrderStatus(주문상태-주문,취소 등등)

 

-한 번 주문시 여러 상품을 주문할 수 있으므로 주문과 주문상품(OrderItem)은 일대다 관계 다.

-주문은 상품을 주문한 회원과 배송 정보, 주문 날짜, 주문 상태(status)를 가지고 있다.

-주문 상태는 열 거형을 사용했는데 주문(ORDER), 취소(CANCEL)을 표현할 수 있다.

 

3.주문상품 엔티티

 

주문상품(OrderItem)

-주문한 상품 정보와 주문 금액(orderPrice), 주문 수량(count) 정보를 가지고 있다.

 (보통 OrderLine, LineItem 으로 많이 표현한다.)

-주문상품을 추가한 다른 이유는 한번 주문시 여러 개의 상품을 담을 수 있는데

 주문시점시 주문한 상품의 개수 및 주문한 상품 가격정보가 필요하기 때문이다.

 

4.배송 엔티티

 

배송(Delivery)

id

order : order(주문정보, 배송지주소 아님)

address : Address(배송지주소-value type 값타입 주소를 재활용)

status : DeliveryStatus(배송현황)

 

-배송(Delivery): 주문시 하나의 배송 정보를 생성한다. 주문과 배송은 일대일 관계다.

-주문시 하나의 배송 정보를 생성한다. 주문과 배송은 일대일 관계다.

 

5.상품엔티티

 

상품(Item)

id

name (상품이름)

price: int (상품가격)

stockQuantity (상품재고)

categories: List(어느 카테고리에 매핑 되어 있는지에 대한 정보)

 

상품엔티티와의 상속관계

-상속관계Album, Book, Movie들이 있고,

 개별속성들이 상속관계로 다음과 같이 표현되어 있다.

Album - artist, etc

Book - author, isbn

Movie - director, actor

 

-이름, 가격, 재고수량(stockQuantity)을 가지고 있다.

 상품을 주문하면 재고수량이 줄어든다.

 상품의 종류로는 도서, 음반, 화가 있는데 각각은 사용하는 속성이 조금씩 다르다.

 

6. 카테고리엔티티

 

카테고리(Category)

id

name

items: List(카테고리가 가지고 있는 리스트)

parent: Category

(계층구조로 되어 있다. 내 부모가 누구인데, 자식(속성)은 여러개의 구조로 되어 있다라는 형태, 부모는 한개)

child: List

 

-카테고리(Category): 상품과 다대다 관계를 맺는다. parent, child로 부모, 자식 카테고리를

 연결한다.

 

7. 주소엔티티

 

주소(Address)

-값 타입(임베디드 타입)이다. 회원과 배송(Delivery)에서 사용한다.

 

 

설계구성도는 JPA에서 다룰 수 있는 모든관계개념들을 모두 넣어두었다.

1. MemberOrder - 1대 다 관계

2. Orderdelivery - 11 관계

3. OrderOrderItem - 1대 다 관계

4. OrderItemItem - 다대 1 관계

5. ItemAlbum,Book,Movie - 상속관계

6. CategoryItem - 다대 다 관계

 

실무에서 사용하기 애매한 것들

-Categoryitem의 다대 다 관계(JPA@다대다관계)는 실제운영에서 사용하면 안된다.

 1대 다 또는 다대 1 관계로 풀어내야 한다.

-MemberOrder 1대 다 관계가 아닌, 시스템적으로는 동등한 관계로 보고 고민해야 한다.

-회원을 통해서 주문이 일어난다가 아니라, 주문을 생성할 때 Member가 필요하다라고 생각을 해야 한다.

-쿼리가 일어날 때도 주문 내역이 필요할 때 멤버를 찾아서 거기에 있는 주문 리스트를 가져 오는 것이 아니라,

 order에서도 필터링 조건에 멤버가 들어간다.

-그래서 사실상 1대 다의 컬렉션은 필요없다..

 자세한 것은 기본편강의에서 다루었다.

 

참고

회원이 주문을 하기 때문에, 회원이 주문리스트를 가지는 것은 얼핏 보면 잘 설계한 것 같지만,

객체 세상은 실제 세계와는 다르다. 실무에서는 회원이 주문을 참조하지 않고, 주문이 회원을 참조하는 것으로 충분하다. 여기서는 일대다, 다대일의 양방향 연관관계를 설명하기 위해서 추가했다.

2-1. 도메인모델과 테이블 설계

a.회원, 주문, 상품의 관계

-회원은 여러 상품을 주문할 수 있다. 그리고 한 번 주문할 때 여러 상품을 선택할 수 있기에 주문과 상품은 다대다 관

-하지만 이런 다대다 관계는 관계형 데이터베이스는 물론이고 엔티 티에서도 거의 사용하지 않는다.

-따라서 그림처럼 주문상품이라는 엔티티를 추가해서 다대다 관계를 일대 다, 다대 일 관계로 풀어냈다.

 

상품 분류

-상품은 도서, 음반, 화로 구분되는데 상품이라는 공통 속성을 사용하므로 상속 구조로 표현했 다.

 

b.회원과 주문 관계

-회원은 여러 주문을 할 수 있어서 회원과 주문의 관계는 1대 다의 관계이다.

 

c.회원과 주문, 주문과 상품 관계

-회원이 한번 주문할 때 여러개의 상품을 주문할 수 있다.

-상품도 여러 주문에 담길 수 있으면 다대 다 관계가 된다.

-하지만 이런 다대다 관계는 관계형 데이터베이스는 물론이고 엔티티에서도 거의 사용하지 않는다.

-그래서 1대 다 다 대 1의 관계 관계로 만들기 위해

 그림처럼 주문상품(테이블+주문수량)이라는 엔티티를 추가해서 다대다 관계를 일대 다,

 다대일 관계로 풀어냈다.

 

d.주문과 배송정보

-11 관계

-주문할 때 배송정보를 입력할 수 있도록 해놓음

 

e.상품

-도서, 음반, 영화 로 타입이 나누어짐

 

f.상품과 카테고리

-상품은 카테고리에 매핑이 된다.

-하나의 카테고리에 여러 가지 상품이 들어갈 수도 있고,

 어떤 상품이 카테고리에 복수로 들어갈 수 있기 때문에

 다대 다 관계로 세팅

 

g.데이터베이스의 목적

(참고 블로그 http://kdskor.blogspot.com/2010/10/pk-fk.html)

-효율적이고, 성능면에서 매우 편리하며 신속하게 수많은 데이터들을 보관 및 관리하는 것

-그 중에서도 제일 우선시 되어야 하는 것이 데이터의 무결성이다.

 신속하게 처리한다 하더라도 데이터에 결점이 있다면 데이터는 쓸모가 없을 뿐 아니라,

 데이터의 결점으로 인하여 잘못된 결과를 초래

-데이터 무결성을 보장해주기 위해서 가장 기본적으로 필요한 것이 PKFK

 

h.PK(Primary Key)FK(Foreign Key)

PK

-데이터베이스 생성에서 가장 기본적으로 고려되는 것이 PK

-PK만큼 중요한 것이 FK

-PK는 테이블에서 오직한개만 존재

-PK가 데이터의 유일성을 보장

-테이블에서 PK를 조건으로 조회하여 한 개의 값만 나오거나 값이 나오지 않게 됨

 

FK

-외래 키는 참조하는 테이블에서 1개의 키(속성 또는 속성의 집합)에 해당하고,

 참조하는 측의 관계 변수는 참조되는 측의 테이블의 키를 가리킨다.

-관계형 데이터베이스에서 외래 키(외부 키, Foreign Key)는 한 테이블의 필드(attribute) 중 다른 테이블의 행(row)

 식별할 수 있는 키

 

)아파트테이블 세대테이블의 아파트코드값에

   아파트테이블의 아파트코드에 없는 값이 들어간다면?

   이 때 데이터를 저장 또는 수정시 아파트코드 데이터는 아파트 테이블에 있는 아파트코드

   가 맞는지 확인해야 하는데, 이것이 자동적으로 확인되도록 도와주는 것이 FK이다.

+ Recent posts