Search code examples
javaspring-boothibernatehibernate-enversspring-data-envers

Hibernate Envers how to get the log history properly?


i create a table audit log using Hibernate Envers, and i use Spring Data Envers as my library, when i save/update/delete it successfully saved the log in my autid_log table, but when i want to retrive the log data, i got infinite loop of error, how can i do this properly? here is my code :

Here is my controller :

@GetMapping("/getPartnerRelationshipLog/{partnerId}")
public ResponseEntity<?> getPartnerRelationshipLog(@PathVariable Long partnerId) {
    // Long id = partner.getId();

    Revisions<Integer,Partner> history = partnerService.findRelationLog(partnerId);
    return ResponseEntity.ok(history);
 
}

here is my Partner.java model : package com.example.envers.auditing.Model;

@Data
@Entity
@Audited
@EntityListeners(AuditingEntityListener.class)
@Table(name = "msPartner")
public class Partner {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    public String partnerCode;

    public String partnerName;

    @CreatedDate
    private Date createDate;
    @LastModifiedDate
    private Date lastModifiedDate;
    @CreatedBy
    private String createdBy;
    @LastModifiedBy
    private String modifiedBy;

    @OneToMany(mappedBy = "partner", cascade = CascadeType.ALL)
    public List<PartnerShipment> partnerShipment;

}

and here is my PartnerShipment.java :

@Data
@Entity
@Audited
@EntityListeners(AuditingEntityListener.class)
@Table(name = "msPartnerShipment")
public class PartnerShipment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long id;

    public String partnerShipmentCode;

    public String partnerShipmentAddress;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "partnerId")
    // @NotAudited
    public Partner partner;

    @CreatedDate
    private Date createDate;
    @LastModifiedDate
    private Date lastModifiedDate;
    @CreatedBy
    private String createdBy;
    @LastModifiedBy
    private String modifiedBy;

}

and here is my service :

public Revisions<Integer,Partner> findRelationLog(Long id) {
    Revisions<Integer,Partner> partner = partnerRepository.findRevisions(id);

    return partner;
}

here is my Repository :

@Repository
public interface PartnerRepository extends RevisionRepository<Partner, Long, Integer>, JpaRepository<Partner, Long > {
    
}

and here is my Application.java

@SpringBootApplication
@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
@EnableJpaAuditing
public class AuditingApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuditingApplication.class, args);
    }

}

when i get the data with id = 1, i got something like infinite error of loop, start with java.lang.StackOverflowError: null, what i see in my terminal only this :

java.lang.StackOverflowError: null
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]
    at com.example.envers.auditing.Model.Partner.hashCode(Partner.java:31) ~[classes/:na]
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]
    at com.example.envers.auditing.Model.Partner.hashCode(Partner.java:31) ~[classes/:na]
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]
    at com.example.envers.auditing.Model.Partner.hashCode(Partner.java:31) ~[classes/:na]
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]
    at com.example.envers.auditing.Model.Partner.hashCode(Partner.java:31) ~[classes/:na]
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]
    at com.example.envers.auditing.Model.Partner.hashCode(Partner.java:31) ~[classes/:na]
    at com.example.envers.auditing.Model.PartnerShipment.hashCode(PartnerShipment.java:33) ~[classes/:na]
    at java.util.AbstractList.hashCode(AbstractList.java:541) ~[na:1.8.0_241]
    at org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.CollectionProxy.hashCode(CollectionProxy.java:131) ~[hibernate-envers-5.4.17.Final.jar:5.4.17.Final]

what did i miss to avoid this error?


Solution

  • Add @EqualsAndHashCode(of = "id") on both of your entities. I think it's also good to use @ToString to specify the required fields. I hope this will solve your issue.

    @Data
    @Entity
    @Audited
    @EntityListeners(AuditingEntityListener.class)
    @Table(name = "msPartner")
    @EqualsAndHashCode(of = "id")
    @ToString(of = {"id", "partnerCode", "partnerName"})
    public class Partner {}
    
    
    
    @Data
    @Entity
    @Audited
    @EntityListeners(AuditingEntityListener.class)
    @Table(name = "msPartnerShipment")
    @EqualsAndHashCode(of = "id")
    @ToString(of = {"id", "partnerShipmentCode", "partnerShipmentAddress"})
    public class PartnerShipment {}
    

    @Data annotation implements @EqualsAndHashCode. But in this case, it is creating an infinite circular recursion. Example: for the hashCode method the default implementation of @EqualsAndHashCode will include all the fields.

    public class Partner {
      public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $id = this.getId();
        result = result * PRIME + ($id == null ? 43 : $id.hashCode());
        final Object $partnerCode = this.getPartnerCode();
        result = result * PRIME + ($partnerCode == null ? 43 : $partnerCode.hashCode());
        final Object $partnerName = this.getPartnerName();
        result = result * PRIME + ($partnerName == null ? 43 : $partnerName.hashCode());
        final Object $createDate = this.getCreateDate();
        result = result * PRIME + ($createDate == null ? 43 : $createDate.hashCode());
        final Object $lastModifiedDate = this.getLastModifiedDate();
        result = result * PRIME + ($lastModifiedDate == null ? 43 : $lastModifiedDate.hashCode());
        final Object $createdBy = this.getCreatedBy();
        result = result * PRIME + ($createdBy == null ? 43 : $createdBy.hashCode());
        final Object $modifiedBy = this.getModifiedBy();
        result = result * PRIME + ($modifiedBy == null ? 43 : $modifiedBy.hashCode());
        final Object $partnerShipment = this.getPartnerShipment();
        result = result * PRIME + ($partnerShipment == null ? 43 : $partnerShipment.hashCode());
        return result;
    }
    }
    

    look at the partnerShipment. it's using result = result * PRIME + ($partnerShipment == null ? 43 : $partnerShipment.hashCode()); and partnerShipment is a List here.

    and the hashCode of the list comes from AbstractList which is

        public int hashCode() {
        int hashCode = 1;
        for (E e : this)
            hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
        return hashCode;
    }
    

    in this case, it iterates over each PartnerShipment item and calls it's hashCode method. and you are also using @Data annotation on PartnerShipment so it's hashCode method also includes Partner field which creates an infinite recursion.