1) 일대일
2) 일대다
3) 다대일
4) 다대다
장바구니 - 회원
@Entity
@Table(name = "cart")
@Getter @Setter
@ToString
public class Cart extends BaseEntity {
@Id
@Column(name = "cart_id")
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id")
private Member member;
public static Cart createCart(Member member){
Cart cart = new Cart();
cart.setMember(member);
return cart;
}
}
- Cart는 member_id 컬럼을 외래키로 갖게 된다.
- JPA는 영속성 컨텍스트에 데이터를 저장 후 트랜잭션 끝날 때 FLUSH() 호출해서 데이터베이스에 반영
- JPA는 영속성 컨텍스트로부터 엔티티 조회 후 영속성 컨텍스트에 엔티티가 없으면 데이터베이스 조회
CartItem.java
@Entity
@Getter @Setter
@Table(name="cart_item")
public class CartItem extends BaseEntity {
@Id
@GeneratedValue
@Column(name = "cart_item_id")
private Long id;
//cartitem(자신) : card = 다대일
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="cart_id")
private Cart cart;
// caritem : item = 다대일
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
private int count;
public static CartItem createCartItem(Cart cart, Item item, int count) {
CartItem cartItem = new CartItem();
cartItem.setCart(cart);
cartItem.setItem(item);
cartItem.setCount(count);
return cartItem;
}
public void addCount(int count){
this.count += count;
}
public void updateCount(int count){
this.count = count;
}
}
Order.java
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order extends BaseEntity {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
private LocalDateTime orderDate; //주문일
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus; //주문상태
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL
, orphanRemoval = true, fetch = FetchType.LAZY)
private List<OrderItem> orderItems = new ArrayList<>();
}
OrderItem.java
@Entity
@Getter @Setter
public class OrderItem extends BaseEntity {
@Id @GeneratedValue
@Column(name = "order_item_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id")
private Item item;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
private int orderPrice; //주문가격
private int count; //수량
}
Order에서도 Orderitem을 참조하고, Orderitem도 Order을 참조하는
이를 양방향 구조라고 한다, 이떄 양방향은 연관관계의 주인을 반듯시! 설정해줘야 한다.
외래키가 있는 곳을 주인으로 지정
외래키가 있는 게
多 쪽
多 쪽
이 무조건연관관계 주인
- 그래서 다대일 (@ManyToOne)은 mappedBy 속성이 없다고 보면 됨
- 연결 테이블에는 컬럼을 추가할 수 없기 때문입니다.
- 추가 이유 출처 : https://gilssang97.tistory.com/
- 1) 중간 테이블을 생성해주긴 하지만 묵시적으로 생성해주기 때문에 자기도 모르는 복잡한 조인의 쿼리가 발생하는 경우가 생길 수 있다.
- 2) 우리는 중간 테이블에 두 테이블의 기본키를 기본키이자 외래키로 들고와서 추가로 필요한 컬럼이 존재할 확률이 크지만, 중간 테이블에 필요한 추가 컬럼을 사용할 수 없다. (두 테이블에 추가된 컬럼에 대해 매핑이 되지 않기 때문이다.)
특정 엔티티와 연관된 엔티티의 상태를 함께 변화시키는 옵션
CascadeType.ALL 사용해보기
Order.java
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL
, orphanRemoval = true, fetch = FetchType.LAZY)
private List<OrderItem> orderItems = new ArrayList<>();
(+) example
고아 객체 제거 기능위한 주의사항
- 고아 객체 제거기능은 참조하는 곳이 하나일 때만 사용
- 다른 곳에서도 참조하는 엔티티인데 삭제하면 문제가 생길 수 있음
- 즉 @OnetoMany, @OnetoOne 의 옵션으로 묶인 필드에게 적용할 수 있는 것
Order.java
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL
, orphanRemoval = true, fetch = FetchType.LAZY)
private List<OrderItem> orderItems = new ArrayList<>();
CASCADE REMOVE 옵션 VS orphanRemoval = true 옵션
1) CASCADE REMOVE
- 기능 : 부모 엔티티가 삭제되면 자식 엔티티도 삭제
- 부모가 자식과의 관계를 제거한다고 하더라도, 즉 자식과 부모 관계를 끊는다고 하여도 자동적으로 삭제되는 것이 아님
If you invoke setOrders(null), the related Order entities will NOT be removed in db automatically.
2) orphanRemoval = true- 기능 : 부모 엔티티 삭제되면 자식 엔티티도 삭제
- 자식의 부모관계를 끊어버리자마자 DB에서 자동적으로 삭제됨
setOrders(null), the related Order entities will be removed in db automatically.
FETCH 전략에는 즉시 로딩 이외에도 지연로딩 전략 존재
일대일, 다대일로 매핑할 경우, 기본 전략인 즉시 로딩을 통해 엔티티를 함께 가져옴
즉시로딩은 엔티티가 자신과 매핑된 엔티티들도(자신이 아닌 엔티티들까지;;) 한꺼번에 데려오는 것
실제 비즈니스 하고 있다면 매핑되는 엔티티 갯수는 훨씬 많음
개발자는 쿼리 실행 예측 불가능, 또한 대량의 데이터 한번에 조회 => 성능문제 가능성 존재
=> 따라서 lazy 타입을 사용해야 합니다.
엔티티 공통 속성으로 등록시간, 수정시간 존재
실제 서비스 운영 시 등록시간, 수정시간, 등록자, 수정자를 테이블에 넣어놓고 활용 필요
데이터 생성되거나 수정될 때 시간 기록 , 어떤 사용자가 등록을 했는지 아이디 남기기
=> 이 컬럼들은 버그 있거나 문의 들어올 때 활용 가능
데이터 대용량으로 업데이트 진행 시, 다시 업데이트 진행해야할 경우 변경 대상 찾을 때 활용 가능하다
Spring Data JPA에서는 Auditinfg 기능을 제공해 엔티티가 저장, 수정될 때 자동으로 등록일, 수정일, 등록자, 수정자 입력
즉 엔티티의 생성과 수정을 감시받고 있는 것
= > 이런 공통 멤버 변수들을 추상클래스로 만들고, 해당 추상클래스를 상속받는 형태로 엔티티 리팩토링
AuditAwareImpl.java
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userId = "";
if(authentication != null){
userId = authentication.getName();
}
return Optional.of(userId);
}
- 현재 로그인한 사용자의 정보를 조회해 사용자의 이름을 등록자와 수정자로 지정
AuditConfig.java
@Configuration
@EnableJpaAuditing
public class AuditConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
}
@Configuration 어노테이션의 정체는!?
예전부터 모르고 지나갔지만 이제야 알아본다.
출처 : https://yhmane.tistory.com/129
@Component
- 개발자가 직접 작성한 클래스를 bean 등록하고자 할 경우 사용
@Configuration + @Bean- 외부라이브러리 또는 내장 클래스를 bean으로 등록하고자 할 경우 사용.
- 1개 이상의 @Bean을 제공하는 클래스의 경우 반드시 @Configuraton을 명시
BaseTimeEntity.java
@EntityListeners(value = {AuditingEntityListener.class})
@MappedSuperclass
@Getter @Setter
public abstract class BaseTimeEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime regTime;
@LastModifiedDate
private LocalDateTime updateTime;
}