개요
- Redis에 Entity 객체를 저장하면서 생긴 순환참조 문제에 대해 이해하고 해결 할 수 있습니다
코드
LolPlayerHistoryEntity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@EntityListeners(AuditingEntityListener.class)
@Entity
public class LolPlayerHistory implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
private String playerHistoryTitle;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LolType type;
@OneToMany(mappedBy = "playerHistory", cascade = CascadeType.ALL, orphanRemoval = true)
private List<LolPlayer> players = new ArrayList<>();
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
public LolPlayerHistory(User user, String playerHistoryTitle, LolType type) {
this.user = user;
this.playerHistoryTitle = playerHistoryTitle;
this.type = type;
}
public static LolPlayerHistory from (LolPlayerHistoryRequestDto dto, User user, LolType type) {
return new LolPlayerHistory(
user,
dto.getPlayerHistoryTitle(),
type
);
}
public void updatePlayerHistoryTitle(String playerHistoryTitle) {
this.playerHistoryTitle = playerHistoryTitle;
}
}
LolPlayerEntity
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LolPlayer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LolTier tier;
@Column(nullable = false)
private int mmr;
@OneToMany(mappedBy = "player", cascade = CascadeType.ALL, orphanRemoval = true)
private List<LolLines> lines = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "lol_player_history_id")
private LolPlayerHistory playerHistory;
public LolPlayer(String name, LolTier tier, int mmr, LolPlayerHistory playerHistory) {
this.name = name;
this.tier = tier;
this.mmr = mmr;
this.playerHistory = playerHistory;
}
public static List<LolPlayer> from(LolPlayerHistoryRequestDto dto, LolPlayerHistory playerHistory) {
List<LolPlayer> playerList = new ArrayList<>();
for ( LolPlayerDto riftPlayerRequestDto : dto.getLolPlayerDtos() ) {
playerList.add(
new LolPlayer(
riftPlayerRequestDto.getName(),
riftPlayerRequestDto.getTier(),
riftPlayerRequestDto.getTier().getScore(),
playerHistory
)
);
}
return playerList;
}
public static List<LolPlayerDto> to(List<LolPlayer> players) {
return players.stream()
.map(player -> {
// 각 플레이어의 라인을 별도로 처리
List<LolLinesDto> lolLinesDtos = player.getLines().stream()
.map(line -> new LolLinesDto(line.getLine(), line.getLineRole()))
.collect(Collectors.toList());
// 새로운 LolPlayerDto 생성
return new LolPlayerDto(
player.getName(),
player.getTier(),
lolLinesDtos,
player.getMmr()
);
})
.collect(Collectors.toList());
}
}
현재상황
- LolPlayerHisotry는 어려개의 LolPlayer를 가질 수 있다
- LolPlayer는 하나의 LolPlayerHisotry를 가질 수 있다
- 현재 양방향 관계
- UserEntity에는 Jpa의 Lazy Loading을 적용하여 정보가 필요하지않을때는 불러오지 않게 설정하였다
문제상황
public void setPlayerHistoryDetailTeam(LolPlayerHistory lolPlayerHistory){
redisTemplate.opsForValue().set(REDIS_NAME_PLAYER_HISTORY + lolPlayerHistory.getId(), lolPlayerHistory);
}
- Redis에 Entity 객체인 LolPlayerHisotry를 저장시키려고 하는순간 문제가 발생한다
Document nesting depth (1001) exceeds the maximum allowed (1000, from `StreamWriteConstraints.getMaxNestingDepth()`)
- Jackson이 직렬화 하는 과정에서 그 객체의 깊이가 1000개를 넘어 에러가 발생한것이다
- 즉 순환참조가 발생됐다고 볼 수 있다
근데 왜 JPA를 활용한 Entity에 저장할때는 순환참조 문제가 생기지 않는것일까? 분명 양방햔 연관관계인데...
Redis에 저장할떄는 직렬화후에 저장해야하는 반면
Entity를통한 DB에 저장할떄는 필요하지않기 때문이다
다시 본론으로 돌아가보자
그 이유가 뭘까?
- 서로 양방향 연관관계있다
- Jackson은 PlayerHisotry를 직렬화한다
- PlayerHisotry를 직렬화할떄 Player도 직렬화를 하는데
- Player안에서 다시 PlayerHisotry를 참조하고있다
- 따라서 스프링은 PlayerHistory,Player Bean중 어떤것을 먼저 만들어야할지 몰라서 에러가 난다고 볼 수 있다
- 그래서 계속 참조하다가 객체의 깊이가 초과치까지 깊어지다가 에러가 발생되는것이다
그럼 어떻게 해결가능한가?
- 간단하다
- 순환참조를 가지지 않게 하면되기 때문에 DTO로 변환해주면된다
public void setPlayerHistoryDetailTeam(LolPlayerHistory lolPlayerHistory){ redisTemplate.opsForValue().set(REDIS_NAME_PLAYER_HISTORY + lolPlayerHistory.getId(), LolPlayerHistoryResponseDetailDto.of(lolPlayerHistory)); }
- Entity -> DTO
- 문제해결!
'SpringBoot' 카테고리의 다른 글
카카오 소셜 로그인시, CORS 문제 발생 (1) | 2025.03.13 |
---|---|
다양한 상황에서의 DB에 저장하는 시간을 알아보자 (0) | 2025.02.26 |
JSP 란? (1) | 2025.01.21 |
JPA Cascade ? (2) | 2025.01.20 |
디스패처 서블릿(Dispatcher-Servlet) ? (0) | 2025.01.20 |