I using Hibernate Envers for auditing and have an issue with composite primary keys. I have many to many entities with composite primary key based on the relating properties. The construct is like following:
@Entity
@Audited
@Table(indexes = { @Index(columnList = "person_id"),
@Index(columnList = "document_id") })
public class PersonDocument implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private Document document;
@Id
@ManyToOne(optional = false, fetch = FetchType.EAGER)
private Person person;
The relation is not bi-directional annotated. The primary key is correct used in the audited tables, like the docu of envers describes it.
But now I wan't all revisions that relates to person. With following:
final AuditQuery query = AuditReaderFactory.get(entityManager)
.createQuery().forRevisionsOfEntity(PersonDocument.class, false, true)
.add(AuditEntity.relatedId("person").eq("12"))
.addOrder(AuditEntity.revisionNumber().desc());
Then I get following error:
This criterion can only be used on a property that is a relation to another property.
If I use a non composite primary key then it runs without problems, but with I get the error. Has anyone an Idea? The migration of the data from composite primary key to an extra primary key for many to many entity is not so easy.
I use Hibernate version 4.3.11
Best regards
The problem here is that Envers basically doesn't register relations for @Id
annotated types, which is precisely why you run into this error.
Unfortunately, Hibernate 4.3 is no longer being maintained so any bug fix we do would be applicable for Hibernate 5.x at this point, most likely only being 5.2.x.
That said, there is a workaround you can use to avoid having to change your composite-id setup. The idea is that you create a property that shadows the composite-id key values and you use the shadowed property for these queries.
@Entity
@Audited
public class PersonDocument implements Serializable {
@Id
@ManyToOne(optional = false)
private Document document; // lets assume this maps to document_id
@Id
@ManyToOne(optional = false)
private Person person; // lets assume this maps to person_id
// we'll shadow those properties now
@Column(name = "document_id", nullable = false, insertable = false, updatable = false)
private Integer documentId;
@Column(name = "person_id", nullable = false, insertable = false, updatable = false)
private Integer personId;
}
Now rather than using the relatedId
approach, we can query based on simple properties:
reader.createQuery().forRevisionsOfEntity( PersonDocument.class, false, true )
.add( AuditEntity.property( "personId" ).eq( 42 ) )
.addOrder( AuditEntity.revisionNumber().desc() );
Obviously this is less than ideal, but you can certainly use things like @PostUpdate
and @PostPersist
in order to keep the various shadowed properties aligned with their object counter-parts.