I’m attempting to update a spring-boot app to 3.3.0, which involves hibernate being bumped from 6.4.4 to 6.5.2. After the bump, a number of queries are failing. In particular, I’ve been debugging this one:
public record Summary(
long productionRunId, long productId, String productLengthName, long count, double volume) { }
@Query(
"""
SELECT new com.example.Summary(
t.productionRun.id,
t.product.id,
t.productLengthName,
COUNT(*),
SUM(t.volume)
)
FROM Tag t
GROUP BY t.productionRun.id, t.product.id, t.productLengthName
""")
List<Summary> getSummaries();
Caused by: org.hibernate.query.SemanticException: Missing constructor for type 'Summary'
at app//org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInstantiation(SemanticQueryBuilder.java:1506)
at app//org.hibernate.query.hql.internal.SemanticQueryBuilder.visitInstantiation(SemanticQueryBuilder.java:275)
at app//org.hibernate.grammars.hql.HqlParser$InstantiationContext.accept(HqlParser.java:4029)
at app//org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelectableNode(SemanticQueryBuilder.java:1453)
at app//org.hibernate.query.hql.internal.SemanticQueryBuilder.visitSelection(SemanticQueryBuilder.java:1407)
I set some breakpoints to see what was going wrong, and I see that the types that Hibernate is looking for in the constructor do not match the types in Summary:
Hi Marco, thank you for the direction. I wrote the test case but unfortunately, I can not reproduce the issue. I’m not sure where to go from here.
To make it closer to my setup, I’d need to use a Repository to define the query instead of using EntityManager, but I don’t see an example of that, and the classes aren’t part of the test case project.
If you cannot reproduce the problem in the template, using plain Hibernate, it means that it might be caused by an external factor. Since it sounds like you’re using Spring Data, I would suggest asking those folks for help.
I came across the same error when upgrading to 6.5.2 (using Quarkus).
It was a similar situation; the isConstructorCompatible method did not find the correct constructor.
In my case it was because of a generic field in an entity which was seen as “Serializeable” which strangely did not match the “String” type in the DTO constructor.
@JGlink can you reproduce the issue using Hibernate only? If so, it would be great it you could share a reproducer and open a Jira issue so someone will be able to look into it.
I am also using Spring-Boot 3.3.0, but it is possible to override the Hibernate version by setting a property (for Maven builds this is <hibernate.version>6.4.8.Final</hibernate.version>.
By playing around with versions, I observed that the problem was introduced when upgrading from 6.4.8.Final to 6.5.0.Final (and above).
There’s really nothing surprising in the entities that can’t be inferred from the query I shared, but I understand you don’t want to make assumptions. I’d also prefer not to post the entities in their entirety, but here are all the entities with all the fields used by the query + tenantId.
@Entity
@Getter
@Accessors(chain = true)
@Setter
public class Tag {
@Id @GeneratedValue private long id;
@TenantId
@Column(name = "tenant_id", nullable = false, updatable = false)
private Long tenantId;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Product product;
@Column(name = "product_length_name", nullable = false)
private String productLengthName;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private ProductionRun productionRun;
}
@Entity
@Getter
@Accessors(chain = true)
@Setter
public class ProductionRun {
@Id @GeneratedValue private long id;
@TenantId
@Column(name = "tenant_id", nullable = false, updatable = false)
private Long tenantId;
}
@Entity
@Getter
@Accessors(chain = true)
@Setter
public class Product {
@Id @GeneratedValue private long id;
@TenantId
@Column(name = "tenant_id", nullable = false, updatable = false)
private Long tenantId;
}
Lombok annotations are used to create getters + setters.
This is what I used to attempt to recreate the issue in the test case repository, hopefully you have better luck than I did!
I think if “missing constructor” exception will not show if you change productionRunId in Summary from long to java.lang.Long (i.e. wrapper instead of primitive type) . However, this may not be fix for the issue. For some (unknown at the moment) reason this first argument is treated as null. See lines in org.hibernate.query.sqm.tree.expression.Compatibility.areAssignmentCompatible:
if ( from == Void.class && !to.isPrimitive() ) {
// treat Void as the bottom type, the class of null
return true;
}
So, if the only problem (bug) is why first parameter is treated as Void/null, this might be cure for your problem.
In my case, the entities looks like this and the issue occurs with the “id” field coming from the base class.
Base class:
@MappedSuperclass
public abstract class AbstractEntity<K extends Serializable> extends PanacheEntityBase {
@Id
protected K id;
protected K getId() { return id; }
protected void setId(K id) {
this.id = id;
}
}
And a sample of a concrete entity:
@Getter
@Setter
@Entity
public class MyConcreteEntity extends AbstractEntity<Long> {
protected String someOtherField;
}
A DTO containing the Id field from the abstract class and the fields of the concrete class:
public class MyConcreteDto {
protected Long id;
protected String someOtherField;
public MyConcreteDto(Long id, String someOtherField) {
this.id = id;
this.someOtherField = someOtherField;
}
}
When executing a query with projection into the DTO, I need to cast the generic “id” field of the abstract entity to long; otherwise I get an error that no matching constructor was found.
Thanks @JGlink and @kevinm416 for sharing your entity and DTO mappings. I was able to reproduce the issue in a simple project using Hibernate only, and I have created a bug report [HHH-18218] - Hibernate JIRA.
Feel free to watch the issue to receive any updates on a fix and vote for it to increase visibility.
@mbladel Did you try reproducing the issue without using abstract classes or generic keys? My issue has neither of those. All entity fields were concrete and matched the DTO fields.
@kevinm416 no, if you couldn’t reproduce the issue yourself it will be very hard for anyone else to try with only partial access to your mappings, but from what I can see the dynamic instantiation in your query should work. As I said, if you cannot reproduce with pure Hibernate maybe something else is breaking along the way.
I was able to reproduce a similar regression involving SemanticQueryBuilder in a different query that started failing in 6.4.8 including a test case: [HHH-18291] - Hibernate JIRA.
@Spunc mentioned being able to change hibernate versions, but it took me a while to figure out how to do that. In the root gradle file, I used this line to override the hibernate version being used by spring-boot:
ext['hibernate.version'] = '6.4.8.Final'
That allowed me to figure out the exact hibernate version where the regression happened before attempting to write the test case, and also confirm it was specific to Hibernate.
And now that I have the ext trick for forcing the Hibernate version, and got past that other regression in 6.4.8.Final, here is a reproduction of the original issue: [HHH-18292] - Hibernate JIRA. The regression happened between 6.4.9.Final and 6.5.0.Final and is present in the most recent 6.5.2.Final release.