오늘의 에러 [ detached entity passed to persist]
ds_chanin
·2019. 11. 14. 01:39
발생한 에러 (2019.11.13)
org.hibernate.PersistentObjectException: detached entity passed to persist
문제 발생시 도메인 상황
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Champion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String riotId;
private String key;
@Column(unique = true)
private String name;
@OneToMany(mappedBy = "champion", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Stat> stats;
...
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer hp;
...
@ManyToOne(cascade = CascadeType.ALL)
private LolInfo lolInfo;
@ManyToOne(cascade = CascadeType.ALL)
private Champion champion;
...
}
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class LolInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String patchNoteVersion;
...
}
서비스 코드 상황
// 챔피언 스탯 정보 저장
public StatResDto saveStat(StatSaveDto statSaveDto, Long championId, Long lolInfoId) {
Champion champion = championRepository.findById(championId)
.orElseThrow(RuntimeException::new);
LolInfo lolInfo = lolInfoRepository.findById(lolInfoId)
.orElseThrow(RuntimeException::new);
Stat stat = statRepository.save(statSaveDto.toEntity(lolInfo, champion));
return StatResDto.of(stat);
}
실패한 테스트 코드
@Test
public void saveStat_정상저장() {
ChampionSaveDto championSaveDto = ChampionSaveDto.builder()
.name("아리")
.key("Ahri")
.riotId("109")
.build();
Champion champion = championRepository.save(championSaveDto.toEntity());
Long championId = champion.getId();
championRepository.flush();
LolInfoSaveDto lolInfoSaveDto = LolInfoSaveDto.builder()
.patchNoteVersion("9.22.1")
.build();
LolInfo lolInfo = lolInfoRepository.save(lolInfoSaveDto.toEntity());
Long lolInfoId = lolInfo.getId();
lolInfoRepository.flush();
StatSaveDto statSaveDto = StatSaveDto.builder()
.hp(100)
.build();
StatResDto statResDto = lolInfoService.saveStat(statSaveDto, championId, lolInfoId);
assertThat(statResDto.getHp()).isEqualTo(100);
}
실패한 이유
테스트 코드에서 Stat
을 save하는 과정에서 Champion
엔티티가 이미 DB 상에 존재하여 에러가 발생했다.
Stat
에 에서 Champion
에 대해 영속성을 CascadeType.ALL
로 설정을 해놓았기 때문에 Stat
을 저장할때 Champion
이 중복 저장되는 상황이 발생한 것이었다!
해결방법은 CascadeType.ALL
을 수정하거나 제거 하는것이다.
결국
Entity의 설계가 잘못되어 CascadeType의 설정이 ALL이 엉뚱한 위치에 있었다!
데이터를 파싱해 오는 곳에서는 Stat
을 기준으로 Champion
을 만들고 있었는데 반대가 되어야 했던 것이다.
그래서 서비스 코드에서 statRepositoy.save()
가 아닌 championRepository.save()
하는 방식으로 문제를 해결했다.
그래서 서비스코드가 다음과 같이 수정이 되었고
@Transactional
public StatResDto saveStat(StatSaveDto statSaveDto, Long championId, Long lolInfoId) {
Champion champion = championRepository.findById(championId)
.orElseThrow(RuntimeException::new);
LolInfo lolInfo = lolInfoRepository.findById(lolInfoId)
.orElseThrow(RuntimeException::new);
champion.getStats().add(0, statSaveDto.toEntity(lolInfo, champion));
return StatResDto.of(champion.getStats().get(0));
}
도메인은 다음과 같이 수정되었다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Champion {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Setter
private String riotId;
@Setter
private String key;
@Setter
private String name;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "stat_id")
private List<Stat> stats = new ArrayList<>();
...
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Stat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer hp;
...
@ManyToOne(fetch = FetchType.LAZY)
private LolInfo lolInfo;
@Builder
public Stat(Integer hp, ...) {
this.hp = hp;
...
}
}
맺으며
CascadeType.ALL 을 사용할때 부모객체가 생성될때 자식객체가 생성되도록 사용해야하고 자식객체가 부모객체를 같이 생성하게 사용하면 안될 것 같다.
써야한다면 부모와 자식의 관계가 1:1이라면 사용해도 되지 않을까?
'스터디 > JPA' 카테고리의 다른 글
Spring Data Jpa Bulk Insert 하기 (2) | 2021.01.28 |
---|---|
단방향 @OneToMany 관계에서 발생 할 수 있는 문제점 (0) | 2020.12.06 |
fetch join 과 limit 을 같이 쓸 때 주의하자. (firstResult/maxResults specified with collection fetch) (4) | 2020.10.08 |
[JPA] nullable = false와 @NotNull의 차이점 (4) | 2019.05.06 |
[이론] 영속성 컨텍스트 (0) | 2019.03.29 |