如何使用CriteriaBuilder处理存储为varchar类型的MonthDay?
解决方案:用Criteria API查询varchar存储的MonthDay对象
问题根源
Hibernate对LocalDate有内置的JDBC类型映射,能自动将varchar列转换为SQL的date类型处理,因此as(LocalDate.class)能正常工作。但MonthDay没有默认的JDBC类型映射,Hibernate会将其视为可序列化对象,转换为Postgres的bytea类型,导致查询谓词失效。
可行方案
1. 自定义类型转换器(推荐,支持直接用MonthDay对象查询)
通过Hibernate的AttributeConverter实现MonthDay与数据库varchar字符串的双向转换,让Hibernate能正确识别列类型:
import javax.persistence.AttributeConverter; import javax.persistence.Converter; import java.time.MonthDay; @Converter(autoApply = true) public class MonthDayConverter implements AttributeConverter<MonthDay, String> { @Override public String convertToDatabaseColumn(MonthDay attribute) { return attribute != null ? attribute.toString() : null; } @Override public MonthDay convertToEntityAttribute(String dbData) { return dbData != null ? MonthDay.parse(dbData) : null; } }
- 若实体类中对应字段为
MonthDay类型,添加@Convert(converter = MonthDayConverter.class)(或因autoApply=true自动生效)。 - 查询时直接使用
MonthDay对象,无需手动转换:
criteriaBuilder.between(root.get("my_varchar_column_that_contains_a_monthday"), startMonthDay, endMonthDay);
2. 字符串格式匹配比较(无需修改实体)
如果数据库中varchar存储的MonthDay格式与MonthDay.toString()输出的--MM-dd一致(比如--12-25),可直接将Java端的MonthDay转为字符串,用字符串比较:
String startMonthDayStr = startMonthDay.toString(); String endMonthDayStr = endMonthDay.toString(); Predicate predicate = criteriaBuilder.between( root.get("my_varchar_column_that_contains_a_monthday").as(String.class), startMonthDayStr, endMonthDayStr );
这种方式依赖字符串的字典序与MonthDay的时间顺序一致,只要格式统一就能保证查询逻辑正确。
3. 数据库函数辅助转换(适合复杂场景)
利用Postgres的日期函数,将varchar列转换为可比较的月日数值,比如提取月份和日组合成整数(如1225代表12月25日):
// 提取月份并乘以100加上日期,转为整数比较 Expression<Integer> monthDayNum = criteriaBuilder.function( "to_number", Integer.class, criteriaBuilder.concat( criteriaBuilder.function("substring", String.class, root.get("my_varchar_column_that_contains_a_monthday"), criteriaBuilder.literal(3), criteriaBuilder.literal(2)), criteriaBuilder.function("substring", String.class, root.get("my_varchar_column_that_contains_a_monthday"), criteriaBuilder.literal(6), criteriaBuilder.literal(2)) ) ); int startNum = startMonthDay.getMonthValue() * 100 + startMonthDay.getDayOfMonth(); int endNum = endMonthDay.getMonthValue() * 100 + endMonthDay.getDayOfMonth(); Predicate predicate = criteriaBuilder.between(monthDayNum, startNum, endNum);
总结
- 可以使用实际的
MonthDay对象处理,但需要通过自定义类型转换器让Hibernate正确映射类型。 - 若不想修改实体或映射配置,基于统一格式的字符串比较是简单可行的临时方案。
内容的提问来源于stack exchange,提问作者flauschtrud
相关产品推荐
相关产品推荐

