— 1 min read
최근 실무에서 오랜만에 JPA의 @ElementCollection
기능을 사용하게되었다. 많이 까먹은데다 정리해둔 것도 없어 처음 쓸때도 여러 번 검색하며 고민했고, 이런저런 에러도 마주하게 되어 간단히 정리해보려 한다.
@ElementCollection
이란?@ElementCollection
은 값 타입 collection을 매핑할 때 사용할 수 있는 기능이다.간단한 예시로 유저의 ‘신청’이라는 엔티티가 있고, ‘희망하는 날짜’를 여러개 체크할 수 있다고 가정하면 아래와 같이 구성해볼 수 있다.
1@Entity2class Apply(3 userId: Long,4 desiredDates: MutableList<LocalDate>5) {6 @Id7 @GeneratedValue(strategy = GenerationType.IDENTITY)8 val id: Long = 0910 val userId: Long = userId1112 @ElementCollection13 @CollectionTable(name = "apply_desired_date", joinColumns = [JoinColumn(name = "apply_id")])14 var desiredDates: MutableList<LocalDate> = desiredDates15 protected set16}
@CollectionTable
이라는 어노테이션이 함께 필요하다. 위 코드에서 여러개의 값을 저장하는 테이블은 apply_desired_date
이고, join은 apply_id
라는 컬럼으로 하게 된다.1create table apply (2 id bigint not null auto_increment,3 user_id bigint not null,4 primary key (id)5);6create table apply_desired_date (7 apply_id bigint not null,8 desired_dates date not null9);
@ElementCollection
을 쓰게 되었는가@ElementCollection
사용Kotlin의 List는 Immutable한 타입이다. 값을 변경해야한다면 MutableList를 사용한다. @ElementCollection
을 사용한 필드를 수정할 때 ‘완전히 새로운 List를 할당한다면 MutableList를 쓰지 않아도 괜찮지 않을까?’ 라고 무심코 생각하며 List를 사용했더니 아래와 같은 에러가 생겼다.
1java.lang.UnsupportedOperationException2 at java.base/java.util.AbstractList.remove(AbstractList.java:167)3 at java.base/java.util.AbstractList$Itr.remove(AbstractList.java:387)4 at java.base/java.util.AbstractList.removeRange(AbstractList.java:598)5 at java.base/java.util.AbstractList.clear(AbstractList.java:243)6 at org.hibernate.type.CollectionType.replaceElements(CollectionType.java:580)7 at org.hibernate.type.CollectionType.replace(CollectionType.java:757)8 at org.hibernate.type.TypeHelper.replace(TypeHelper.java:168)9 ...10 at jdk.proxy3/jdk.proxy3.$Proxy110.merge(Unknown Source)11 at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:669)12 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)13 at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)14 at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)15 at java.base/java.lang.reflect.Method.invoke(Method.java:568)
JPA 내부적으로는 AbstractList.remove()를 호출했는데 List타입에는 없는 메서드라 UnsupportedOperationException이 발생한 것.
그 전에 호출된 아래의 CollectionType의 replaceElements()
를 보면 iterator를 돌며 한땀한땀 target객체를 비운 뒤 original 객체에 add 하는 것을 확인할 수 있다.
1/**2 * Replace the elements of a collection with the elements of another collection.3 *4 * @param original The 'source' of the replacement elements (where we copy from)5 * @param target The target of the replacement elements (where we copy to)6 * @param owner The owner of the collection being merged7 * @param copyCache The map of elements already replaced.8 * @param session The session from which the merge event originated.9 * @return The merged collection.10 */11 public Object replaceElements(12 Object original,13 Object target,14 Object owner,15 Map copyCache,16 SharedSessionContractImplementor session) {17 java.util.Collection result = ( java.util.Collection ) target;18 result.clear();1920 // copy elements into newly empty target collection21 Type elemType = getElementType( session.getFactory() );22 Iterator iter = ( (java.util.Collection) original ).iterator();23 while ( iter.hasNext() ) {24 result.add( elemType.replace( iter.next(), null, session, owner, copyCache ) );25 }26/* ....너무 길어서 생략 */2728 return result;29 }
⇒ 수정될일 없는/수정되면 안되는 Collection이라면 List를 사용하고, 그렇지 않은 경우에는 MutableList를 사용해야한다는 결론을 얻었다. (쓰고보니 너무나 당연한 문장)
https://github.com/myangw/jpa-test
ApplyServiceTest
클래스에서 테스트를 돌려보며 확인 가능하다.