[Spring Data JPA] @OneToMany 관계시 발생한 에러

JPA의 구현체인 hiberante를 이용해서 엔티티간의 상속관계를 설정하고 처음 사용해보는데 발생한 에러인 A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance 문제 해결한 방법을 기록하려고 합니다.

User의 엔티티와 Customer엔티티간의 1:N의 연관관계를 맺기 위해 User에는 @OneToMany를 Customer에는 @ManyToOne관계를 설정을 하고 로직을 돌려보는데 위와 같은 에러가 발생했습니다.


문제점 1. 초기화를 하지 않았다.

public class User{
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long id;

  @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
  private List<Customer> customers;
}

아직 연관관계맺는것에 익숙하지 않아 다른 필드와 같이 선언만 해주고 초기화 코드가 없었고, 새로운 User를 선언하거나 수정할때 해당 cutomer가 null이 되서 아무 것도 참조하지 않는다고 에러가 뜬 것이었습니다.

엔티티를 영속 상태로 만들때 컬렉션 필드를 효율적으로 관리하기 위해 하이버네이트에서 미리 만들어둔 Wrapper클래스로 감싸(변경하여) 저장하기 때문에 즉시 초기화해서 사용하는 것을 권장한다고 합니다.

  • PersistentBag : Collection/List (중복 허용o / 순서 x)
  • PersistentSet : Set (중복 허용 x / 순서 x)
  • PersistentList : List + @OrderColumn (중복 허용 o / 순서 o)

번외) @OrderColumn잘 사용하지 않는 이유

OrderColumn을 사용하면 순서를 저장하기 위해 position컬럼도 추가로 저장외 되는데 1:N의 관계에서 키를 1이 가지는 경우의 문제처럼 1에서 N을 저장하려고 할때 position을 모르기 때문에 insert시 처음 position없이 삽입하고 다시 update하는 sql문이 추가로 발생하게 됩니다.

해결방법

public class User{
  @Id
  @GeneratedValue(strategy=GenerationType.IDENTITY)
  private Long id;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Customer> customers = new ArrayList<>();
}

초기화 문제이기 때문에 생성자등으로 초기화를 해주거나 위와 같이 처음부터 비어있는 ArrayList를 갖도록 바꾸어 주었습니다.


문제점 2. Update시에 똑같은 에러 발생

Update시에 Dto를 이용해서 입력을 받아오고 새로운 Entity객체를 만들어 save하려고 보니 위의 에러가 발생했습니다. 정확한 이유는 알 수 없었지만 많은 고민끝에 빈 컬렉션이라고 해도 기존의 User엔티티가 참조하고 있는 customers와 새로운 entity의 customers가 같지 않기 때문에 (hashcode가 다르다.) 새로운 객체로 삽입시 참조가 끊기기 때문에 에러가 발생한 거라고 추측하고 새로운 Entity에도 기존의 customers를 추가하는 방법으로 해결했습니다.

@PatchMapping("/me")
public ApiResult<User.Info> updateMyInfo(@Valid @RequestBody UpdateInfoRequest updateInfoRequest,
                                             JwtAuthenticationToken authentication){
  User user = (User)authentication.getDetails();
  User updateUser = User.builder()
                .id(user.getId())
                .customers(user.getCustomers())   //추가한 부분 (기존에는 빼놓고 작성해서 다른 비어있는 ArrayList가 들어갔을 것이다.)
                .accumulationRate(updateInfoRequest.getAccumulationRate())
                .email(updateInfoRequest.getEmail())
                .name(updateInfoRequest.getName())
                .password(updateInfoRequest.getPassword())
                .phoneNumber(updateInfoRequest.getPhoneNumber())
                .build();
  return success(User.Info.of(userService.insert(updateUser)) );
}


일단은 이렇게 에러는 해결했는데 글을 보시고 문제점이 있는 부분을 말씀해주시면 공부하고 수정하도록 하겠습니다.