5 Replies Latest reply on Feb 3, 2012 8:48 AM by Adam Warski

    Having difficulty using Envers to audit a many-to-many relation

    Patrick Garner Newbie

      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. From reading the exception message, I suspect that I need to override auditing behavior so that Envers doesn't try to audit the embedded component.  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?

        • 1. Re: Having difficulty using Envers to audit a many-to-many relation
          Adam Warski Master

          Hmm, which version of Envers are you using?

           

          Normally you would use @NotAudited to not audit a field, but here it's a primary key.

           

          Adam

          • 2. Re: Having difficulty using Envers to audit a many-to-many relation
            Patrick Garner Newbie

            I'm using the version of envers that comes with jboss-as-7.1.0.CR1b, which appears to be the following:

             

            $JBOSS_HOME/modules/org/hibernate/envers/main/hibernate-envers-4.0.0.Final.jar

            • 3. Re: Having difficulty using Envers to audit a many-to-many relation
              Patrick Garner Newbie

              Perhaps I should not be trying to "not audit" at all?  I want to audit the association table.  I just tried @NotAudit on the embedded pk field as a desperate attempt to resolve the problem.  It doesn't even make sense to take that approach, really, but I couldn't see any other direction to go.

               

              Using my @AssociationOverrides, shouldn't envers recognize it and figure out how to audit user, user_role, and role tables by "navigating through" the embedded component, UserRolePk?

              • 4. Re: Having difficulty using Envers to audit a many-to-many relation
                Vyacheslav Sakhno Newbie

                As far as i see you declaring Long id in the super class of UserRole too.

                 

                public class UserRole extends Entity<UserRole>

                @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;
                ... // is Long id annotated as id here?
                }

                Then you trying to have both

                @EmbeddedId
                   
                public UserRolePk getPk() { return pk; }

                 

                and Long id in the code.

                 

                Here is working example from my project btw:

                 

                @Entity

                @Table(name = "PROPERTY_FILE_LINKS")

                @Audited

                public class PropertyFileLink implements Auditable {

                          @SuppressWarnings("serial")

                          @Embeddable

                          public static final class Pk implements Serializable {

                                    @Column(name="PROPERTY_ID")

                                    private long propertyId;

                                    @Column(name="FILE_ID")

                                    private long fileId;

                 

                 

                                    public Pk() {}

                 

                 

                                    public Pk(long propertyId, long fileId) {

                                              this.propertyId = propertyId;

                                              this.fileId = fileId;

                                    }

                 

                              public boolean equals(Object o) {

                                  if (this == o) return true;

                                  if (o == null || getClass() != o.getClass()) return false;

                          

                                  Pk that = (Pk) o;

                                  return (propertyId == that.propertyId && fileId == that.fileId);

                              }

                 

                 

                              public int hashCode() {

                                        return new Long(propertyId*127L+fileId).hashCode();

                              }

                 

                 

                                    public long getPropertyId() {

                                              return propertyId;

                                    }

                 

                 

                                    public void setPropertyId(long propertyId) {

                                              if(this.propertyId==0)

                                                        this.propertyId = propertyId;

                                    }

                 

                 

                                    public long getFileId() {

                                              return fileId;

                                    }

                 

                 

                                    public void setFileId(long fileId) {

                                              if(this.fileId==0)

                                                        this.fileId = fileId;

                                    }

                 

                                    public String toString() {

                                              return String.valueOf(propertyId) + "," + String.valueOf(fileId);

                                    }

                             

                          }

                 

                 

                          @Transient

                          private Long revisionId;

                 

                          public Long getRevisionId() {

                                    return revisionId;

                          }

                 

                 

                          public void setRevisionId(Long revisionId) {

                                    this.revisionId = revisionId;

                          }

                 

                          @EmbeddedId

                          private final Pk pk = new Pk();

                 

                          @ManyToOne(fetch=FetchType.LAZY,optional=false)

                          @JoinColumn(name="PROPERTY_ID",insertable=false,updatable=false)

                          private Property property;

                 

                          @OneToOne(optional=false,cascade={CascadeType.ALL},orphanRemoval = true)

                          @JoinColumn(name="FILE_ID",insertable=false,updatable=false)

                          @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)

                          private FileInfo fileInfo;

                 

                 

                          @OneToOne

                          @JoinColumn(name="FILE_TYPE_ID")

                          @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)

                          private FileType fileType;

                 

                          @ManyToOne(fetch=FetchType.LAZY)

                          @JoinColumn(name="ZONE_ID")

                          @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)

                          private PropertyZone propertyZone;

                 

                          @Column(name="ID_OLD")

                          private Long idOld;

                 

                          public Long getIdOld() {

                                    return idOld;

                          }

                 

                 

                          public void setIdOld(Long idOld) {

                                    this.idOld = idOld;

                          }

                 

                 

                          public Pk getPk() {

                                    return pk;

                          }

                 

                          public Property getProperty() {

                                    return property;

                          }

                 

                          public void setProperty(Property property) {

                                    if(property != null && pk.propertyId == 0) {

                                              this.property = property;

                                              pk.setPropertyId(property.getId());

                                              return;

                                    }

                                    //TODO: should be exception!

                          }

                 

                          public Long getPropertyId() {

                                    return pk.getPropertyId(); //boxed

                          }

                 

                          public FileInfo getFileInfo() {

                                    return fileInfo;

                          }

                          public void setFileInfo(FileInfo fileInfo) {

                                    if(fileInfo != null && pk.fileId==0) {

                                              this.fileInfo = fileInfo;

                                              pk.setFileId(fileInfo.getId());

                                              return;

                                    }

                                    //TODO: should be exception!

                          }

                 

                          public Long getFileId() {

                                    return pk.getFileId(); //boxed

                          }

                 

                 

                          public FileType getFileType() {

                                    return fileType;

                          }

                 

                 

                          public void setFileType(FileType fileType) {

                                    this.fileType = fileType;

                          }

                 

                          public Symbol getSystag() {

                                    return getFileType().getSystag();

                          }

                 

                 

                          public PropertyZone getPropertyZone() {

                                    return propertyZone;

                          }

                 

                 

                          public void setPropertyZone(PropertyZone propertyZone) {

                                    this.propertyZone = propertyZone;

                          }

                 

                }


                • 5. Re: Having difficulty using Envers to audit a many-to-many relation
                  Adam Warski Master

                  Does it work without @AssociationOverrides? I mean, m2m relations to entities with composite keys?

                   

                  Adam