Hibernate CriteriaBuilder查询带AttributeConverter的枚举集合字段报错
问题分析与解决方案
报错原因
出现ClassCastException的核心原因是Hibernate在Criteria查询阶段的类型处理逻辑冲突:
- 实体属性
enumSet被定义为Set<MyEnum>,同时通过AttributeConverter映射到数据库的String类型 - 直接用
criteriaBuilder.equal(root.get("enumSet"), enumSet)时,Hibernate会尝试将传入的Set<MyEnum>通过转换器转换为数据库列类型,但内部处理时错误地将单个枚举值强制转换为Set,触发类型异常 - 传入String参数时,Hibernate又会尝试将数据库返回的String通过转换器转为
Set<MyEnum>,但查询阶段参数类型与实体属性类型不匹配,导致String转Set的类型错误
可行解决方案(保留现有Converter)
方案1:CriteriaBuilder手动转换参数+属性类型强转
核心思路是:先将查询用的Set<MyEnum>转换为数据库存储的String格式,再通过as(String.class)将实体属性的Path强转为String类型,让两边以String做匹配,绕开Hibernate自动触发的转换器逻辑。
代码示例:
public List<Foo> findByEnumSet(Set<MyEnum> enumSet) { try (var session = sessionFactory.openSession()) { var criteriaBuilder = session.getCriteriaBuilder(); var query = criteriaBuilder.createQuery(Foo.class); var root = query.from(Foo.class); // 手动用转换器将Set转为数据库存储格式的字符串 MyConverter converter = new MyConverter(); String dbFormattedValue = converter.convertToDatabaseColumn(enumSet); // 将实体属性强转为String类型后执行equal匹配 query.select(root).where( criteriaBuilder.equal(root.get("enumSet").as(String.class), dbFormattedValue) ); return session.createQuery(query).getResultList(); } catch (Exception e) { // 自定义异常处理逻辑 throw new RuntimeException("查询失败", e); } }
方案2:使用原生SQL查询
既然原生SQL可以正常查询,直接通过Session创建原生查询是最直接的替代方案,完全绕开CriteriaBuilder的类型问题:
代码示例:
public List<Foo> findByEnumSet(Set<MyEnum> enumSet) { try (var session = sessionFactory.openSession()) { MyConverter converter = new MyConverter(); String dbFormattedValue = converter.convertToDatabaseColumn(enumSet); String sql = "SELECT * FROM foo WHERE enum_set = :dbValue"; return session.createNativeQuery(sql, Foo.class) .setParameter("dbValue", dbFormattedValue) .getResultList(); } catch (Exception e) { throw new RuntimeException("查询失败", e); } }
方案3:模糊匹配场景的处理
如果需要检查是否包含某个枚举值,同样用as(String.class)转换属性类型,同时覆盖枚举值在字符串首尾、中间的情况:
代码示例:
public List<Foo> findByEnumSetContaining(MyEnum targetEnum) { try (var session = sessionFactory.openSession()) { var criteriaBuilder = session.getCriteriaBuilder(); var query = criteriaBuilder.createQuery(Foo.class); var root = query.from(Foo.class); String enumName = targetEnum.name(); String wrappedEnum = Constants.SEPARATOR + enumName + Constants.SEPARATOR; // 覆盖枚举值在字段开头、结尾、中间以及字段本身就是单个枚举值的情况 Predicate matchCondition = criteriaBuilder.or( criteriaBuilder.equal(root.get("enumSet").as(String.class), enumName), criteriaBuilder.like(root.get("enumSet").as(String.class), enumName + Constants.SEPARATOR + "%"), criteriaBuilder.like(root.get("enumSet").as(String.class), "%" + wrappedEnum + "%"), criteriaBuilder.like(root.get("enumSet").as(String.class), "%" + Constants.SEPARATOR + enumName) ); query.select(root).where(matchCondition); return session.createQuery(query).getResultList(); } catch (Exception e) { throw new RuntimeException("查询失败", e); } }
内容的提问来源于stack exchange,提问作者Antsa Rakotoarimalala
相关产品推荐
相关产品推荐

