작성
·
268
0
기존 강의에다가 JWT token + security 를 적용하여 프로젝트를 하고 있는데 ,
failed to lazily initialize a collection of role: study.wonyshop.user.entity.User.orders, could not initialize proxy - no Session
오류가 발생합니다.
원인을 찾아보니 이 오류는 지연로딩된 컬렉션을 사용하는 도중에 세션이 종료되어서 발생하는 문제입니다. User
엔티티에서 orders
컬렉션을 지연로딩으로 설정하였기 때문에 실제로 컬렉션에 접근할 때 데이터베이스 연결이 필요하며 세션이 닫힌 상태에서는 접근할 수 없습니다.
package study.wonyshop.user.entity;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import study.wonyshop.common.TimeStamped;
import study.wonyshop.delivery.Address;
import study.wonyshop.order.entity.Order;
//import study.wonyshop.order.entity.Order;
@Entity
@Table(name = "USERS") //테이블 user 예약어 있어서 사용할 수 없음)
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id", nullable = false)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false, unique = true)
private String nickname;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private Address address;
@Setter
private String profileImage;
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
@Column(nullable = false, unique = true)
private String phoneNumber;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
//todo 즉시로딩 필요함 -> fetch 조인 생각 할것
/**
* 결제 수단은 포인트로 가능 ! 하다는 가정
*/
@Column(nullable = false)
private int point = 1000; // 기본 가입 포인트
private Boolean inUser; // 추후 휴면계정 관리 할때 사용 하기 위함
@Builder
public User(String email, String nickname, String password, Address address, String profileImage,
UserRoleEnum role, String phoneNumber) {
this.email = email;
this.nickname = nickname;
this.password = password;
this.address = address;
this.profileImage = profileImage;
this.role = role;
this.phoneNumber = phoneNumber;
}
// admin , seller 용
@Builder
public User(String email, String nickname, String password, Address address, String profileImage,
UserRoleEnum role, String phoneNumber, int point) {
this.email = email;
this.nickname = nickname;
this.password = password;
this.address = address;
this.profileImage = profileImage;
this.role = role;
this.phoneNumber = phoneNumber;
this.point = point;
}
/**
* 소비자가 -> 셀러 에게 지불
*
* @param totalPrice
*/
public void payForOrder(int totalPrice) {
int restPoint = this.point - totalPrice;
if (restPoint < 0) {
throw new IllegalArgumentException("포인트가 부족합니다. 포인트 충전 후 다시 이용해 주세요.");
}
this.point = restPoint;
}
/**
* 셀러가 판매해서 받은 돈
* @param totalPrice
*/
public void receivePayment(int totalPrice) {
this.point += totalPrice;
}
/**
* 취소 시 환불
* @param refundPayment
*/
public void refundPayment(int refundPayment){
this.point -= refundPayment;
}
}
package study.wonyshop.order.entity;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import study.wonyshop.common.TimeStamped;
import study.wonyshop.delivery.Delivery;
import study.wonyshop.delivery.DeliveryStatus;
import study.wonyshop.orderItem.OrderItem;
import study.wonyshop.user.entity.User;
@Entity
@Table(name = "ORDERS")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order extends TimeStamped {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "order_id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY) //연관관계 주인
@JoinColumn(name = "user_id")
private User user; // 주문자
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "delivery_id")
private Delivery delivery; //배송정보
@Enumerated(EnumType.STRING)
private OrderStatus status; //ORDER, CANCEL
private LocalDateTime orderDate;// 주문 날짜
@Builder
public Order(User user, Delivery delivery, OrderStatus status, LocalDateTime orderDate) {
this.user = user;
this.delivery = delivery;
this.status = status;
this.orderDate = orderDate;
}
//--- 양방향 연관관계 편의 메서드 ------// 편의메서드는 컨트롤 하는 쪽에 만들어주면 됨
// 다 쪽이 연관관계 주인으로 값 의 변경사항은 주인쪽에서 함
// 다 쪽 : 일의 값은 set 으로 저장
// 일 쪽 : 리스트(다) 를 조회하여 add
// order : user = m :1
public void setUser(User user) {
this.user = user;
user.getOrders().add(this);
} //이때 이것을 호출 하기전에 세션이 종료되어 에러 발생
//order : orderItem = 1: m
public void addOrderItem(OrderItem orderItem) {
this.orderItems.add(orderItem);
orderItem.setOrder(this);
}
// order : delivery = 1: 1
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
public void setOrderDate(LocalDateTime orderDate) {
this.orderDate = orderDate;
}
public void setStatus(OrderStatus status) {
this.status = status;
}
//===== 생성 메서드 =====//
//OrderItem... orderItems에서 ...은 가변 인자를 선언하는 부분입니다. 이는 OrderItem 타입의 인자를 0개 이상 받을 수 있다는 의미입니다.
//Order order = createOrder(user, delivery, item1, item2);
public static Order createOrder(User user, Delivery delivery, OrderItem... orderItems) {
Order order = new Order();
order.setUser(user);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
//--- 비지니스 로직 -------//
/**
* 주문취소
*/
public void cancel(){
if(delivery.getDeliveryStatus() == DeliveryStatus.COMP ) {
throw new IllegalStateException(" 이미 배송이 완료된 상품은 취소가 불가능 합니다. "); //Non-cancellable product
}
this.setStatus(OrderStatus.CANCEL);
// 주문 상품을 다 취소 시켜야함
for(OrderItem orderItem : orderItems){
orderItem.cancel();
}
}
//=== 조회 로직 =====//
/**
* 전체 주문 가격 조회
*/
public int getTotalPrice(){
int totalPrice = 0;
for(OrderItem orderItem :orderItems){
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;
}
}
@PostMapping("")
public OrderResponse orderItem(@AuthenticationPrincipal UserDetailsImpl userDetails
, @RequestBody OrderRequest orderRequest) {
Long itemId = orderRequest.getItemId();
int quantity = orderRequest.getQuantity();
return orderService.orderItem(userDetails.getUser(), itemId, quantity);
}
@Service
@RequiredArgsConstructor
public class OrderService {
private final OrderRepository orderRepository;
private final ItemRepository itemRepository;
private final UserRepository userRepository;
private final DeliveryRepository deliveryRepository;
@Transactional
public OrderResponse orderItem(User user, Long itemId, int quantity) {
Item findItem = itemRepository.findById(itemId).orElseThrow(
() -> new CustomException(ExceptionStatus.NOT_EXIST)
);
//userRepository.findByIdWithOrders(user.getId());
//배송 생성
Delivery delivery = new Delivery(user.getAddress(), DeliveryStatus.READY);
// 주문상품 생성
OrderItem orderItem = OrderItem.createOrderItem(findItem, findItem.getPrice(), quantity);
// 주문생성
Order order = Order.createOrder(user, delivery, orderItem);
deliveryRepository.save(delivery);
userRepository.save(user);
orderRepository.save(order);
return new OrderResponse(order);
}}
이 상태에서
//userRepository.findByIdWithOrders(user.getId());
이 주석 된 부분에 이처럼 user 객체를 가져올때 오더와 함께 가져오는 페치조인을 사용해봤지만 이번 엔 user가 null 값이라는 에러가 납니다. ㅠㅠㅠ 아무리 머리를 굴려도 ㅠㅠㅠㅠㅠ 해결이 안되네용 ㅠㅠㅠ
페치조인 코드 는 아래와 같이 작성했습니다.
public interface OrderRepository extends JpaRepository<Order,Long> {
@Query("select u from User u join fetch u.orders WHERE u.id = :userId")
User findByIdWithOrders(@Param("userId")Long id);
}
해결방법은
FetchType.EAGER
이걸 적용하면 되는데 eager 적용하지 않고 하는 방법 없을까요 ㅠㅠ
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL,Fetch = FetchType.EAGER orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
답변 1
0
안녕하세요. 빛혜원님, 공식 서포터즈 y2gcoder입니다.
문제해결하고자 하는 요구사항을 정확하게 모르는 상태로 조심스럽게 답변합니다 :)
orderItem() 메서드에서 user 객체를 파라미터로 받고 계신데 파라미터로 받은 user 객체가 null이라면 말씀하신 문제가 발생할 수 있을 것 같습니다.
//userRepository.findByIdWithOrders(user.getId());
위의 메서드로 불러온 user를 사용하고 싶으시다면
user = userRepository.findByIdWithOrders(user.getId());
이러한 과정이 필요해보입니다!
감사합니다.