Spring Data JPA原生查询枚举参数在H2与MariaDB下异常问题
带@Enumerated(EnumType.STRING)的原生查询枚举参数异常问题
问题现象
- 基于Spring Boot 3.4.2开发,实体类枚举字段标注
@Enumerated(EnumType.STRING)时,自定义原生SQL查询无法正确识别枚举参数,返回错误结果甚至抛出异常 - Spring Data自动生成的
findByStatus方法可正常工作,但自定义原生查询findByStatusSql存在以下问题:- H2数据库环境:查询
FEEDBACK返回ACTIVE的实体,调整枚举值顺序后结果会变化;查询ACTIVE直接抛出DataIntegrityViolationException,提示值"0"不符合列的允许值 - MariaDB数据库环境:查询
ACTIVE返回空列表
- H2数据库环境:查询
- 移除
@Enumerated(EnumType.STRING)注解后,原生查询恢复正常
相关代码
实体类
@Data @Entity @Table(name = "order_") @NoArgsConstructor @AllArgsConstructor public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Enumerated(EnumType.STRING) @Column(name="status", nullable = false) private OrderStatus status; }
枚举类
public enum OrderStatus { ACTIVE, FEEDBACK, COMPLETED, FINISHED }
Repository接口
public interface OrderRepository extends JpaRepository<Order, Long> { @Query(nativeQuery = true, value = "SELECT * FROM order_ s WHERE s.status = :status") List<Order> findByStatusSql(OrderStatus status); List<Order> findByStatus(OrderStatus status); }
H2测试代码
@DataJpaTest public class OrderRepositoryTest { @Autowired private OrderRepository repository; @Autowired TestEntityManager entityManager; @Test void test() { Order order = new Order(null, OrderStatus.FEEDBACK); entityManager.persist(order); order = new Order(null, OrderStatus.ACTIVE); entityManager.persist(order); List<Order> orders = repository.findByStatusSql(OrderStatus.FEEDBACK); // 1 - ACTIVE System.out.println(orders.size()); orders = repository.findByStatusSql(OrderStatus.FINISHED); // 1 - FEEDBACK System.out.println(orders.size()); orders = repository.findByStatus(OrderStatus.FEEDBACK); // 1 - FEEDBACK System.out.println(orders.size()); orders = repository.findByStatusSql(OrderStatus.ACTIVE); // org.springframework.dao.DataIntegrityViolationException: JDBC exception executing SQL [SELECT * FROM order_ s WHERE s.status = ?] [Value not permitted for column "('ACTIVE', 'COMPLETED', 'FEEDBACK', 'FINISHED')": "0"; SQL statement: //SELECT * FROM order_ s WHERE s.status = ? [22030-232]] [n/a]; SQL [n/a] } }
MariaDB配置
spring: datasource: url: jdbc:mariadb://localhost:3306/db?connectionTimeZone=SERVER&preserveInstants=true username: user password: pass driver-class-name: org.mariadb.jdbc.Driver connection-test-query: SELECT 1 jpa: hibernate: ddl-auto: create-drop show-sql: true
原因分析
核心问题是原生查询不会自动应用实体类上的@Enumerated(EnumType.STRING)转换规则:
- Spring Data自动生成的查询方法(如
findByStatus)会读取实体的枚举注解,自动将枚举参数转换为对应的字符串值传入查询 - 但原生查询默认会将枚举对象转换为其ordinal序号(从0开始的整数),而非字符串值
- H2数据库中枚举类型列会校验值是否在允许的字符串列表中,传入序号"0"会触发校验异常;MariaDB中字符串列与数字"0"不匹配,因此返回空列表;调整枚举顺序会改变ordinal值,导致返回结果变化
解决思路
方案1:手动转换枚举为字符串参数
修改Repository方法的参数类型为String,调用时传入枚举的name()值:
@Query(nativeQuery = true, value = "SELECT * FROM order_ s WHERE s.status = :status") List<Order> findByStatusSql(String status); // 调用示例 repository.findByStatusSql(OrderStatus.FEEDBACK.name());
方案2:使用SPEL表达式自动转换
通过SPEL表达式在查询中直接获取枚举的字符串值,无需修改参数类型:
@Query(nativeQuery = true, value = "SELECT * FROM order_ s WHERE s.status = :#{#status.name()}") List<Order> findByStatusSql(@Param("status") OrderStatus status);
方案3:自定义属性转换器(推荐)
实现AttributeConverter统一处理枚举与字符串的转换,替代@Enumerated注解,自动生成方法和原生查询都会应用该转换规则:
import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; @Converter(autoApply = true) public class OrderStatusConverter implements AttributeConverter<OrderStatus, String> { @Override public String convertToDatabaseColumn(OrderStatus status) { return status != null ? status.name() : null; } @Override public OrderStatus convertToEntityAttribute(String dbData) { return dbData != null ? OrderStatus.valueOf(dbData) : null; } }
之后移除实体类中的@Enumerated(EnumType.STRING)注解即可。
内容的提问来源于stack exchange,提问作者Sergey Baltak
相关产品推荐
相关产品推荐

