I have a composite ID table with an additional field in my database, and the corresponding entity model configured using the EmbeddedId java persistence annotation. When performing edits in my application, everything works fine. However, when attempting to retrieve Audit data using Hibernate Envers, the code fails, giving me the following stacktrace:
Caused by: org.hibernate.QueryException: could not resolve property: contract_id of: com.mycompany.model.DesignContract_AUD
at org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:62)
at org.hibernate.persister.entity.AbstractPropertyMapping.toType(AbstractPropertyMapping.java:56)
at org.hibernate.persister.entity.AbstractEntityPersister.toType(AbstractEntityPersister.java:1801)
at org.hibernate.hql.internal.ast.tree.FromElementType.getPropertyType(FromElementType.java:393)
at org.hibernate.hql.internal.ast.tree.FromElement.getPropertyType(FromElement.java:507)
at org.hibernate.hql.internal.ast.tree.DotNode.getDataType(DotNode.java:660)
at org.hibernate.hql.internal.ast.tree.DotNode.prepareLhs(DotNode.java:264)
at org.hibernate.hql.internal.ast.tree.DotNode.resolve(DotNode.java:204)
at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:109)
at org.hibernate.hql.internal.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:104)
at org.hibernate.hql.internal.ast.HqlSqlWalker.resolve(HqlSqlWalker.java:1013)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.expr(HqlSqlBaseWalker.java:1286)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.exprOrSubquery(HqlSqlBaseWalker.java:4699)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.comparisonExpr(HqlSqlBaseWalker.java:4169)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2134)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2059)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2059)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.whereClause(HqlSqlBaseWalker.java:813)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:607)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
There are two major problems there. First, Contract_Id is not a field. I have the JoinColumn configured with the proper name of "FK_CONTRACT". Second, I don't understand why it's trying to use the java class name for the audit table, instead of using the name set in the @Table annotation, which is "CONTRACT_DESIGNS". My classes are as follows:
@Entity
@DynamicInsert
@DynamicUpdate
@SelectBeforeUpdate
@Table(name="CONTRACTS")
@Audited
public class Contract implements Serializable {
private static final long serialVersionUID = 1L;
private List<DesignContract> designs;
@OneToMany(mappedBy = "pk.contract", fetch = FetchType.EAGER, cascade = {CascadeType.ALL}, orphanRemoval=true)
@Fetch(value = FetchMode.SUBSELECT)
public List<DesignContract> getDesigns() {
return designs;
}
public void setDesigns(List<DesignContract> designs) {
this.designs= designs;
}
}
@Entity
@AssociationOverrides({
@AssociationOverride(name = "pk.contract",
joinColumns = @JoinColumn(name = "FK_CONTRACT")),
@AssociationOverride(name = "pk.design",
joinColumns = @JoinColumn(name = "FK_DESIGN")) })
@Table(name="CONTRACT_DESIGNS")
@Audited
public class DesignContract implements Serializable {
private static final long serialVersionUID = 1L;
public DesignContract () {
}
public DesignContract (Contract contract, Design design) {
pk.setContract(contract);
pk.setDesign(design);
}
private DesignContractId pk = new DesignContractId();
@EmbeddedId
public DesignContractId getPk() {
return pk;
}
public void setPk(DesignContractId pk) {
this.pk = pk;
}
@Transient
public Contract getContract() {
return getPk().getContract();
}
public void setContract(Contract contract) {
getPk().setContract(contract);
}
@Transient
public Design getDesign() {
return getPk().getDesign();
}
public void setDesign(Design design) {
getPk().setDesign(design);
}
private Double goal;
@Column(name = "GOAL", nullable = true, insertable = true, updatable = true, precision = 5, scale = 2)
@Basic
public Double getGoal() {
return this.goal;
}
public void setGoal(Double goal) {
this.goal = goal;
}
}
@Embeddable
public class DesignContractId implements Serializable {
private static final long serialVersionUID = 1L;
private Contract contract;
private Design design;
@ManyToOne
public Contract getContract() {
return contract;
}
public void setContract(Contract contract) {
this.contract = contract;
}
@ManyToOne
public Design getDesign() {
return design;
}
public void setDesign(Design design) {
this.design = design;
}
}
The toString(), hashCode(), and equals() methods are implemented for all three models, I've just omitted them for brevity.
My test code for retrieving the audit information is incredibly basic, as I'm mostly just doing a proof of concept that things are working and that the lazily-initialized records attached to my main Contract entity can be initialized. The error occurs when I invoke the size() method of the DesignContract list, which according to other posts I've found is the way to force the intilization of Hibernate Enver's ListProxy.
AuditReader reader = AuditReaderFactory.get(sessionFactory.openSession());
List<Number> revisionsContract = reader.getRevisions(Contract.class, contractId);
for (Number revisionNum : revisionsContract) {
System.out.println(" revisionNum = " + revisionNum);
Contract contract = reader.find(Contract.class, contractId, revisionNum);
System.out.println(contract.getDesigns().size());
System.out.println(contract.getDesigns());
System.out.println(contract);
}
For debugging purposes, I've set show_sql to true, and the query that Hibernate is attempting when size() is invoked doesn't make sense:
select e__ from com.mycompany.model.DesignContract_AUD e__ where e__.contract_id = :contract_id and e__.originalId.REVISION_NUMBER.id = (select max(e2__.originalId.REVISION_NUMBER.id) from com.mycompany.model.DesignContract_AUD e2__ where e2__.originalId.REVISION_NUMBER.id <= :revision and e__.originalId.design = e2__.originalId.design and e__.originalId.contract = e2__.originalId.contract) and REVISION_TYPE != :delrevisiontype
Again, there is no contract_id field so I don't know where it's getting that from given my annotations in the models for DesignContract and DesignContractId, and the use of com.mycompany.model.DesignContract_AUD is something I can't understand at all. I even added the @AuditTable annotation, pointing it to the CONTRACT_DESIGNS_AUD table, but that didn't change anything. When performing normal operations such as editing and saving a contract in my application, the audit information is properly added to the CONTRACT_DESIGNS_AUD table, where I can query the information directly via straight sql. I'm at a loss as to why this would be happening with the AuditReader. I have multiple composite ID entities in my application, and they are all configured the same way, and they all are experiencing the same errors, with an unknown contract_id parameter, and the java class name used for the audit table in the queries. I did attempt to configure my model to use the @IdClass annotation instead of @EmbeddedId, but that only helped resolve the table name in the query - it still attempted to filter based on contract_id which, of course, it states cannot be resolved.
I'm using Hibernate 5.2.10.FINAL, which I had upgraded to after reading that composite Id entities failed to work with earlier versions of Hibernate Envers - https://hibernate.atlassian.net/browse/HHH-7625
Any assistance as to what I'm doing wrong would be greatly appreciated.
The problem you're facing a definite bug. It seems when we implemented support for @IdClass
mappings in HHH-7625, we didn't account for @EmbeddedId
correctly.
I have logged a JIRA issue HHH-11770 on this problem, which has been fixed and will be included in the 5.2.11.Final release.