Spring Boot3迁移后Hibernate6.3中@Lob字段调用lower()抛异常
问题场景
将项目从Spring Boot 2迁移至Spring Boot 3(搭配Hibernate 6.3.1 Final、Java 17)后,使用Criteria查询带@Lob注解的String类型字段时,抛出InvalidDataAccessResourceUsageException异常,异常信息如下:
jakarta.servlet.ServletException: Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: Parameter 1 of function 'lower()' has type 'STRING', but argument is of type 'java.lang.String'
相关实体类与查询代码如下:
实体类
@Entity public class FooEntity { @Id private Long id; @Column @Lob private String comment; // 其他字段与方法 }
查询代码
public List<FooEntity> search(FooFilters filters) { Specification<FooEntity> specification = getSpecification(filters); return fooRepository.findAll(specification); } private Specification<FooEntity> getSpecification(FooFilters filters) { return (root, query, criteriaBuilder) -> { List<Predicate> p = new ArrayList<>(); if (StringUtils.isNotBlank(filters.comment())) { p.add(criteriaBuilder.like( criteriaBuilder.lower(root.get(FooEntity_.COMMENT)), "%" + filters.comment() + "%")); } return criteriaBuilder.and(p.toArray(Predicate[]::new)); }; }
移除@Lob注解后查询可正常执行,测试基于H2数据库。
原因分析
Hibernate 6对@Lob类型的映射逻辑与旧版本不同:标注@Lob的String字段在H2数据库中会被映射为CLOB类型,而criteriaBuilder.lower()默认期望处理的是标准STRING类型,导致类型不匹配报错。
解决方案
方案1:显式转换字段类型后调用lower()
在Criteria查询中,先将CLOB类型的字段显式转换为String类型,再执行lower()操作:
private Specification<FooEntity> getSpecification(FooFilters filters) { return (root, query, criteriaBuilder) -> { List<Predicate> p = new ArrayList<>(); if (StringUtils.isNotBlank(filters.comment())) { // 显式转换CLOB为String类型 Expression<String> commentExpr = criteriaBuilder.cast(root.get(FooEntity_.COMMENT), String.class); p.add(criteriaBuilder.like( criteriaBuilder.lower(commentExpr), "%" + filters.comment().toLowerCase() + "%")); } return criteriaBuilder.and(p.toArray(Predicate[]::new)); }; }
方案2:修改实体字段的映射类型
如果字段长度允许,可通过@Column的columnDefinition指定字段为VARCHAR类型,同时保留@Lob以支持大文本存储(适用于H2等支持大VARCHAR的数据库):
@Entity public class FooEntity { @Id private Long id; @Column(columnDefinition = "VARCHAR(2000)") // 根据实际需求调整长度 @Lob private String comment; // 其他字段与方法 }
方案3:直接调用数据库原生LOWER函数
通过criteriaBuilder.function()调用H2数据库的原生LOWER函数,直接处理CLOB类型:
private Specification<FooEntity> getSpecification(FooFilters filters) { return (root, query, criteriaBuilder) -> { List<Predicate> p = new ArrayList<>(); if (StringUtils.isNotBlank(filters.comment())) { Expression<String> lowerComment = criteriaBuilder.function( "LOWER", String.class, root.get(FooEntity_.COMMENT) ); p.add(criteriaBuilder.like(lowerComment, "%" + filters.comment().toLowerCase() + "%")); } return criteriaBuilder.and(p.toArray(Predicate[]::new)); }; }
内容的提问来源于stack exchange,提问作者cwiq

