Inheritance cascade of fields with bean inheritance
Deletes aren't cascaded to embedded beans referenced using @ElementCollection
Steps to reproduce
The (abstract) data model is:
- A bean (
RootBean) that is inherited bySimpleBeanandComplexBean ComplexBeanembeds instances ofElementBeanRootBeans are referenced through@OneToManyfromReferencingBean
@Entity
class ReferencingBean {
@Id
@GeneratedValue
public UUID id;
@OneToMany(cascade = CascadeType.ALL)
private List<RootBean> rootBeans;
public ReferencingBean(List<RootBean> rootBeans) { ... }
public List<RootBean> getRootBeans() { ... }
public void setRootBeans(List<RootBean> rootBeans) { ... }
}
@Entity
@Inheritance
abstract class RootBean {
@Id
@GeneratedValue
public UUID id;
}
@Entity
class SimpleBean extends RootBean {
private String value;
public SimpleBean(String value) { ... }
public String getValue() { ... }
public void setValue(String value) { ... }
}
@Entity
class ComplexBean extends RootBean {
@ElementCollection
private List<ElementBean> elements;
public ComplexBean(List<ElementBean> elements) { ... }
public List<ElementBean> getElements() { ... }
public void setElements(List<ElementBean> elements) { ... }
}
@Embeddable
class ElementBean {
@EmbeddedId
@GeneratedValue
private Long id;
@NotNull
private String value;
public ElementBean(String value) { ... }
public String getValue() { ... }
}
Instantiating the data structure described above and then deleting a ReferencingBean fails as illustrated by the test below:
RootBean bean1 = new ComplexBean(asList(new ElementBean("element-1"), new ElementBean("element-2")));
RootBean bean2 = new SimpleBean("simple-1");
ReferencingBean referencingBean = new ReferencingBean(asList(bean1,bean2));
database.save(referencingBean);
database.delete(referencingBean);
assertNull(database.find(RootBean.class, database.getBeanId(bean1)));
assertNull(database.find(RootBean.class, database.getBeanId(bean2)));
with the following output:
2019-10-17 09:37:28,307 INFO [main] io.ebean.config.properties.LoadContext:83 - loaded properties from []
2019-10-17 09:37:28,325 INFO [main] io.ebean.EbeanVersion:31 - ebean version: 11.45.1
2019-10-17 09:37:28,629 INFO [main] io.ebean.datasource.pool.ConnectionPool:297 - DataSourcePool [db] autoCommit[false] transIsolation[READ_COMMITTED] min[2] max[200]
2019-10-17 09:37:28,822 INFO [main] io.ebeaninternal.server.core.DefaultContainer:208 - DatabasePlatform name:db platform:h2
2019-10-17 09:37:29,466 INFO [main] io.ebean.migration.ddl.DdlRunner:65 - Executing db-drop-all.sql - 35 statements
2019-10-17 09:37:29,475 INFO [main] io.ebean.migration.ddl.DdlRunner:65 - Executing db-create-all.sql - 35 statements
2019-10-17 09:37:29,538 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] insert into referencing_bean (id) values (?); -- bind(3bb12ba3-a997-4466-bb4e-f87ab996d43b)
2019-10-17 09:37:29,547 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] insert into root_bean (id, referencing_bean_id, dtype) values (?,?,?)
2019-10-17 09:37:29,547 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] -- bind(f31c05f1-ab35-45d9-9206-a28ae91eeb7f,3bb12ba3-a997-4466-bb4e-f87ab996d43b,ComplexBean)
2019-10-17 09:37:29,555 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] insert into root_bean_elements (root_bean_id,id,value) values (?,?,?)
2019-10-17 09:37:29,556 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] -- bind(f31c05f1-ab35-45d9-9206-a28ae91eeb7f, null, element-1)
2019-10-17 09:37:29,556 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] -- bind(f31c05f1-ab35-45d9-9206-a28ae91eeb7f, null, element-2)
2019-10-17 09:37:29,559 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] insert into root_bean (id, referencing_bean_id, dtype, value) values (?,?,?,?)
2019-10-17 09:37:29,559 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] -- bind(53372fc0-8928-40f1-a1f0-15c66d5d3aaa,3bb12ba3-a997-4466-bb4e-f87ab996d43b,SimpleBean,simple-1)
2019-10-17 09:37:29,591 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] select t0.id from referencing_bean t0 where t0.id = ? ; --bind(3bb12ba3-a997-4466-bb4e-f87ab996d43b, )
2019-10-17 09:37:29,595 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] select t0.dtype, t0.id, t0.value from root_bean t0 where t0.id = ? ; --bind(f31c05f1-ab35-45d9-9206-a28ae91eeb7f, )
2019-10-17 09:37:29,596 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] select t0.dtype, t0.id, t0.value from root_bean t0 where t0.id = ? ; --bind(53372fc0-8928-40f1-a1f0-15c66d5d3aaa, )
2019-10-17 09:37:29,603 DEBUG [main] io.ebeaninternal.server.logger.DSpiLogger:26 - txn[] select t0.id from root_bean t0 where referencing_bean_id=? ; --bind(3bb12ba3-a997-4466-bb4e-f87ab996d43b)
io.ebean.DataIntegrityException: Referential integrity constraint violation: "FK_ROOT_BEAN_ELEMENTS_ROOT_BEAN_ID: PUBLIC.ROOT_BEAN_ELEMENTS FOREIGN KEY(ROOT_BEAN_ID) REFERENCES PUBLIC.ROOT_BEAN(ID) ('f31c05f1-ab35-45d9-9206-a28ae91eeb7f')"; SQL statement:
delete from root_bean where id in (?,?) [23503-193]
at io.ebean.config.dbplatform.SqlCodeTranslator.translate(SqlCodeTranslator.java:49)
at io.ebean.config.dbplatform.DatabasePlatform.translate(DatabasePlatform.java:227)
at io.ebeaninternal.server.transaction.TransactionManager.translate(TransactionManager.java:243)
at io.ebeaninternal.server.transaction.JdbcTransaction.translate(JdbcTransaction.java:689)
at io.ebeaninternal.server.core.PersistRequest.translateSqlException(PersistRequest.java:105)
at io.ebeaninternal.server.persist.ExeUpdateSql.execute(ExeUpdateSql.java:59)
at io.ebeaninternal.server.persist.DefaultPersistExecute.executeSqlUpdate(DefaultPersistExecute.java:95)
at io.ebeaninternal.server.core.PersistRequestUpdateSql.executeNow(PersistRequestUpdateSql.java:85)
at io.ebeaninternal.server.core.PersistRequest.executeStatement(PersistRequest.java:126)
at io.ebeaninternal.server.core.PersistRequestUpdateSql.executeOrQueue(PersistRequestUpdateSql.java:95)
at io.ebeaninternal.server.persist.DefaultPersister.executeOrQueue(DefaultPersister.java:121)
at io.ebeaninternal.server.persist.DefaultPersister.executeSqlUpdate(DefaultPersister.java:166)
at io.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:827)
at io.ebeaninternal.server.persist.DefaultPersister.deleteChildrenById(DefaultPersister.java:1123)
at io.ebeaninternal.server.persist.DefaultPersister.deleteManyDetails(DefaultPersister.java:1099)
at io.ebeaninternal.server.persist.DefaultPersister.deleteAssocMany(DefaultPersister.java:1065)
at io.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:891)
at io.ebeaninternal.server.persist.DefaultPersister.deleteRequest(DefaultPersister.java:617)
at io.ebeaninternal.server.persist.DefaultPersister.deleteRequest(DefaultPersister.java:597)
at io.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:589)
at io.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1978)
at io.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1969)
at ....InheritanceDeleteCascadeTest.test2(InheritanceDeleteCascadeTest.java:49)
...
Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_ROOT_BEAN_ELEMENTS_ROOT_BEAN_ID: PUBLIC.ROOT_BEAN_ELEMENTS FOREIGN KEY(ROOT_BEAN_ID) REFERENCES PUBLIC.ROOT_BEAN(ID) ('f31c05f1-ab35-45d9-9206-a28ae91eeb7f')"; SQL statement:
delete from root_bean where id in (?,?) [23503-193]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:426)
at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:443)
at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:318)
at org.h2.table.Table.fireConstraints(Table.java:967)
at org.h2.table.Table.fireAfterRow(Table.java:985)
at org.h2.command.dml.Delete.update(Delete.java:101)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:160)
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:146)
at io.ebean.datasource.pool.ExtendedPreparedStatement.executeUpdate(ExtendedPreparedStatement.java:148)
at io.ebeaninternal.server.persist.ExeUpdateSql.execute(ExeUpdateSql.java:50)
... 40 more
By coincidence I discovered a workaround: adding a @PreDelete hook in the RootBean:
@Entity
@Inheritance
public abstract class RootBean {
...
// added as workaround for the issue
@PreRemove
public void preRemove() {
}
}
I hope this workaround gives a clue for a solution.
Let me know if I can do anything to help.
I just discovered that the issue isn't tied to the use of @ElementCollection and @Embeddable in particular. With the setup as described above, but with the ComplexBean.element relationship expressed through @OneToMany (and ElementBean being an @Entity, not @Embeddable).
Sorry for the confusion.
Note that the workaround works due to BeanDescriptor.isDeleteByBulk() ... doesn't allow bulk delete due to the persistController that is added by RootBean.preDelete().