[내일배움캠프 Spring 4기] 57일차 TIL - "detached entity passed to persist" 해결 | 내 프로필 조회 기능 구현 (이어서)

서예진·2024년 3월 3일
0

오늘의 학습 키워드 💻

▸ 오늘의 코드카타
▸ "detached entity passed to persist" 해결
▸ 내 프로필 조회 기능 구현 (이어서)


✅ 오늘의 코드카타

2024년 2월 27일 - [프로그래머스 - 자바(JAVA)] 31 : 프로세스


✅ "detached entity passed to persist" 해결

  • 다른 팀원분들이 구현한 코드를 pull 받아와서 돌려보니 Hibernate에서 다음과 같은 에러가 떴다.

    InvalidDataAccessApiUsageException: detached entity passed to persist

  • 이 오류는 JPA에서 영속 상태가 아닌 엔티티를 persist() 메서드에 전달하려고 할 때 발생한다.
  • 오류의 발생 이유 : JPA에서 엔티티의 영속성 상태를 관리하기 위해 사용하는데, 엔티티가 영속성 컨텍스트에서 분리된 상태에서는 persist() 등의 영속성 관련 메서드를 호출할 수 없기 때문이다.
  • 코드를 자세히 살펴보니 User Entity와 1:N 관계를 갖는 Post Entity에 오류의 원인이 있었다.
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Table(name = "TB_POST")
public class PostEntity {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long postId;

	...

	@ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
	@JoinColumn(name = "user_id")
	@OnDelete(action = OnDeleteAction.CASCADE)
	private UserEntity userEntity;

	public PostEntity(PostRequestDto requestDto, UserEntity entity) {
		this.title = requestDto.getTitle();
		this.content = requestDto.getContent();
		this.views = 0L;
		this.userEntity = entity;
	}
}
  • 위의 코드를 보면 @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST) 여기서 cascade = CascadeType.PERSIST 때문에 오류가 발생했다.
  • cascade = CascadeType.PERSIST 는 JPA에서 관계를 관리할 때 사용되는 설정 중 하나이다. 이 설정은 엔티티 간의 관계가 영속성 컨텍스트에서 동일한 영속성 상태를 갖도록 하는 방법을 지정한다.
  • PERSIST는 연관된 엔티티를 영속 상태로 만들 때 연관 엔티티도 함께 영속화한다. 따라서 부모 엔티티를 저장할 때 자식 엔티티도 함께 저장됩니다.
    이 설정을 사용하면 부모 엔티티를 저장할 때 연관된 자식 엔티티가 자동으로 저장된다.
  • 그러나 여러 상황에서 CascadeType.PERSIST를 사용할 때 부작용이 발생할 수 있다.
    • 예를 들어, 이미 영속 상태에 있는 부모 엔티티를 다시 persist() 메서드로 저장하려고 시도할 때 문제가 발생할 수 있다.
    • 이때 자식 엔티티가 이미 영속 상태에 있다면, 부모 엔티티를 다시 영속화하면서 자식 엔티티도 함께 저장하려고 할 것이다.
    • 하지만 이미 영속 상태인 자식 엔티티를 다시 영속 상태로 저장하려고 하면 데이터베이스에서 "detached entity passed to persist"와 같은 오류가 발생할 수 있다.
    • 즉, 테스트 코드에서 중복 저장이 되면서 오류가 발생되었다.
  • 해결 방법 : cascade = CascadeType.PERSIST 이 설정을 삭제하면 된다.

✅ 내 프로필 조회 기능 구현 (이어서)

  • 어제의 구현에 이어서 이제 내 프로필에서 내 팔로워, 내 댓글, 내 게시글를 같이 조회할 수 있게 기능을 구현했다.
  • 그러기 위해서는 다른 도메인의 repository를 주입받아왔다.

UserService

private final UserRepository userRepository;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
private final FollowRepository followRepository;

...

public ProfileResponseDto getProfile(User user) {

        ProfileResponseDto profileResponseDto = user.profileResponseDto();

        profileResponseDto.setMyPosts(getPostResponseDtoListBy(user));
        profileResponseDto.setMyComments(getCommentResponseDtoListBy(user));
        profileResponseDto.setMyFollowers(getFollowerListBy(user));

        return profileResponseDto;
    }
    
...

public List<GetPostResponseDto> getPostResponseDtoListBy(User user) {
        return postRepository
            .findByUserEntityUserId(user.toEntity().getUserId()).stream()
            .filter(Objects::nonNull)
            .map(GetPostResponseDto::new)
            .collect(Collectors.toList());
    }

public List<CommentResponseDto> getCommentResponseDtoListBy(User user) {
        return commentRepository
            .findByUserEntityUserId(user.toEntity().getUserId()).stream()
            .filter(Objects::nonNull)
            .map(CommentResponseDto::new)
            .collect(Collectors.toList());
    }

public List<GetProfileResponseDto> getFollowerListBy(User user) {
        return followRepository.findAllByFollowing(user.toEntity()).stream()
            .filter(Objects::nonNull)
            .map(followEntity -> new GetProfileResponseDto(followEntity.getFollower()))
            .collect(Collectors.toList());
    }
  • 로그인된 유저가 작성한 게시글, 댓글, 팔로워 리스트를 반환하는 메서드를 만들어 주고 프로필을 조회하는 메서드에서 호출하는 방식으로 했다.
  • 위의 코드를 작성하면서 User 도메인에서 다른 도메인의 repository를 주입받아오는 것이 너무 복잡하다고 생각이 들었고 보다 간결하게 작성하는 방법이 없는지 고민했다.
  • 이와 같은 고민과 함께 다른 수강생분들과 튜터님께 피드백을 요청했고 받은 피드백은 다음과 같다.
    1. UserService에서 다른 도메인의 repository를 주입받아오지 말고 각 도메인 Service에서 처리한 후, 그 service를 주입받아오기
      즉, repository가 아니라 service를 주입받아오기
    2. 위와 같이 한번에 보여주려고 하지 말고 API를 더 나누기
  • 현실적으로는 실무에서는 내 코드 처럼 1개의 API에서 처리하는 것이 아니라 2번 피드백같이 API를 나눌 것 같다.
profile
안녕하세요

0개의 댓글