I have searched all over the internet, these tricks do not fix my problem. Here is my entity (simplified version):
@Entity
@Table(name = "msg")
public class InternalMessage implements Serializable {
private static final long serialVersionUID = 3768430013233258L;
@PrePersist
public void genTicketId() {
// something
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", unique = true)
private Long id;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "internal_msg_parent_fk", nullable = true)
private InternalMessage parent = null;
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<InternalMessage> childMessages = new ArrayList<>();
}
I don't see any problems, and this worked fine before I migrated to Spring boot 3 (hibernate 6).
So what could be the issue?
Edit:
Mysql: 8.0.33
Spring boot: 3.3.3
Vscode 1.92.2
gradle: 8.10
Same result run the app in debug mode in vscode, or command line: ./gradlew bootRun
OK, I have some idea, seems the following code if failing:
public Predicate toPredicate(Root<InternalMessage> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (search == null || search.trim().isEmpty()) {
// this part is causing me issue
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Predicate p = criteriaBuilder.isNull(parent);
predicates.add(p);
query.distinct(true);
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
} else {
// this part works
}
}
and this is the line causing the exception:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Anything wrong with the above line? It worked in older spring boot/hibernate.
I managed to replicate your error locally. I created an implementation of the Specification
interface so to have almost the same toPredicate
method and the context usage. I confirm also that the error originates from the line:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Anyway, I have been able to solve the problem replacing the lines:
Expression<InternalMessage> parent = root.get("parent").as(InternalMessage.class);
Predicate p = criteriaBuilder.isNull(parent);
With this single statement, removing the intermediate Expression
step:
Predicate p = criteriaBuilder.isNull(root.get("parent"));
I am not sure if this leads to the correct data extraction, but I can see that the sql statement that is generated in junit tests is:
select distinct im1_0.id,im1_0.internal_msg_parent_fk from msg im1_0 where im1_0.internal_msg_parent_fk is null
That seems what is required in the original toPredicate
method.
Let me se if this solves the problem in your application.
For the sake of completness, the original method seems to work if Long
is used instead of InternalMessage
, in this way:
Expression<Long> parent = root.get("parent").as(Long.class);
but I think that the above solution is clearer, also because using this approach the generated select statement is this:
select distinct im1_0.id,im1_0.internal_msg_parent_fk from msg im1_0 where cast(im1_0.internal_msg_parent_fk as bigint) is null
where there is this cast(... as bigint) that is quite unnecessary, in this case.