Cannot persist Entity with several inverse one-to-many relations due to UnsupportedOperationException
danielksb opened this issue · 2 comments
Expected Behavior
I should be possible to save the following entity with a CrudRepository:
@MappedEntity
public record Entity(
@Id @AutoPopulated Long id,
@Relation(value = Kind.ONE_TO_MANY, cascade = Cascade.ALL, mappedBy = "entity")
List<SubEntityA> subEntityAs,
// a second relationship is needed to trigger the error
@Relation(value = Kind.ONE_TO_MANY, cascade = Cascade.ALL, mappedBy = "entity")
List<SubEntityB> subEntityBs
){};
@MappedEntity
public record SubEntityA(
@Id @AutoPopulated Long id,
@Nullable Integer data,
// a back reference is needed to trigger the error
@Relation(Kind.MANY_TO_ONE)
Entity entity
){};
@MappedEntity
public record SubEntityA(
@Id @AutoPopulated Long id,
@Nullable Integer data,
@Relation(Kind.MANY_TO_ONE)
Entity entity
){};
interface EntityRepository extends CrudRepository<Entity, Long> {}
Actual Behaviour
When saving with CrudRepository.save(entity)
the following error is thrown:
io.micronaut.data.exceptions.DataAccessException: Error executing PERSIST: null
at io.micronaut.data.runtime.operations.internal.BaseOperations.failed(BaseOperations.java:141)
at io.micronaut.data.runtime.operations.internal.BaseOperations.persist(BaseOperations.java:90)
at io.micronaut.data.runtime.operations.internal.EntityOperations.persist(EntityOperations.java:31)
at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$persist$20(DefaultJdbcRepositoryOperations.java:706)
at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.lambda$executeWrite$24(DefaultJdbcRepositoryOperations.java:781)
at io.micronaut.data.connection.support.AbstractConnectionOperations.withExistingConnectionInternal(AbstractConnectionOperations.java:128)
at io.micronaut.data.connection.support.AbstractConnectionOperations.execute(AbstractConnectionOperations.java:92)
at io.micronaut.data.connection.ConnectionOperations.executeWrite(ConnectionOperations.java:82)
at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.executeWrite(DefaultJdbcRepositoryOperations.java:778)
at io.micronaut.data.jdbc.operations.DefaultJdbcRepositoryOperations.persist(DefaultJdbcRepositoryOperations.java:702)
at io.micronaut.data.runtime.intercept.DefaultSaveEntityInterceptor.intercept(DefaultSaveEntityInterceptor.java:45)
at io.micronaut.data.runtime.intercept.DataIntroductionAdvice.intercept(DataIntroductionAdvice.java:83)
at io.micronaut.aop.chain.MethodInterceptorChain.proceed(MethodInterceptorChain.java:138)
at mytest.EntityRepository$Intercepted.save(Unknown Source)
at mytest.EntityRepositoryIT.testCascadingSaveAndFetching(EntityRepositoryIT.java:21)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at io.micronaut.test.extensions.junit5.MicronautJunit5Extension$2.proceed(MicronautJunit5Extension.java:142)
at io.micronaut.test.extensions.AbstractMicronautExtension.interceptEach(AbstractMicronautExtension.java:162)
at io.micronaut.test.extensions.AbstractMicronautExtension$3.proceed(AbstractMicronautExtension.java:174)
at io.micronaut.test.context.TestMethodInterceptor.interceptTest(TestMethodInterceptor.java:46)
at io.micronaut.transaction.test.DefaultTestTransactionExecutionListener.lambda$interceptTest$0(DefaultTestTransactionExecutionListener.java:93)
at io.micronaut.transaction.support.AbstractPropagatedStatusTransactionOperations.lambda$execute$2(AbstractPropagatedStatusTransactionOperations.java:68)
at io.micronaut.transaction.TransactionCallback.apply(TransactionCallback.java:37)
at io.micronaut.transaction.support.AbstractTransactionOperations.executeTransactional(AbstractTransactionOperations.java:333)
at io.micronaut.transaction.support.AbstractTransactionOperations.executeWithNewTransaction(AbstractTransactionOperations.java:318)
at io.micronaut.transaction.support.AbstractTransactionOperations.executeNew(AbstractTransactionOperations.java:235)
at io.micronaut.transaction.support.AbstractTransactionOperations.doExecute(AbstractTransactionOperations.java:137)
at io.micronaut.transaction.support.AbstractTransactionOperations.lambda$doExecute$0(AbstractTransactionOperations.java:122)
at io.micronaut.data.connection.support.AbstractConnectionOperations.executeWithNewConnection(AbstractConnectionOperations.java:143)
at io.micronaut.data.connection.support.AbstractConnectionOperations.execute(AbstractConnectionOperations.java:90)
at io.micronaut.transaction.support.AbstractTransactionOperations.doExecute(AbstractTransactionOperations.java:120)
at io.micronaut.transaction.support.AbstractPropagatedStatusTransactionOperations.execute(AbstractPropagatedStatusTransactionOperations.java:65)
at io.micronaut.transaction.test.DefaultTestTransactionExecutionListener.interceptTest(DefaultTestTransactionExecutionListener.java:91)
at io.micronaut.test.extensions.AbstractMicronautExtension.interceptEach(AbstractMicronautExtension.java:166)
at io.micronaut.test.extensions.AbstractMicronautExtension.interceptTest(AbstractMicronautExtension.java:119)
at io.micronaut.test.extensions.junit5.MicronautJunit5Extension.interceptTestMethod(MicronautJunit5Extension.java:129)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$ListItr.set(ImmutableCollections.java:419)
at io.micronaut.data.runtime.operations.internal.AbstractCascadeOperations.afterCascadedMany(AbstractCascadeOperations.java:184)
at io.micronaut.data.runtime.operations.internal.SyncCascadeOperations.cascadeEntity(SyncCascadeOperations.java:157)
at io.micronaut.data.runtime.operations.internal.AbstractSyncEntityOperations.cascadePost(AbstractSyncEntityOperations.java:83)
at io.micronaut.data.runtime.operations.internal.BaseOperations.persist(BaseOperations.java:87)
... 36 more
The reason seems to be io.micronaut.data.runtime.operations.internal.AbstractSyncEntitiesOperations#getEntities
which is returning an immutable list of entities. But in io.micronaut.data.runtime.operations.internal.AbstractCascadeOperations#afterCascadedMany
an iterator is created for this list of entities and iterator.set(newc)
is called. For this to work we need an iterator over a mutable list.
This is possibly a regression bug due to this PR: #2905
Steps To Reproduce
- see code sample above for entity mapping
- create an entity with subentity A and subentity B set and save it to the repository.
Environment Information
- Error was observed in
io.micronaut.data:micronaut-data-runtime:4.8.1
after an upgrade tomicronaut-parent
4.5.0 - When downgrading to Micronaut 4.4.3 which is using
io.micronaut.data:micronaut-data-runtime:4.7.1
the code behaves as expected.
Example Application
No response
Version
4.5.0
Yes, you are right. This line change is causing this issue https://github.com/micronaut-projects/micronaut-data/pull/2905/files#diff-8a51dce18e47b3d23f3c13d9e60b50738f370fd0e2ca14db87cadac7c4303da5R160
Fixed by #3025