Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Envers: @Audited on a subclass

I have a classic inheritance persistence with entities Parent and Child, where Child extends Parent. Class Parent is abstract, while Child is not.

I want to audit Child. This entity is under my control, while Parent is not. Besides, it has many other subclasses which need not be audited. The inheritance strategy on the whole hierarchy is JOINED.

So I have annotated Child with @Audited and additionally with @AuditOverride(forClass = Parent.class).

What I get is this error:

"org.hibernate.MappingException: Entity 'Child' is audited, but its superclass: 'Parent' is not."

By the way, I'm using envers 4.0.1.Final version.

Does anyone know how can I achieve this? I've tried removing @Audited in Child class, removing @AuditOverride, using deprecated auditParents in @Audited annotation, but nothing seems to work.

This is the Parent entity:

@Entity
@Table(name = "parent")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public class Parent {
    public Parent() {
        super();
    }

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "base_id", unique = true, nullable = false)
    private Integer baseId;

    @Column(name = "base_field")
    private String baseField;

    @Column(name = "type")
    private String type;

    // getters and setters
}

And this is my Child entity:

@Entity
@Table(name = "child")
@DiscriminatorValue("CHILD")
@Audited
@AuditOverride(forClass = Parent.class)
public class Child extends Parent {
    public Child() {
        super();
    }

    @Column(name = "child_field")
    private String childField;

    // getters and setters
}

This are the entities:

CREATE  TABLE `REVINFO` (
  `REV` BIGINT NOT NULL AUTO_INCREMENT,
  `REVTSTMP` BIGINT NULL ,
  PRIMARY KEY (`REV`)
);

CREATE TABLE `parent` (
  `base_id` int(11) NOT NULL AUTO_INCREMENT,
  `base_field` varchar(45) DEFAULT NULL,
  `type` varchar(45) NOT NULL,
  PRIMARY KEY (`base_id`)
);

CREATE TABLE `child` (
  `base_id` int(11) NOT NULL AUTO_INCREMENT,
  `child_field` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`base_id`)
);

CREATE TABLE `child_AUD` (
  `base_id` int(11) NOT NULL,
  `REV` BIGINT NOT NULL,
  `REVTYPE` tinyint(2) DEFAULT NULL,
  `child_field` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`base_id`,`REV`)
);

Here is a test case:

public class EnversInheritanceTest extends AbstractJUnit4SpringContextTests {

    @Inject
    private EntityManagerFactory entityManagerFactory;

    private EntityManager entityManager;

    @Test
    public void inheritanceTest() {

        this.entityManager = this.entityManagerFactory.createEntityManager();

        Child child = this.createChild();
        this.saveChild(child);

        this.modifyChild(child);
        this.saveChild(child);

        Assert.assertNotNull(child.getBaseId());
        Assert.assertNotNull(this.getOriginalRevision(child.getBaseId()));

        Child original = this.getOriginalChild(child.getBaseId());

        Assert.assertNotNull(original);
        Assert.assertEquals("child", original.getChildField());

    }

    private Child createChild() {
        Child child = new Child();
        child.setBaseField("base");
        child.setChildField("child");
        child.setType("CHILD");
        return child;
    }

    private void saveChild(Child child) {
        this.entityManager.getTransaction().begin();
        this.entityManager.persist(child);
        // We need to commit in order to trigger Envers magic
        this.entityManager.getTransaction().commit();
    }

    private void modifyChild(Child child) {
        child.setBaseField("foo");
        child.setChildField("bar");
    }

    public Child getOriginalChild(Serializable id) {
        Object queryResult = this.getAuditReader().createQuery()
                .forEntitiesAtRevision(Child.class, this.getOriginalRevision(id))
                .add(AuditEntity.id().eq(id))
                .getSingleResult();
        return (Child) queryResult;
    }

    private Number getOriginalRevision(Serializable id) {
        AuditProjection minRevNumberAuditProjection = AuditEntity.revisionNumber().min();
        Number revision = (Number) this.getAuditReader().createQuery()
                .forRevisionsOfEntity(Child.class, false, false)
                .add(AuditEntity.id().eq(id))
                .addProjection(minRevNumberAuditProjection)
                .getSingleResult();

        return revision;
    }

    private AuditReader getAuditReader() {
        return AuditReaderFactory.get(this.entityManager);
    }

}

Thank you in advance!

like image 327
Jose Avatar asked Dec 15 '15 19:12

Jose


2 Answers

A very good question. There was the same discussion on the jboss developers forum in 2013 year. And the answer was from the founder and project lead of Hibernate Enver:

You would have to get the superclass audited somehow. Currently there's no other way to specify such metadata except for annotations.

In the same discussion tree, according to the fact that a parent class should also be annotated, there was suggested to annotate them in Runtime. But this decision seems to be ugly and is not suitable in your case: you can annotate the parent class manually.

As a workaround, if you don't want the parent class being audited, you can try to create a base abstract MappedSuperClass which essentially will be the same as Parent, while Parent will be just its descendant, and then try to put @AuditOverride again for the Child class. It is possible that it will "skip" audit for Parent class and do it for Child.

like image 191
Andremoniy Avatar answered Oct 23 '22 23:10

Andremoniy


Try to mark parent and child classes with @Audited annotation but for parent class add @Audited(targetAuditMode = NOT_AUDITED)

like image 27
LvDevR1 Avatar answered Oct 23 '22 23:10

LvDevR1