다대다
1. ERD 에서 다대다 관계
- 주문 테이블(S_ORD), 상품테이블(S_PRODUCT)
- 두 테이블을 다대다 관계로 맺어주기 위해 관계설정용 table이 필요함
- S_ITEM table
- 관계 설정용 table은 정규화를 통해서 중복되는 데이터를 없애기 위해서 만들어진 것
- 관계를 맺어주기 위해, S_ITEM 칼럼은 S_ORD, S_PRODUCT 의 PK를 FK로 사용하고 이 두개의 FK가 복합키로 사용됨
2. 다대다 table 관계를 Entity 기준 설계
관계 설정 방법
- Entity 두개로 관계를 설정
- S_ORD 에 대한 Entity 와 S_PRODUCT 에 대한 Entity 가 필요하고, 두 테이블의 관계 설정 용 Entity는 사용하지 않음
- 식별관계
- S_ORD 에 대한 Entity 와 S_PRODUCT 에 대한 Entity 의 각각의 식별자 변수를 S_ITEM의 관계 설정 용 FK 로 사용해서 복합 키로 사용
- 비식별관계(식별관계 반대 의미 X)
- S_ORD 에 대한 Entity 와 S_PRODUCT 에 대한 Entity 의 각각의 식별자 변수를 S_ITEM의 관계 설정 용 FK 로만 사용하고 S_ITEM 의 PK는 별도의 식별자 변수로 사용
2.1 연결 클래스를 사용하지 않는 다대다 단방향(식별 관계, @JoinTable 사용)
연결 클래스를 사용하지 않지만 연결 테이블을 생성하도록 조건을 설정하여서 두개의 외래키를 복합키로서 사용해 연결 테이블을 관리하는 방법
⇒ 사용자가 클래스로 조건을 설정하지 않고 JoinTable로 조건만 주어서 자동으로 생성
@JoinTable을 사용해서 연결 클래스를 선언하지 않고 자동으로 JPA 안에서 생성되어 연결 테이블은 생성이 된다
표 참고(324p)
클래스 생성하지 않고 연결 테이블 생성
@ManyToMany(fetch = FetchType.EAGER) // 기본은 lazy
@JoinTable(name = "S_ITEM",
joinColumns = @JoinColumn(name = "ORD_ID"),
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"),
uniqueConstraints = @UniqueConstraint(columnNames = {"ORD_ID", "PRODUCT_ID"})
)
private List<Product> productList = new ArrayList<Product>();
테이블 생성 결과
Hibernate:
create table S_ITEM (
ORD_ID bigint not null,
PRODUCT_ID bigint not null
)
Hibernate:
create table S_ORD (
id bigint generated by default as identity,
CUSTOMER_ID bigint,
ORDER_DATE timestamp,
total double,
primary key (id)
)
Hibernate:
create table S_PRODUCT (
id bigint generated by default as identity,
category varchar(255),
name varchar(255),
SHORT_DESC varchar(255),
primary key (id)
)
- Order 클래스에서 설정한 조건으로 자동으로 연결 테이블 생성
외래키 설정
Hibernate:
alter table S_ITEM
add constraint UK1min9dm3xj2einmn4tq8gscwk unique (ORD_ID, PRODUCT_ID)
Hibernate:
alter table S_ITEM
add constraint FKi0210b7rxwtlk1tquh6eqj8sy
foreign key (PRODUCT_ID)
references S_PRODUCT
Hibernate:
alter table S_ITEM
add constraint FKhn9efciwou1aoordkugwpqtye
foreign key (ORD_ID)
references S_ORD
- Order 에서 joinColumns, inverseJoinColumns로 지정한 외래키 설정을 각 테이블에 설정을 한다
- S_ITEM 에 uniqueConstraints으로 유니크키를 설정하여 pk 대신 사용할 수 있도록 한다
- S_ITEM 에서 PRODUCT_ID, ORD_ID 는 외래키이자 동시에 복합키
- 결과
Hibernate:
select
order0_.id as id1_3_0_,
order0_.CUSTOMER_ID as customer2_3_0_,
order0_.ORDER_DATE as order_da3_3_0_,
order0_.total as total4_3_0_
from
S_ORD order0_
where
order0_.id=?
1번 주문에 대한 상품 목록
Hibernate:
select
productlis0_.ORD_ID as ord_id1_2_0_,
productlis0_.PRODUCT_ID as product_2_2_0_,
product1_.id as id1_4_1_,
product1_.category as category2_4_1_,
product1_.name as name3_4_1_,
product1_.SHORT_DESC as short_de4_4_1_
from
S_ITEM productlis0_
inner join
S_PRODUCT product1_
on productlis0_.PRODUCT_ID=product1_.id
where
productlis0_.ORD_ID=?
---> LG 통돌이 세탁기
---> 다이슨 청소기
@ManyToMany 로 Order에 설정을 해 놓았다
이때 기본 fetch = FetchType.LAZY 로 되어 있기 때문에 S_ORD 테이블만 조회해서 사용한다
그다음에 product의 값을 사용해야 되어서 S_ITEM과 S_PRODUCT를 join 한다음 order id 값이 존재하는 행을 찾아 반환해 준다
- FetchType.EAGER 인 경우
Hibernate:
select
order0_.id as id1_3_0_,
order0_.CUSTOMER_ID as customer2_3_0_,
order0_.ORDER_DATE as order_da3_3_0_,
order0_.total as total4_3_0_,
productlis1_.ORD_ID as ord_id1_2_1_,
product2_.id as product_2_2_1_,
product2_.id as id1_4_2_,
product2_.category as category2_4_2_,
product2_.name as name3_4_2_,
product2_.SHORT_DESC as short_de4_4_2_
from
S_ORD order0_
left outer join
S_ITEM productlis1_
on order0_.id=productlis1_.ORD_ID
left outer join
S_PRODUCT product2_
on productlis1_.PRODUCT_ID=product2_.id
where
order0_.id=?
1번 주문에 대한 상품 목록
---> LG 통돌이 세탁기
---> 다이슨 청소기
테이블 S_ITEM , S_PRODUCT , S_ORD 를 join 한다음 사용하게 된다
이때 order를 기준으로 단방향 다대다이므로 left otuer join 이다
2.2 연결 클래스를 사용하지 않는 다대다 양방향(식별관계, @JoinTable 사용)
양방향 관계를 맺기 위해 설정
//Product 클래스
@ManyToMany(mappedBy = "productList")
private List<Order> orderList = new ArrayList<Order>();
// Order 클래스
@ManyToMany(fetch = FetchType.EAGER) // 기본은 lazy
@JoinTable(name = "S_ITEM",
joinColumns = @JoinColumn(name = "ORD_ID"),
inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"),
uniqueConstraints = @UniqueConstraint(columnNames = {"ORD_ID", "PRODUCT_ID"})
)
private List<Product_BothWay> productList = new ArrayList<Product_BothWay>();
// 상품을 등록할 때 따로 add() 하는 것이 아닌 메서드로 자동으로 하게 한다
public void addProduct(Product_BothWay product) {
productList.add(product);
// 반대쪽(Product) 에도 주문에 대한 참조 정보 설정
product.getOrderList().add(this);
}
- 결과 (fetch = fetchType.EAGER)
코드
private static void selectData(EntityManagerFactory emf) {
EntityManager em = emf.createEntityManager();
//1번 부분
// order를 검색하여 order와 연관된 product 목록 출력
Order_BothWay order_BothWay = em.find(Order_BothWay.class, 1L);
System.out.println(order_BothWay.getId() + "번 주문에 대한 상품 목록");
List<Product_BothWay> productList = order_BothWay.getProductList();
for(Product_BothWay prBWay : productList) {
System.out.println("---> " + prBWay.getName());
}
// 2번 부분
// 검색한 Product_BothWay를 통해 Order 목록을 출력
Product_BothWay product_BothWay = em.find(Product_BothWay.class, 1L);
System.out.println(product_BothWay.getName() + "상품에 대한 주문 정보");
List<Order_BothWay> orderList = product_BothWay.getOrderList();
// 3번 부분
for(Order_BothWay ordBWay: orderList) {
System.out.println("-->" + ordBWay.toString());
}
em.close();
}
콘솔 결과
// 1번 부분 실행 결과
Hibernate:
select
order_both0_.id as id1_3_0_,
order_both0_.CUSTOMER_ID as customer2_3_0_,
order_both0_.ORDER_DATE as order_da3_3_0_,
order_both0_.total as total4_3_0_,
productlis1_.ORD_ID as ord_id1_2_1_,
product_bo2_.id as product_2_2_1_,
product_bo2_.id as id1_4_2_,
product_bo2_.category as category2_4_2_,
product_bo2_.name as name3_4_2_,
product_bo2_.SHORT_DESC as short_de4_4_2_
from
S_ORD order_both0_
left outer join
S_ITEM productlis1_
on order_both0_.id=productlis1_.ORD_ID
left outer join
S_PRODUCT product_bo2_
on productlis1_.PRODUCT_ID=product_bo2_.id
where
order_both0_.id=?
1번 주문에 대한 상품 목록
---> LG 통돌이 세탁기
// 2번 부분에 대한 실행 결과
LG 통돌이 세탁기상품에 대한 주문 정보
// 3번 부분에 대한 실행 결과
Hibernate:
select
orderlist0_.PRODUCT_ID as product_2_2_0_,
orderlist0_.ORD_ID as ord_id1_2_0_,
order_both1_.id as id1_3_1_,
order_both1_.CUSTOMER_ID as customer2_3_1_,
order_both1_.ORDER_DATE as order_da3_3_1_,
order_both1_.total as total4_3_1_
from
S_ITEM orderlist0_
inner join
S_ORD order_both1_
on orderlist0_.ORD_ID=order_both1_.id
where
orderlist0_.PRODUCT_ID=?
Hibernate:
select
productlis0_.ORD_ID as ord_id1_2_0_,
productlis0_.PRODUCT_ID as product_2_2_0_,
product_bo1_.id as id1_4_1_,
product_bo1_.category as category2_4_1_,
product_bo1_.name as name3_4_1_,
product_bo1_.SHORT_DESC as short_de4_4_1_
from
S_ITEM productlis0_
inner join
S_PRODUCT product_bo1_
on productlis0_.PRODUCT_ID=product_bo1_.id
where
productlis0_.ORD_ID=?
-->Order_BothWay(id=1, customerId=null, orderDate=2024-07-24 10:45:46.03, total=null)
-->Order_BothWay(id=2, customerId=null, orderDate=2024-07-24 10:45:46.032, total=null)
- 1번 부분 실행 결과
⇒ 실행을 할때 order, product를 모두 사용하므로 item, order, product를 모두 조인한 테이블을 생성을 해서 사용한다
- 2번 부분
⇒ 1번 부분에서 order와 연관된 product 값을 모두 들고 와서 저장했으므로 select를 실행하지 않는다
- 3번 부분
⇒ 1번 부분에서 들고온 것은 order를 기준으로 들고온 order, product 값이다
이 부분은 1번에서 들고온 product 값에 대해 다시 order 값들을 들고 와야 한다. 처음 orderList 값은 주소 값이기 때문에 1번에서 들고온 product에 포함이 되어 있었다
하지만, orderList 안의 내용 값은 1번에서 들고 오지 않았기에 for문을 시작하여 order를 하나씩 추출할때 두번째 select 문을 실행한다
두번째 select 문은 orderList안에 있는 order의 값을 들고오는 과정이다
세번째 select 문은 들고 온 새로운 order 값에서 toString()을 할때 order 안에 있는 product의 값을 들고 오기 위해 다시 실행되는것이다
두번째 select문에서 product 값을 join 해서 들고 오지 않았기에 product의 값은 가지지 않은 채로 order 값들을 들고 왔다. 그러므로 order 안에 있는 product 값들을 들고 오기 위해 마지막 세번째 select 문을 실행하여서 product 전체 값을 들고 온다
이러한 과정은 jpa 가 모든 데이터를 들고 있지 않고, 필요할때마다 데이터베이스에서 가져와 사용한다는 것을 알 수 있다.
2.3 연결클래스를 사용하는 다대다 매핑 단방향 (비식별 관계)
비식별 관계는 식별 관계와는 다르게 item table을 직접 생성해서 사용을 한다
이렇게 직접 연관 클래스를 만들어서 사용하게 되면, 데이터에 관한 인식이 편해질 뿐만 아니라, 데이터 관리도 편하게 된다
- 코드
Item table
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ITEM_ID")
private Long id; // 주문내역 아이디
@ManyToOne
@JoinColumn(name = "ORD_ID")
private OrderNonIdentifying order; // 주문 참조
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product; // 상품 참조
private Long price; // 주문 가격
private Long quantity; // 주문 수량
// 주문(Order)과의 양방향 참조 설정
public void setOrder(OrderNonIdentifying order) {
this.order = order;
order.getItemList().add(this);
}
명시적으로 order, product의 pk 키를 fk로 저장하고 있다
또한 양방향을 위해 order의 값을 저장한다
Order
@OneToMany(mappedBy ="order")
private List<Item> itemList = new ArrayList<Item>();
기존의 jointable을 없애고 item과 관계를 맺기 위해 일대다 관계를 맺어 준다
⇒ 단방향이기 때문에 Product에는 따로 설정을 하지 않는다
2.4 연결 클래스를 사용하는 다대다 양방향(비식별관계)
양방향 관계는 위의 단방향 관계에서 product에 item과 관계설정을 하면 양방향 관계가 설정된다
Product
@OneToMany(mappedBy = "product")
private List<Item> itemList = new ArrayList<Item>();
Product가 Item을 참조하게 하여서 일대다 관계를 맺도록 한다
Item
public void setProduct(ProductNonIdentifying product) {
this.product = product;
product.getItemList().add(this);
}
Item에도 Product 값을 바로 저장할 수 있도록 set 값을 생성한다
!!!! 프록시 객체가 자동으로 itemList 값을 저장해 준다 이것을 자동 연관 관계 관리라고 한다
그러나 자동으로 하기 때문에 명시적으로 하는 것보다 코드의 이해가 힘들고, 오류가 발생할 가능성이 있다. (김영한의 jpa 8장 참고)
- 결과
실행 코드
private static void selectData(EntityManagerFactory emf) {
EntityManager em = emf.createEntityManager();
OrderNonIdentifying order = em.find(OrderNonIdentifying.class, 1L);
System.out.println("주문 날짜 : " + order.getOrderDate());
System.out.println("[ 주문 목록 ]");
List<Item> itemList = order.getItemList();
for (Item item : itemList) {
System.out.println("---> " + item.getProduct().getName());
}
}
결과1 : ITEM 테이블 생성 및 조건 설정
Hibernate:
create table S_ITEM (
ITEM_ID bigint generated by default as identity,
price bigint,
quantity bigint,
ORD_ID bigint,
PRODUCT_ID bigint,
primary key (ITEM_ID)
)
Hibernate:
alter table S_ITEM
add constraint UK1min9dm3xj2einmn4tq8gscwk unique (ORD_ID, PRODUCT_ID)
Hibernate:
alter table S_ITEM
add constraint FKhn9efciwou1aoordkugwpqtye
foreign key (ORD_ID)
references S_ORD
Hibernate:
alter table S_ITEM
add constraint FKi0210b7rxwtlk1tquh6eqj8sy
foreign key (PRODUCT_ID)
references S_PRODUCT
식별 관계와 다르게 item 테이블에 다른 값들이 들어가고 order, product 테이블의 pk 값들이 복합키이자 기본키가 아니라 외래키이자 복합키로 삽입이 되었다
결과2 : 데이터 삽입 후 출력을 한 결과
Hibernate:
select
ordernonid0_.id as id1_3_0_,
ordernonid0_.CUSTOMER_ID as customer2_3_0_,
ordernonid0_.ORDER_DATE as order_da3_3_0_,
ordernonid0_.total as total4_3_0_
from
S_ORD ordernonid0_
where
ordernonid0_.id=?
주문 날짜 : 2024-07-24 15:10:52.812
[ 주문 목록 ]
Hibernate:
select
itemlist0_.ORD_ID as ord_id4_2_0_,
itemlist0_.ITEM_ID as item_id1_2_0_,
itemlist0_.ITEM_ID as item_id1_2_1_,
itemlist0_.ORD_ID as ord_id4_2_1_,
itemlist0_.price as price2_2_1_,
itemlist0_.PRODUCT_ID as product_5_2_1_,
itemlist0_.quantity as quantity3_2_1_,
productnon1_.id as id1_4_2_,
productnon1_.category as category2_4_2_,
productnon1_.name as name3_4_2_,
productnon1_.SHORT_DESC as short_de4_4_2_
from
S_ITEM itemlist0_
left outer join
S_PRODUCT productnon1_
on itemlist0_.PRODUCT_ID=productnon1_.id
where
itemlist0_.ORD_ID=?
---> LG 통돌이 세탁기
---> 갤럭시 20
fetchType = LAZY 이기 때문에 조인 테이블을 사용해 전체 데이터를 부르지 않고 사용할 order 테이블만 생성한다
뒤에 product 테이블과 item 테이블의 연관 관계를 출력할 때 두 테이블을 조인해서 값을 생성한다
'BackEnd' 카테고리의 다른 글
[JPA] Spring과 JPA-1 (0) | 2024.07.29 |
---|---|
[JPA] JPQL 연산자와 함수 (0) | 2024.07.25 |
[JPA] groupping과 subquery (0) | 2024.07.24 |
[JPA] JPQL (0) | 2024.07.22 |
[JPA] 연관관계 매핑(4장 다대일, 일대다) (0) | 2024.07.14 |