Using a non-mapped superclass breaks my entity listener

Sorry about the somewhat vague title; this is easier to illustrate in code. I had a BeforeInsertEntityListener which creates a new access group when a specific entity type is created, which worked just fine until I changed the entity type to extend a non-mapped superclass.

Firstly, here’s the code which works:

(All of my entites were created using Studio, so there’s no custom code involved except in the listener.)

ConcreteEntity

@Listeners("app_ConcreteEntityListener")
@Table(name = "APP_CONCRETE_ENTITY")
@Entity(name = "app$ConcreteEntity")
public class ConcreteEntity extends BaseLongIdEntity {
    // lots of POD properties
}

EntityAccessGroup

@Entity(name = "app$EntityAccessGroup")
public class EntityAccessGroup extends Group {
    @OnDeleteInverse(DeletePolicy.CASCADE)
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CONCRETE_ENTITY_ID")
    private ConcreteEntity concreteEntity;

    // getters, setters
}

ConcreteEntityListener

@Component("app_ConcreteEntityListener")
public class ConcreteEntityListener implements BeforeInsertEntityListener<ConcreteEntity> {
    @Inject private Metadata metadata;

    @Override
    public void onBeforeInsert(ConcreteEntity entity, EntityManager entityManager) {
        Group parentGroup = entityManager
              .createQuery("SELECT g FROM sec$Group g WHERE g.name = :name", Group.class)
              .setParameter("name", "parentAccessGroupName")
              .getFirstResult();

        if (parentGroup == null) {
            throw new IllegalStateException("Parent access group missing!");
        }

        EntityAccessGroup accessGroup = metadata.create(EntityAccessGroup.class);
        accessGroup.setParent(parentGroup);
        accessGroup.setConcreteEntity(entity);
        accessGroup.setName(generateAccessGroupName(entity)); // returns a string based on entity.getName()
        entityManager.persist(accessGroup);

        SessionAttribute attr = metadata.create(SessionAttribute.class);
        attr.setGroup(accessGroup);
        attr.setName("concreteEntityId");
        attr.setDatatype("long");
        attr.setStringValue(entity.getId().toString());
        entityManager.persist(attr);
    }
}

So when I create a new ConcreteEntity, I also get a new EntityAccessGroup set up which puts the ConcreteEntity ID into the session.

However, if I introduce a superclass to pull out some of the implementation of ConcreteEntity, things no longer work correctly. For example, if I add:

MyBaseEntity

@DiscriminatorColumn(name = "DTYPE", discriminatorType = DiscriminatorType.STRING)
@Inheritance(strategy = InheritanceType.JOINED)
@Table(name = "APP_MY_BASE_ENTITY")
@Entity(name = "app$MyBaseEntity")
public class MyBaseEntity extends BaseLongIdEntity implements SoftDelete, Updatable, Creatable {
    // some common stuff
}

Then change ConcreteEntity thus:

@PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
@Listeners("app_ConcreteEntityListener")
@Table(name = "APP_CONCRETE_ENTITY")
@Entity(name = "app$ConcreteEntity")
public class ConcreteEntity extends MyBaseEntity {
    // Some concrete class-specific stuff
}

Now I get the following SQLException when I try to insert a new ConcreteEntity (caused by the underlying INSERT INTO SEC_GROUP … query):

SQLException: The INSERT statement conflicted with the FOREIGN KEY constraint “FK_SEC_GROUP_CONCRETE_ENTITY”. The conflict occurred in database “mydbname”, table “dbo.AAP_CONCRETE_ENTITY”, column ‘ID’.

I’ve found a solution: If I add a call to entityManager.flush(); before I create the EntityAccessGroup instance, it works.

I’m still curious whether this is the “correct” way of going about things, or whether there are other possible solutions.

Hi David,

Thank you for reporting the problem.
The cause of the problem is that EclipseLink ORM doesn’t order inserts properly for this case: it first inserts MyBaseEntity, then EntityAccessGroup, which has a reference to ConcreteEntity that is not inserted yet.

A similar problem is described in Cascade Insert problem - CUBA.Platform

Workaround: create the reference to MyBaseEntity in EntityAccessGroup.

We have created a YouTrack issue, see the link on the right.

1 Like

:ticket: See the following issue in our bug tracker:

https://youtrack.cuba-platform.com/issue/PL-8135