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!
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
.
Try to mark parent and child classes with @Audited annotation but for parent class add @Audited(targetAuditMode = NOT_AUDITED)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With