Jose Jose - 1 month ago 10
Java Question

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);
}

}


And finally here's a potato:

Potato image

Thank you in advance!

Answer

Very good question. There was same discussion on 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 same discussion tree according to the fact that parent class should be also annotated there was suggested annotate them in runtime. But this decision seems to be ugly and is not suitable in your case: you can annotate parent class manually.

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

Comments