Envers:错误映射具有已删除实体的集合

Envers: error mapping a collection with deleted entities

我们正在使用 Hibernate Envers 从某个实体及其相关实体中获取特定日期的完整数据。问题是我们在访问在那一刻之后删除的集合中的项目时出错。 所以问题似乎是因为正在访问数据库以检索不再存在的实体。

这是主体:

  @XmlType  
  @XmlRootElement  
  @Entity  
  @Audited  
  @Table(name = PayrollEntity.TABLE_NAME)  
  @NamedQueries({  
  @NamedQuery(name = PayrollEntity.EXISTS_BY_ID_QUERY_NAME, query = PayrollEntity.EXISTS_BY_ID_QUERY, hints = {@QueryHint(name = "org.hibernate.cacheable", value = "true")}),  
  @NamedQuery(name = PayrollEntity.FIND_PAYROLL_QUERY_NAME, query = PayrollEntity.FIND_PAYROLL_QUERY, hints = {@QueryHint(name = "org.hibernate.cacheable", value = "true")}),  
  @NamedQuery(name = PayrollEntity.FIND_EXTRAPAY_QUERY_NAME, query = PayrollEntity.FIND_EXTRAPAY_QUERY, hints = {@QueryHint(name = "org.hibernate.cacheable", value = "true")}),  
  @NamedQuery(name = PayrollEntity.FIND_PAYROLLS_BY_EMPLOYMENT_QUERY_NAME, query = PayrollEntity.FIND_PAYROLLS_BY_EMPLOYMENT_QUERY, hints = {@QueryHint(name = "org.hibernate.cacheable", value = "true")}),  
  })  
  public class PayrollEntity extends AbstractEntity {  

  private static final long serialVersionUID = -1982937254314630067L;  

  /* PARAMS */  
  public static final String EXISTS_PAYROLL_EXCLUDEDIDPAYROLL_PARAM = "idPayroll";  
  public static final String EXISTS_PAYROLL_IDPAYROLLTYPE_PARAM = "idPayrollType";  
  public static final String EXISTS_PAYROLL_IDEMPLOYMENT_PARAM = "idEmployment";  
  public static final String EXISTS_PAYROLL_YEAR_PARAM = "year";  
  public static final String EXISTS_PAYROLL_MONTH_PARAM = "month";  
  public static final String EXISTS_PAYROLL_IDEXTRAPAY_PARAM = "idExtraPay";  

  /* QUERIES */  
  public static final String EXISTS_BY_ID_QUERY_NAME = "EXISTS_PAYROLL_BY_ID";  
  public static final String EXISTS_BY_ID_QUERY =  
  "SELECT e.idPayroll FROM PayrollEntity e WHERE e.idPayroll = :" + EXISTS_BY_ID_QUERY_PARAM;  

  public static final String FIND_PAYROLL_QUERY_NAME = "EXISTS_PAYROLL_BY_CONCEPTUAL_ID";  
  public static final String FIND_PAYROLL_QUERY =  
  "SELECT e FROM PayrollEntity e WHERE e.idPayroll != :" + EXISTS_PAYROLL_EXCLUDEDIDPAYROLL_PARAM  
  + " AND e.employment.idEmployment = :" + EXISTS_PAYROLL_IDEMPLOYMENT_PARAM  
  + " AND e.year = :" + EXISTS_PAYROLL_YEAR_PARAM  
  + " AND e.month = :" + EXISTS_PAYROLL_MONTH_PARAM  
  + " AND e.payrollType.idPayrollType = :" + EXISTS_PAYROLL_IDPAYROLLTYPE_PARAM;  

  public static final String FIND_EXTRAPAY_QUERY_NAME = "EXISTS_EXTRAPAY_BY_CONCEPTUAL_ID";  
  public static final String FIND_EXTRAPAY_QUERY =  
  "SELECT e FROM PayrollEntity e WHERE e.idPayroll != :" + EXISTS_PAYROLL_EXCLUDEDIDPAYROLL_PARAM  
  + " AND e.employment.idEmployment = :" + EXISTS_PAYROLL_IDEMPLOYMENT_PARAM  
  + " AND e.year = :" + EXISTS_PAYROLL_YEAR_PARAM  
  + " AND e.month = :" + EXISTS_PAYROLL_MONTH_PARAM  
  + " AND e.payrollType.idPayrollType = :" + EXISTS_PAYROLL_IDPAYROLLTYPE_PARAM  
  + " AND e.extraPay.idExtraPay = :" + EXISTS_PAYROLL_IDEXTRAPAY_PARAM;  

  public static final String FIND_PAYROLLS_BY_EMPLOYMENT_QUERY_NAME = "FIND_PAYROLLS_BY_EMPLOYMENT";  
  public static final String FIND_PAYROLLS_BY_EMPLOYMENT_QUERY =  
  "SELECT e FROM PayrollEntity e WHERE e.employment.idEmployment = :" + EXISTS_PAYROLL_IDEMPLOYMENT_PARAM  
  + " AND e.year = :" + EXISTS_PAYROLL_YEAR_PARAM  
  + " AND e.month = :" + EXISTS_PAYROLL_MONTH_PARAM;  

  /* TABLE */  
  public static final String TABLE_NAME = "EP_PAYROLL";  

  /* COLUMNS */  
  public static final String ID_PAYROLL_COLUMN = "ID_PAYROLL";  
  public static final String VERSION_COLUMN = "VERSION";  
  public static final String ID_EMPLOYMENT_COLUMN = "ID_EMPLOYMENT";  
  public static final String ID_PAYROLL_TYPE_COLUMN = "ID_PAYROLL_TYPE";  
  public static final String YEAR_COLUMN = "YEAR";  
  public static final String MONTH_COLUMN = "MONTH";  
  public static final String CALCULATION_DATETIME_COLUMN = "CALCULATION_DATETIME";  
  public static final String PAYSLIP_GENERATION_DATETIME_COLUMN = "PAYSLIP_GENERATION_DATETIME";  
  public static final String ID_EXTRA_PAY_COLUMN = "ID_EXTRA_PAY";  
  public static final String DISCOUNT_RATE_CALCULATED = "DISCOUNT_RATE_CALCULATED";  
  public static final String DISCOUNT_RATE_SOLICITED = "DISCOUNT_RATE_SOLICITED";  
  public static final String DISCOUNT_RATE_APPLIED = "DISCOUNT_RATE_APPLIED";  

  private String idPayroll;  
  private int version;  
  private PayrollTypeEntity payrollType;  
  private EmploymentEntity employment;  
  private int year;  
  private int month;  
  private DateTime calculationDateTime;  
  private DateTime payslipGenerationDatetime;  
  private List<PayrollSalaryItemEntity> payrollSalaryItems;  
  private ExtraPayEntity extraPay;  
  private Float discountRateCalculated;  
  private Float discountRateSolicited;  
  private Float discountRateApplied;  

  @Column(name = CALCULATION_DATETIME_COLUMN)  
  @Type(type = JODA_DATE_TIME_JPA_CONVERTER)  
  public DateTime getCalculationDateTime() {  
  return calculationDateTime;  
  }  

  @Column(name = DISCOUNT_RATE_APPLIED, columnDefinition = "decimal(4,2)")  
  public Float getDiscountRateApplied() {  
  return discountRateApplied;  
  }  

  @Column(name = DISCOUNT_RATE_CALCULATED, columnDefinition = "decimal(4,2)")  
  public Float getDiscountRateCalculated() {  
  return discountRateCalculated;  
  }  

  @Column(name = DISCOUNT_RATE_SOLICITED, columnDefinition = "decimal(4,2)")  
  public Float getDiscountRateSolicited() {  
  return discountRateSolicited;  
  }  

  @ManyToOne(targetEntity = EmploymentEntity.class)  
  @JoinColumn(name = ID_EMPLOYMENT_COLUMN, referencedColumnName = EmploymentEntity.ID_EMPLOYMENT_COLUMN)  
  public EmploymentEntity getEmployment() {  
  return employment;  
  }  

  @ManyToOne(targetEntity = ExtraPayEntity.class)  
  @JoinColumn(name = ID_EXTRA_PAY_COLUMN, referencedColumnName = ExtraPayEntity.ID_EXTRA_PAY_COLUMN)  
  public ExtraPayEntity getExtraPay() {  
  return extraPay;  
  }  

  @Column(name = ID_PAYROLL_COLUMN, nullable = false, updatable = false, length = 36)  
  @Id  
  @GeneratedValue(generator = UUID_GENERATOR_NAME)  
  @GenericGenerator(name = UUID_GENERATOR_NAME, strategy = UUID_GENERATOR_STRATEGY)  
  public String getIdPayroll() {  
  return idPayroll;  
  }  

  @Column(name = MONTH_COLUMN)  
  public int getMonth() {  
  return month;  
  }  

  @OneToMany(mappedBy = "payroll", cascade = CascadeType.ALL, orphanRemoval = true)  
  public List<PayrollSalaryItemEntity> getPayrollSalaryItems() {  
  return payrollSalaryItems;  
  }  

  @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)  
  @ManyToOne(targetEntity = PayrollTypeEntity.class)  
  @JoinColumn(name = ID_PAYROLL_TYPE_COLUMN, referencedColumnName = PayrollTypeEntity.ID_PAYROLL_TYPE_COLUMN)  
  public PayrollTypeEntity getPayrollType() {  
  return payrollType;  
  }  

  @Column(name = PAYSLIP_GENERATION_DATETIME_COLUMN)  
  @Type(type = JODA_DATE_TIME_JPA_CONVERTER)  
  public DateTime getPayslipGenerationDatetime() {  
  return payslipGenerationDatetime;  
  }  

  @Version  
  public int getVersion() {  
  return version;  
  }  

  @Column(name = YEAR_COLUMN)  
  public int getYear() {  
  return year;  
  }  

  public void setCalculationDateTime(DateTime calculationDate) {  
  this.calculationDateTime = calculationDate;  
  }  

  public void setDiscountRateApplied(Float discountRateApplied) {  
  this.discountRateApplied = discountRateApplied;  
  }  

  public void setDiscountRateCalculated(Float discountRateCalculated) {  
  this.discountRateCalculated = discountRateCalculated;  
  }  

  public void setDiscountRateSolicited(Float discountRateSolicited) {  
  this.discountRateSolicited = discountRateSolicited;  
  }  

  public void setEmployment(EmploymentEntity employment) {  
  this.employment = employment;  
  }  

  public void setExtraPay(ExtraPayEntity extraPay) {  
  this.extraPay = extraPay;  
  }  

  public void setIdPayroll(String idPayroll) {  
  this.idPayroll = idPayroll;  
  }  

  public void setMonth(int month) {  
  this.month = month;  
  }  

  public void setPayrollSalaryItems(List<PayrollSalaryItemEntity> payrollSalaryItems) {  
  this.payrollSalaryItems = payrollSalaryItems;  
  }  

  public void setPayrollType(PayrollTypeEntity payrollType) {  
  this.payrollType = payrollType;  
  }  

  public void setPayslipGenerationDatetime(DateTime payslipGenerationDatetime) {  
  this.payslipGenerationDatetime = payslipGenerationDatetime;  
  }  

  public void setVersion(int version) {  
  this.version = version;  
  }  

  public void setYear(int year) {  
  this.year = year;  
  }  
  }  

这是我们用来从 envers 获取数据的代码:

public PayrollEntity findHistorical(String idPayroll, DateTime date) {  
AuditReader reader = AuditReaderFactory.get(getEntityManager());  
return reader.find(PayrollEntity.class, idPayroll, (int)
    reader.createQuery()  
    .forRevisionsOfEntity(PayrollEntity.class, false, true)
    .add(AuditEntity.revisionProperty("revTimestamp").lt(date.getMillis()))  
    .addProjection(AuditEntity.revisionNumber().max())  
    .add(AuditEntity.id().eq(idPayroll))  
    .getSingleResult());  
    }  

然后我们尝试使用 dozer 将数据映射到 DTO,我们得到了这个错误:

Caused by: javax.persistence.EntityNotFoundException: Unable to find es.gc.epsilon.core.domain.SalaryItemEntity with id 15  
at org.hibernate.ejb.Ejb3Configuration$Ejb3EntityNotFoundDelegate.handleEntityNotFound(Ejb3Configuration.java:157) [hibernate-entitymanager-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]  
at org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:262) [hibernate-core-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]  
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176) [hibernate-core-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]  
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:286) [hibernate-core-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]  
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) [hibernate-core-4.2.14.SP1-redhat-1.jar:4.2.14.SP1-redhat-1]  

正如我所说,这似乎是因为 dozer 从主实体的 payrollSalaryItems 集合列表中访问了已删除的项目。

那么,问题来了。是否可以将包含集合中已删除项目的实体的数据映射到 DTO?我们做的对吗?

PD:我们使用的是 Hibernate 4.2 版。21.Final

提前致谢。

您所描述的是由 HHH-8093 and HHH-8051 确定的一个问题,这两个问题都暂定为 Hibernate Envers 6.0 版本。

如果我在 6.0 版本之前得到这些,我会尝试。

显然,这不能解决您对 Hibernate Envers 4.2 的直接需求,顺便说一句,它已经很老了,不再维护或修补。