일단 제가 겪었던 한 사례를 말씀드리려고 합니다.
프로젝트 진행 도중 갑자기 이런 오류가 발생합니다. Spring과 Spring Data JPA 를 사용해서 진행을 했었습니다.
한 오류가 발생했었습니다.
java.lang.UnsupportedOperationException: null
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142) ~[na:na]
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.clear(ImmutableCollections.java:149) ~[na:na]
at org.hibernate.collection.spi.PersistentBag.clear(PersistentBag.java:471) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:512) ~[hibernate-core-6.5.2.Final.jar:6.5.2.Final]
~~
이런 오류가 발생하였습니다.
오류를 분석하다보면 다음과 같은 사실을 알 수 있는데요..! ImmutableCollections 라는 말이 나온것을 확인하다보니 불변해야하는 어떤 친구들을 건드렸다는 것이었습니다.
그런데 이 오류가 어느곳에서 터졌었나면? 바로 흔하디 흔한 저희가 자주 사용하게되는 코드인
userRepository.save(user);
이 친구였습니다. 그런데 위에서 살펴보니 이 User라는 엔티티는 어떻게 구성이 되어있었냐면요?
@Entity
@Setter
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Integer age;
@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private List<Team> teamList = new ArrayList<>();
public User(Integer age) {
this.age = age;
}
}
이런 상황이었습니다.
그리고 실제 사용하였던 코드는 다음과 같습니다.
@Transactional
public void setUser(){
User user1 = new User(10);
User user2 = new User(20);
List<Team> list = Arrays.asList(new Team("team1"), new Team("team2")).stream()
.map(team -> team).toList();
user1.setTeamList(list);
user2.setTeamList(new ArrayList<>(Arrays.asList(new Team("team3"), new Team("team4"))));
userRepository.save(user1);
userRepository.save(user2);
updateUserRank();
}
private void updateUserRank() {
List<User> age = userRepository.findAll(Sort.by("age").descending());
for (User fUser : age) {
fUser.setAge(30);
userRepository.save(fUser);
}
}
이 코드를 보고 어느 부분에서 잘못되었는지 만약 찾으셨다면! 제 글을 굳이 읽지 않으셔도 됩니다.
네 사실 정답은 제목에 있습니다. 바로 흔히 사용하는 stream -> map -> toList() 를 사용하였을때 오류가 생긴건데요! 그런데 이 List가 들어있는 User를 save하려다보니 어떤 변경사항이 발생하여서 오류가 생긴것이었습니다.
그래서 이 코드 내부를 한 번 들어가보시면
이렇게 ..어? Collections.unmodifiableList()
입니다. 불변하다는것을 알 수 있는데요!
네... 제 글은 여기서 끝입니다..? 라고 하면 섭섭하죠..
비슷하지만 다른 이 친구입니다.
아까 Stream.toList()를 봤으니 Collectors.toList() 내부를 보겠습니다.
이런식으로 구성이 되어있는데요 이 친구는 불변하지 않습니다. 그런데??! 이렇게 코드를 보다보면 저희가 또 생각나는 친구가 있습니다.
Collectors.toUnmodifiableList()
이 친구가 그러면 왜 존재하는지 사실 저는 조금 의문이었습니다. 왜냐하면 이와 같은 역할을 결국 Stream.toList() 에서 해주기 때문이었습니다.
출처 : https://rules.sonarsource.com/java/tag/java16/RSPEC-6204/
왜 생겼냐면 코드가 unmodifiableList() 까지 쓰기에는 코드가 장황해지기 때문에 java 16에서 Stream.toList()로 들어왔다. 라고 합니다.
위 사진을 보게 되었을때 그냥 가변이고 null 허용인 collect(toList())
, 불변이었던 두 친구 collect(toUnmodifiableList())
Stream.toList()
의 차이점은 바로 null을 허용하는지에 대한 여부였습니다.
그래서 어떻게 사용할 수 있는지 잘 알아보고 이제부터 사용해야겠습니다!