I'm wanting to use Envers to audit a many-to-many relation with an embedded component but I'm having trouble with a MappingException saying the the ComponentType is not supported. This is relevant portion of the stack trace:
Caused by: org.hibernate.MappingException: Type not supported: org.hibernate.type.ComponentType
at org.hibernate.envers.configuration.metadata.IdMetadataGenerator.addIdProperties(IdMetadataGenerator.java:74)
at org.hibernate.envers.configuration.metadata.IdMetadataGenerator.addId(IdMetadataGenerator.java:105)
at org.hibernate.envers.configuration.metadata.AuditMetadataGenerator.generateFirstPass(AuditMetadataGenerator.java:413)
at org.hibernate.envers.configuration.EntitiesConfigurator.configure(EntitiesConfigurator.java:101)
at org.hibernate.envers.configuration.AuditConfiguration.<init>(AuditConfiguration.java:103)
at org.hibernate.envers.configuration.AuditConfiguration.getFor(AuditConfiguration.java:135)
at org.hibernate.envers.event.EnversIntegrator.integrate(EnversIntegrator.java:63)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:295)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.ejb.EntityManagerFactoryImpl.<init>(EntityManagerFactoryImpl.java:76)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:905)
Reading the Envers reference, it appears that Envers can handle what I'm trying to do. According to the reference:
If you'd like to override auditing behaviour of some fields/properties inherited from @Mappedsuperclass or in an embedded component, you can apply the @AuditOverride(s) annotation on the subtype or usage site of the component.
Here's my association entity. You can see where I tried to use @AuditOverride at the class level to prevent auditing the embedded component. I also tried using the annotation on the field itself. Neither made a difference.
@Audited
// @AuditOverride(name = "pk", isAudited = false) <===== Didn't help
@Table(name = "user_role")
@javax.persistence.Entity
@AssociationOverrides
(
{
@AssociationOverride
(name = "pk.user", joinColumns = @JoinColumn(name = "id")),
@AssociationOverride
(name = "pk.role", joinColumns = @JoinColumn(name = "id"))
}
)
public class UserRole extends Entity<UserRole>
{
private static final long serialVersionUID = 1L;
private Date expirationDate;
private UserRolePk pk = new UserRolePk();
public UserRole() {}
// @AuditOverride(name = "pk", isAudited = false) <== Didn't help
@EmbeddedId
public UserRolePk getPk() { return pk; }
@Transient
public User getUser() { return getPk().getUser(); }
@Transient
public Role getRole() { return getPk().getRole(); }
...
}
Here's the user entity:
@Audited
@Table(name = "applicationuser")
@javax.persistence.Entity
public class User extends Entity<User>
{
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
private String email;
private Set<UserRole> userRoles = new HashSet<UserRole>(0);
@OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL,
mappedBy = "pk.user", orphanRemoval = true)
public Set<UserRole> getUserRoles() { return userRoles; }
...
}
Here's the role entity:
@Audited
@Table(name = "role")
@javax.persistence.Entity
public class Role extends Entity<Role>
{
private static final long serialVersionUID = 1L;
private String name;
private String label;
private Set<UserRole> userRoles = new HashSet<UserRole>(0);
@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.role",
cascade=CascadeType.ALL, orphanRemoval = true)
public Set<UserRole> getUserRoles() { return userRoles; }
...
}
Here's the embedded component:
@Embeddable
public class UserRolePk implements Serializable
{
private static final long serialVersionUID = 1L;
private User user;
private Role role;
@ManyToOne
public User getUser() { return user; }
@ManyToOne
public Role getRole() { return role; }
...
}
And finally, here is my base entity, for completeness:
@MappedSuperclass()
public abstract class Entity<X extends Entity<X>>
implements Comparable<X>, Serializable
{
private static final long serialVersionUID = 1L;
private Long id;
private Timestamp timestamp;
...
}
I've read the Envers reference and perused the forum, but the information seems pretty sparse. Any ideas or pointers on this?
I'm using Envers 4.1.3.Final and it definitely still has this bug: for relationships to @Embedded components it logs the audit record correctly in the database, but the query API (
forRevisionsOfEntity
, in my case) simply doesn't see it. It would return a revision (MOD) for the changed object, but when you look in the actual entity property that holds the reference to the embedded object, it is always null, for all revisions.I ended up writing a workaround to query the changed values with native SQL (for the affected properties), since I didn't feel like changing my mapping just to appease a buggy plugin.
I resolved this by discarding the embeddable component, UserRolePk, and just going with @JoinColumn e.g.