Reputation: 678
I'm fairly new to Spring Boot/Hibernate development, this was my first big project.
I have an application using Spring Boot, Hibernate and Hibernate Envers to audit some entities. Hibernate Envers is set to use the ValidityAuditStrategy. Since Hibernate Envers doesn't support yet eager loading to-Many relationships out of the box, I'm trying to find a way to perform a single query and retrieve all the data I need, to avoid the N+1 queries problem that, at the moment, is killing my performance: it's taking almost 2 minutes in our development environment to completely load the entity we need with the relationships required.
Since I need to retrieve an audited version, I cannot leverage on EntityGraph like I used in other part of the application (at least to my understanding).
One of the situation I need to tackle has 4 entities Parameter
, Formula
, Value
and Variable
, that are related like this (showing only the relevant parts, Value
and Variable
are entities and not listed here)
@Entity
public class Parameter {
@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "formula_id", referencedColumnName = "id", nullable = false)
private Formula formula;
}
@Entity
public class Formula {
@ManyToOne(optional = false)
@JoinColumn(name = "tag_id")
private Value tag;
@ManyToMany
@JoinTable(
name = "Formulas_Variables",
joinColumns = @JoinColumn(name = "formula_id"),
inverseJoinColumns = @JoinColumn(name = "variable_id"))
private Set<Variable> variables = new HashSet<>();
}
What I tried to do is create a custom query and mapping the result using @NamedNativeQuery
and @SqlResultSetMapping
but, even though Hibernate is creating the relationship between Formula
and Value
correctly, it's not creating the array on variables
field. The query and the mapping I used are the following:
@SqlResultSetMapping(name = "Parameter.findAllByRevisionMapping", entities = {
@EntityResult(entityClass = Parameter.class, fields = {
@FieldResult(name = "id", column = "id"),
@FieldResult(name = "formula", column = "formula_id")
}),
@EntityResult(entityClass = Formula.class, fields = {
@FieldResult(name = "id", column = "formulaId"),
@FieldResult(name = "tag", column = "tag_id"),
@FieldResult(name = "variables", column = "variable_id")
}),
@EntityResult(entityClass = DomainValue.class, fields = {
@FieldResult(name = "id", column = "tag_id")
}),
@EntityResult(entityClass = Variable.class, fields = {
@FieldResult(name = "id", column = "variableId")
})
})
@NamedNativeQuery(name = "Parameter.findAllByRevision", query = "SELECT om_c_p_aud.id,\n"
+ " f_aud.id AS formulaId,\n"
+ " dv_aud.id AS tag_id,\n"
+ " fv_aud.formula_id AS formula_id,\n"
+ " v_aud.id AS variable_id,\n"
+ " v_aud.id AS variableId\n"
+ "FROM Parameters_AUD om_c_p_aud\n"
+ " LEFT OUTER JOIN Formulas_AUD f_aud\n"
+ " ON f_aud.id = om_c_p_aud.formula_id AND f_aud.REV <= ?1 AND\n"
+ " f_aud.REVTYPE <> 2 AND (f_aud.REVEND > ?1 OR\n"
+ " f_aud.REVEND IS NULL)\n"
+ " LEFT OUTER JOIN Values_AUD dv_aud\n"
+ " ON dv_aud.id = f_aud.tag_id AND dv_aud.REV <= ?1 AND\n"
+ " dv_aud.REVTYPE <> 2 AND (dv_aud.REVEND > ?1 OR\n"
+ " dv_aud.REVEND IS NULL)\n"
+ " LEFT OUTER JOIN Formulas_Variables_AUD fv_aud\n"
+ " ON fv_aud.formula_id = f_aud.id AND fv_aud.REV <= ?1 AND\n"
+ " fv_aud.REVTYPE <> 2 AND (fv_aud.REVEND > ?1 OR\n"
+ " fv_aud.REVEND IS NULL)\n"
+ " LEFT OUTER JOIN Variables_AUD v_aud\n"
+ " ON v_aud.id = fv_aud.variable_id AND v_aud.REV <= ?1 AND\n"
+ " v_aud.REVTYPE <> 2 AND (v_aud.REVEND > ?1 OR\n"
+ " v_aud.REVEND IS NULL)\n"
+ "WHERE om_c_p_aud.REV <= ?1\n"
+ " AND om_c_p_aud.REVTYPE <> 2\n"
+ " AND (om_c_p_aud.REVEND > ?1 OR\n"
+ " om_c_p_aud.REVEND IS NULL)", resultSetMapping = "Parameter.findAllByRevisionMapping")
An example of result from the query performed directly on the database is the following:
The resulting array of json objects I'm receiving when calling findAllByRevision
query is something like this
[
{
"id":1,
"formula":{
"id":52,
"tag":{
"id":20
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":null
}
}
]
whereas the one I'd expect was
[
{
"id":1,
"formula":{
"id":52,
"tag":{
"id":20
},
"variables":[
{
"id":4
}
]
}
},
{
"id":2,
"formula":{
"id":88,
"tag":{
"id":24
},
"variables":[
{
"id":3
},
{
"id":23
},
{
"id":33
},
{
"id":34
},
{
"id":35
},
{
"id":52
}
]
}
}
]
Does anyone have any idea why it isn't creating the formulas <-> variables relationship? I tried to check the query created by Hibernate when using an EntityGraph for a similar situation and it seemed to me the same as the one showed above. I have no way to check the mapping used in that case though (to my understanding again).
Upvotes: 0
Views: 1023
Reputation: 16420
You should be able to use HQL to specify join fetches for the audited associations. An audited entity is just like a normal entity. The entity name is usually suffixed with "_AUD" so if you want to query the audit info of Parameters
you could query Parameters_AUD
like e.g.:
SELECT p
FROM Parameters_AUD p
LEFT JOIN FETCH p.values
LEFT JOIN FETCH p.variables
Upvotes: 1