1 2 Previous Next 20 Replies Latest reply on Jan 10, 2012 3:16 PM by lukasz.antoniak

    This criterion can only be used on a property that is a relation to another property.

    vyacheslav86

      I have org.hibernate.envers.exception.AuditException: This criterion can only be used on a property that is a relation to another property.

       

      in the next code in the last try-catch block:

       

      @Service

      public class EntityRevisionServiceImpl implements EntityRevisionService {

       

                @Resource

                private PersistenceManagerHibernate persistenceManagerHibernate;

       

                @Override

                public Object get(Class<?> clazz, Long id, Date date) {

                          Session entityManager = persistenceManagerHibernate.getHibernateSession();

                          AuditReader auditReader = AuditReaderFactory.get(entityManager);

                          AuditQueryCreator auditQueryCreator = auditReader.createQuery();

                          AuditQuery auditQuery = auditQueryCreator.forRevisionsOfEntity(clazz, false, true);

                          auditQuery = auditQuery.addProjection(AuditEntity.revisionNumber().min())

                          .add(

                                              AuditEntity.revisionProperty("timestamp").maximize()

                                              .add(AuditEntity.revisionProperty("timestamp").le(date))

                                              .add(AuditEntity.id().eq(id))

                                              )

                                              ;

                          List resultList = null;

                          try {

                                    resultList = auditQuery.getResultList();

                          } catch (Exception e) {

                                    e.printStackTrace();

                                    throw new RuntimeException(e);

                          }

                          if (resultList.size() == 0) {

                                    return null;

                          }

                          Object object = resultList.get(0);

                          if (object == null) {

                                    return null;

                          }

                          Long revisionNumber = (Long) object;

       

                          AuditQuery query = auditReader.createQuery().forEntitiesAtRevision(clazz, revisionNumber)

                                    .add(AuditEntity.id().eq(id));

                          try {

                                    Object object2 = query.getSingleResult();

                                    return object2;

                          } catch (Exception e) {

                                    throw new RuntimeException(e);

                          }

       

       

      The class i'm searching for is:

       

      @Entity

      @Audited(auditParents={Property.class})

      public class Atm extends Property {

       

      @Entity

      @Table(name = "PROPERTIES")

      @Inheritance(strategy=InheritanceType.SINGLE_TABLE)

      @DiscriminatorColumn(name="BASE_TYPE_ID", discriminatorType=DiscriminatorType.INTEGER)

      @Audited

      public class Property implements Auditable, Serializable {

          private static final long serialVersionUID = -7827695648259800469L;

       

          @Id

          @GeneratedValue

          @Column(name = "ID")

          private Long id;

       

       

      There is an entity with specified id in a database. There is a revision(it is found successfuly by first audit query;

       

      AuditQuery auditQuery = auditQueryCreator.forRevisionsOfEntity(clazz, false, true);

               auditQuery = auditQuery.addProjection(AuditEntity.revisionNumber().min())

               .add(

                         AuditEntity.revisionProperty("timestamp").maximize()

                         .add(AuditEntity.revisionProperty("timestamp").le(date))

                         .add(AuditEntity.id().eq(id))

                         )

                         ;

      There is a revision of entity with found revisionNumber and specifies id in a database but i get an error.

       

      For other entity without inheritance of audited entity the same query completes successfully:

       

      @Entity

      @Table(name = "CAPEX_CONTRACTS")

      @Audited

      public class CapexContract implements Auditable {

       

       

                @Id

                @NotNull

                @GeneratedValue

                @Column(name = "ID")

                private Long id;

        • 1. Re: This criterion can only be used on a property that is a relation to another property.
          vyacheslav86

          During debugging found that error occures in processing property: propertySafety

           

          @OneToOne(fetch = FetchType.LAZY, mappedBy = "property", cascade = { CascadeType.ALL }, orphanRemoval = true)

              private PropertySafety propertySafety;

           

          OneToOneNotOwningMapper

          ...

          value = versionsReader.createQuery().forEntitiesAtRevision(entityClass, owningEntityName, revision)

                              .add(AuditEntity.relatedId(owningReferencePropertyName).eq(primaryKey)).getSingleResult();

           

          EntitiesAtRevisionQuery

          ...

          public List list() {

          ...

          criterion.addToQuery(verCfg, entityName, qb, qb.getRootParameters());

           

          RelatedAuditExpression

          ...

          public void addToQuery(AuditConfiguration auditCfg, String entityName, QueryBuilder qb, Parameters parameters) {

          ...

          if (relatedEntity == null) {

                      throw new AuditException("This criterion can only be used on a property that is " +

                              "a relation to another property.");

                  } else {

                      relatedEntity.getIdMapper().addIdEqualsToQuery(parameters, id, null, equals);

                  }

           

           

          As far as i see class org.hibernate.envers.entities.mapper.relation.OneToOneNotOwningMapper has

           

          owningReferencePropertyName sety to null
          • 2. Re: This criterion can only be used on a property that is a relation to another property.
            vyacheslav86

            So does Envers can not process inverse @OneToOne relationship specified using mappedBy = "property"?

             

            null first appears at

             

            String owningReferencePropertyName = propertyValue.getReferencedPropertyName(); // mappedBy

             

            in

             

            void addOneToOneNotOwning(PropertyAuditingData propertyAuditingData, Value value,

                                          CompositeMapperBuilder mapper, String entityName) {

             

            at ToOneRelationMetadataGenerator

             

            Am i right that this field must contain mappedBy value for mapping

             

            @OneToOne(fetch = FetchType.LAZY, mappedBy = "property", cascade = { CascadeType.ALL }, orphanRemoval = true)

                private PropertySafety propertySafety;

             

            that is "property"?

            • 3. Re: This criterion can only be used on a property that is a relation to another property.
              vyacheslav86

              Here is the problem. The mapping is incorrect. The owning side of the relationship doesn't have foreign key, but is referenced by foreign key.

               

              Maybe because of that Enver do not process this entry well.

              • 4. Re: This criterion can only be used on a property that is a relation to another property.
                adamw

                Do I understand correctly, that what doesn't work are criterions where you want to specify the id of a related entity, which is mapped using @OneToOne(mappedBy="...")?

                 

                If so, could you create a test case for that? Take a look at the Envers test suite, I think it should be pretty easy to see how to create a new one.

                 

                Adam

                • 5. Re: This criterion can only be used on a property that is a relation to another property.
                  vyacheslav86

                  http://www.google.ru/search?sourceid=chrome&ie=UTF-8&q=Envers+test+suite gives not much.

                   

                  No. Doesn't work any criterion-quieries where entity has @OneToOne(mappedBy="..."). if mappedBy present then this is "not owning" side of a relationship. But owningReferencePropertyName (as far as i see created in hibernate mapping parsing) is null and then the relation is processed by envers in

                   

                  propertyName == null

                   

                  at EntitiesConfigurations::getRelationDescription

                  at CriteriaTools::getRelatedEntity

                  at RelatedAuditExpression::addToQuery

                  at EntitiesAtRevisionQuery::

                  at AbstractAuditQuery::list

                  at AbstractAuditQuery::getSingleResult

                  at OneToOneNotOwningMapper::mapToEntityFromMap

                  at MultiPropertyMapper::mapToEntityFromMap

                  at SubclassPropertyMapper::mapToEntityFromMap

                  at EntityInstantiator::createInstanceFromVersionsEntity

                  at EntitiesAtRevisionQuery::list

                  at AuditQuery::query.getSingleResult()


                  (by hand from debugger, can't get envers compiled yet to edit code)

                   

                  and even more. I used another mapping for @OneToOne relationship to exchange owning and owned side of relationship:

                   

                  @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")

                  @JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })

                  @Entity

                  @Table(name = "PROPERTIES")

                  @Inheritance(strategy=InheritanceType.SINGLE_TABLE)

                  @DiscriminatorColumn(name="BASE_TYPE_ID", discriminatorType=DiscriminatorType.INTEGER)

                  @Audited

                  public class Property implements Auditable, Serializable {

                   

                  ///@OneToOne(fetch = FetchType.LAZY, mappedBy = "property", cascade = { CascadeType.ALL }, orphanRemoval = true)

                      @PrimaryKeyJoinColumn(name="ID", referencedColumnName="ID")

                      @OneToOne(fetch=FetchType.LAZY, cascade = { CascadeType.ALL }, orphanRemoval = true)

                      private PropertySafety propertySafety;

                   

                   

                      @JsonIgnore

                      public PropertySafety getPropertySafety() {

                          return propertySafety;

                      }

                   

                   

                  @JsonIgnoreProperties({"hibernateLazyInitializer","handler"})

                  @Entity

                  @Table(name = "PROPERTY_SAFETY")

                  @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")

                  @Audited

                  public class PropertySafety implements Auditable, Serializable  {

                   

                  //@OneToOne(fetch=FetchType.LAZY)

                      //@JoinColumn(name="ID", updatable=false, insertable=false)

                      @OneToOne(fetch = FetchType.LAZY, mappedBy = "propertySafety")

                      private Property property;

                   

                  but for AuditQuery query = getAuditQueryCreator().forEntitiesAtRevision(clazz, revisionId);

                   

                  owningReferencePropertyName is still null.

                   


                  • 6. Re: This criterion can only be used on a property that is a relation to another property.
                    vyacheslav86

                    I think this behaviour is closely related to

                    https://hibernate.onjira.com/browse/HHH-6825

                     

                    // HACK for @OneToOne with @PrimaryKeyJoinColumn => null

                    this.owningReferencePropertyName = owningReferencePropertyName == null ? "id" : owningReferencePropertyName;

                     

                    btw can you commit patches to 3.6.8 version because get hibernate 4 working with spring is nearly impossible?

                    • 7. Re: This criterion can only be used on a property that is a relation to another property.
                      vyacheslav86

                      Strange. The thing is i'm quering for Property.class which has

                      @PrimaryKeyJoinColumn(name="ID", referencedColumnName="ID")

                          @OneToOne(fetch=FetchType.LAZY, cascade = { CascadeType.ALL }, orphanRemoval = true)

                          private PropertySafety propertySafety;

                       

                       

                       

                      but query processing fails in processing PropertySafety.class

                       

                      which has

                      @OneToOne(fetch = FetchType.LAZY, mappedBy = "propertySafety")

                          private Property property;

                       

                      i can hack

                       

                      // HACK for @OneToOne with @PrimaryKeyJoinColumn => null

                      this.owningReferencePropertyName = owningReferencePropertyName == null ? "id" : owningReferencePropertyName;

                       

                      to pass "property" to owningReferencePropertyName

                       

                      but it says that not-owning relation is unsupported.

                       

                      CriteriaTools::


                      public static RelationDescription getRelatedEntity(AuditConfiguration verCfg, String entityName,

                                                                             String propertyName) throws AuditException {

                              RelationDescription relationDesc = verCfg.getEntCfg().getRelationDescription(entityName, propertyName);

                       

                       

                              if (relationDesc == null) {

                                  return null;

                              }

                       

                       

                              if (relationDesc.getRelationType() == RelationType.TO_ONE) {

                                  return relationDesc;

                              }

                       

                       

                              throw new AuditException("This type of relation (" + entityName + "." + propertyName +

                                      ") isn't supported and can't be used in queries.");

                          }

                       

                      last exception.

                       

                      i'm looking for Property.class revision. Is it processes PropertySafety attribute of class relationship to find revision of PropertySafety corresponding to Property revision?

                       

                      Is it processes reverse, not owning one-to-one relationship to Property.class with information that the entity is processed as one-to-one relation from Property.class to PropertySafety.class? Or is it just a method to break up cycle dependencies?

                      • 8. Re: This criterion can only be used on a property that is a relation to another property.
                        vyacheslav86

                        "This type of relation (ru.csbi.registry.domain.property.PropertySafety.property) isn't supported and can't be used in queries.

                         

                        when i'm quering for Property.class.

                        • 9. Re: This criterion can only be used on a property that is a relation to another property.
                          vyacheslav86

                          Here is the error in processing imho.

                           

                          AuditMetadataGenerator::addValue process each OneToOne relation as not-owning side(addOneToOneNotOwning method if i correctly understand that NotOwning is realted to OneToOne word) though it maybe owning side of relationship, for example my case where @PrimaryKeyJoinColumn is used on the owning side of relationship.

                           

                           

                              void addValue(Element parent, Value value, CompositeMapperBuilder currentMapper, String entityName,

                                            EntityXmlMappingData xmlMappingData, PropertyAuditingData propertyAuditingData,

                                            boolean insertable, boolean firstPass) {

                                  Type type = value.getType();

                                  if (value.toString().toUpperCase().contains("CONTRACTOR_ID")) {

                                            toString();

                                  }

                                  if (value.toString().toUpperCase().contains("CONTACT_ID")) {

                                            toString();

                                  }

                                  if (type instanceof OneToOneType) {

                                            toString();

                                  }

                                  // only first pass

                                  if (firstPass) {

                                      if (basicMetadataGenerator.addBasic(parent, propertyAuditingData, value, currentMapper,

                                              insertable, false)) {

                                          // The property was mapped by the basic generator.

                                          return;

                                      }

                                  }

                           

                           

                                              if (type instanceof ComponentType) {

                                                        // both passes

                                                        componentMetadataGenerator.addComponent(parent, propertyAuditingData, value, currentMapper,

                                                                            entityName, xmlMappingData, firstPass);

                                              } else if (type instanceof ManyToOneType) {

                                      // only second pass

                                      if (!firstPass) {

                                          toOneRelationMetadataGenerator.addToOne(parent, propertyAuditingData, value, currentMapper,

                                                  entityName, insertable);

                                      }

                                  } else if (type instanceof OneToOneType) {

                                      // only second pass

                                      if (!firstPass) {

                          // here is every OneToOne relation processed as not-owning

                                          toOneRelationMetadataGenerator.addOneToOneNotOwning(propertyAuditingData, value,

                                                  currentMapper, entityName);

                                      }

                                  } else if (type instanceof CollectionType) {

                                      // only second pass

                                      if (!firstPass) {

                                          CollectionMetadataGenerator collectionMetadataGenerator = new CollectionMetadataGenerator(this,

                                                  (Collection) value, currentMapper, entityName, xmlMappingData,

                                                                                      propertyAuditingData);

                                          collectionMetadataGenerator.addCollection();

                                      }

                                  } else {

                                      if (firstPass) {

                                          // If we got here in the first pass, it means the basic mapper didn't map it, and none of the

                                          // above branches either.

                                          throwUnsupportedTypeException(type, entityName, propertyAuditingData.getName());

                                      }

                                  }

                              }

                           

                          The fact that hibernate processes/maps internally owning side of OneToOne relatioship as ManyToOne relationship(at least that envers code gets such mappings here) misleads because when @PrimaryKeyJoinColumn is used and mappedBy value is not used we still have owning side of relationship.

                           

                          We can have owned side of relationship with @PrimaryKeyJoinColumn too:

                          for example here http://stackoverflow.com/questions/787698/jpa-hibernate-one-to-one-relationship

                           

                          Owning side:

                          @Entity
                          public class Person {
                             
                          @Id
                             
                          private int id;

                             
                          @OneToOne
                             
                          @PrimaryKeyJoinColumn
                             
                          private OtherInfo otherInfo;

                              rest of attributes
                          ...
                          }

                           

                          owned site

                           

                          @Entity
                          public class OtherInfo {
                             
                          @Id @GeneratedValue(generator = "customForeignGenerator")
                             
                          @org.hibernate.annotations.GenericGenerator(
                                  name
                          = "customForeignGenerator",
                                  strategy
                          = "foreign",
                                  parameters
                          = @Parameter(name = "property", value = "person")
                             
                          )
                             
                          private Long id;

                             
                          @OneToOne(mappedBy="otherInfo")
                             
                          @PrimaryKeyJoinColumn
                             
                          public Person person;

                              rest of attributes
                          ...
                          }

                          • 10. Re: This criterion can only be used on a property that is a relation to another property.
                            vyacheslav86

                            Hack which works:

                             

                            ToOneRelationMetadataGenerator::addToOneOwning

                             

                            public void addToOneOwning(Element parent,

                                                          PropertyAuditingData propertyAuditingData, Value value,

                                                          CompositeMapperBuilder currentMapper, String entityName,

                                                          boolean insertable) {

                             

                             

                                    OneToOne propertyValue = (OneToOne) value;

                                    propertyValue.addColumn(new Column("id"));

                                    addToOne(parent, propertyAuditingData, value, currentMapper, entityName, insertable);

                                   

                                                @SuppressWarnings("rawtypes")

                                                Iterator iterator = propertyValue.getColumnIterator();

                                                if (iterator.hasNext()) {

                                                          iterator.next();

                                                          iterator.remove();

                                                }

                                      }

                             

                            AuditMetadataGenerator::addValue

                            ...

                            } else if (type instanceof OneToOneType) {

                                        // only second pass

                                        if (!firstPass) {

                                                  OneToOne propertyValue = (OneToOne) value;

                                            String owningReferencePropertyName = propertyValue.getReferencedPropertyName(); // mappedBy

                                            if (owningReferencePropertyName == null) {

                                                      toOneRelationMetadataGenerator.addToOneOwning(parent, propertyAuditingData, value, currentMapper,

                                                        entityName, insertable);

                                            } else {

                                                      toOneRelationMetadataGenerator.addOneToOneNotOwning(propertyAuditingData, value,

                                                    currentMapper, entityName);

                                            }

                                        }

                                    }

                             

                            ...

                             

                            Don't really know what is done where.

                            • 11. Re: This criterion can only be used on a property that is a relation to another property.
                              adamw

                              It's really hard to read code as formatted here

                              Anyway, to answer some questions:

                              * as far as I know there won't be any more releases of the 3.6 branch

                              * Envers testsuite is simply here: https://github.com/hibernate/hibernate-core/tree/master/hibernate-envers. Just take a look at the src/test/java directory

                              * https://hibernate.onjira.com/browse/HHH-6825 indeed seems linked. Did you manage to solve the problem, in the end?
                              * OneToOneNotOwning is the @OneToOne(mappedBy) mapping. A simple @OneToOne is viewed by hibernate as a @ManyToOne (wchih behaves similarly).

                               

                              Adam

                              • 12. Re: This criterion can only be used on a property that is a relation to another property.
                                vyacheslav86

                                The problem is not yet solved.

                                OneToOneNotOwning is the @OneToOne(mappedBy) has owningReferencePropertyName null too. That seems to be error. I commented out that reverse dependency to solve it later.

                                 

                                A simple @OneToOne is viewed by hibernate as a @ManyToOne (wchih behaves similarly) until you use @PrimaryKeyJoinColumn or @JoinColumn(name="ID) (same as PrimaryKeyJoinColumn in processing code). This is the only case when hibernate processes @OneToOne owning side as OneToOneType mapping class. This leads to an error in envers @OneToOne processing which solvation is  described in my previous message in this thread. At least i get some data from db. Not sure whether it is correct.

                                • 13. Re: This criterion can only be used on a property that is a relation to another property.
                                  vyacheslav86

                                  My hack is not correctly working though.

                                  • 14. Re: This criterion can only be used on a property that is a relation to another property.
                                    vyacheslav86

                                    Ok. I've checked out hibernate with envers and created simple test case cloning org.hibernate.envers.test.integration.onetoone.bidirectional and adding @PrimaryKeyJoinColumn annotation to owning side of relatioship.

                                     

                                     

                                    Failed tests:
                                      testHistoryOfEdId2(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testRevisionsCounts(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testHistoryOfEdId1(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testHistoryOfEdId1(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                      testHistoryOfEdId2(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                      testRevisionsCounts(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                      testHistoryOfEdId2(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testRevisionsCounts(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testHistoryOfEdId1(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOne2TestCase)
                                      testHistoryOfEdId1(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                      testHistoryOfEdId2(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                      testRevisionsCounts(org.hibernate.envers.test.integration.onetoone.primarykey.PrimaryKeyJoinColumnOneToOneTestCase)
                                    The only change in org.hibernate.envers.test.integration.onetoone.bidirectional code:
                                    /**
                                    * @author Vyacheslav Sakhno
                                    */
                                    @Entity
                                    public class PriRefIngEntity {
                                        @Id
                                        private Integer id;
                                        @Audited
                                        private String data;
                                        @Audited
                                        @OneToOne
                                        @PrimaryKeyJoinColumn //here
                                        private PriRefEdEntity reference;
                                    Don't forget to add tests package to testcase list.
                                    Hope this will be fixed because i cannot fix it myself in a short period of time.
                                    I've also don't understand the idea of org.hibernate.envers.test.integration.onetoone.bidirectional because @JoinColumn annotation is not present at all and this case is not covered in Hibernate annotations documentation.
                                    My case is covered in documentation directly as far as i see, but is logically following these examples:
                                    @Entity
                                    public class Body {
                                        @Id
                                        public Long getId() { return id; }
                                        @OneToOne(cascade = CascadeType.ALL)
                                        @PrimaryKeyJoinColumn
                                        public Heart getHeart() {
                                            return heart;
                                        }
                                        ...
                                    }           
                                    @Entity
                                    public class Heart {
                                        @Id
                                        public Long getId() { ...}
                                    }           
                                    @Entity
                                    public class Customer implements Serializable {
                                        @OneToOne(cascade = CascadeType.ALL)
                                        @JoinColumn(name="passport_fk")
                                        public Passport getPassport() {
                                            ...
                                        }
                                    @Entity
                                    public class Passport implements Serializable {
                                        @OneToOne(mappedBy = "passport")
                                        public Customer getOwner() {
                                        ...
                                    }           
                                    1 2 Previous Next