ebean icon indicating copy to clipboard operation
ebean copied to clipboard

Inheritance cascade of fields with bean inheritance

Open frensjan opened this issue 6 years ago • 2 comments

Deletes aren't cascaded to embedded beans referenced using @ElementCollection

Steps to reproduce

The (abstract) data model is:

  • A bean (RootBean) that is inherited by SimpleBean and ComplexBean
  • ComplexBean embeds instances of ElementBean
  • RootBeans are referenced through @OneToMany from ReferencingBean
@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.

frensjan avatar Oct 17 '19 07:10 frensjan

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.

frensjan avatar Oct 17 '19 08:10 frensjan

Note that the workaround works due to BeanDescriptor.isDeleteByBulk() ... doesn't allow bulk delete due to the persistController that is added by RootBean.preDelete().

rbygrave avatar Oct 18 '19 03:10 rbygrave