Spring Data JPA @SqlResultSetMapping配合外部化查询使用方法
@SqlResultSetMapping 对外部化查询的支持性
完全支持。@SqlResultSetMapping的作用仅为定义数据库结果集到Java业务对象的映射规则,和SQL语句本身的存储位置、加载方式没有强绑定关系,只要最终执行的原生查询能关联到提前定义好的映射名称,就可以正常完成结果转换,完全兼容SQL存放在jpa-named-queries.properties的外部化管理场景。
可运行实现示例
以下是完整的落地步骤,以订单关联用户的复杂查询场景为例:
1. 定义结果集映射规则
在任意JPA实体类(或标注了@EntityScan能扫描到的配置类)上定义映射规则,指定要转换的目标业务对象和列对应关系:
import jakarta.persistence.*; import java.math.BigDecimal; import java.time.LocalDateTime; // 定义结果集映射,后续通过名称OrderDetailVOMapping即可引用 @SqlResultSetMapping( name = "OrderDetailVOMapping", classes = @ConstructorResult( targetClass = OrderDetailVO.class, columns = { @ColumnResult(name = "order_id", type = Long.class), @ColumnResult(name = "order_no", type = String.class), @ColumnResult(name = "user_id", type = Long.class), @ColumnResult(name = "user_name", type = String.class), @ColumnResult(name = "total_amount", type = BigDecimal.class), @ColumnResult(name = "create_time", type = LocalDateTime.class) } ) ) @Entity @Table(name = "t_order") public class Order { @Id private Long id; // 其余实体类字段省略 }
对应的复杂业务对象不需要加JPA注解,仅需要提供和映射规则列顺序、类型完全匹配的构造方法即可:
import java.math.BigDecimal; import java.time.LocalDateTime; public class OrderDetailVO { private Long orderId; private String orderNo; private Long userId; private String userName; private BigDecimal totalAmount; private LocalDateTime createTime; // 必须保留和映射规则匹配的全参构造,否则映射失败 public OrderDetailVO(Long orderId, String orderNo, Long userId, String userName, BigDecimal totalAmount, LocalDateTime createTime) { this.orderId = orderId; this.orderNo = orderNo; this.userId = userId; this.userName = userName; this.totalAmount = totalAmount; this.createTime = createTime; } // 省略getter/setter }
2. 外部化存放长SQL
在项目resources/META-INF目录下创建jpa-named-queries.properties文件,将复杂SQL写入文件,key的命名规则为Repository全限定类名.方法名:
# META-INF/jpa-named-queries.properties # 注意SQL中列的别名必须和@SqlResultSetMapping中定义的列名完全一致 com.example.repository.OrderRepository.queryOrderDetail=SELECT o.id AS order_id, o.order_no AS order_no, u.id AS user_id, u.user_name AS user_name, o.total_amount AS total_amount, o.create_time AS create_time FROM t_order o LEFT JOIN t_user u ON o.user_id = u.id WHERE o.del_flag = 0 AND o.create_time >= ?1
3. 编写Repository接口
在Repository的对应方法上标注@Query注解,指定为原生查询、关联对应的结果集映射名称,不需要在注解内写SQL内容,框架会自动从properties文件中加载同名SQL执行:
import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import java.time.LocalDateTime; import java.util.List; public interface OrderRepository extends Repository<Order, Long> { // nativeQuery = true 标识为原生查询,resultSetMapping关联提前定义的映射规则 @Query(nativeQuery = true, resultSetMapping = "OrderDetailVOMapping") List<OrderDetailVO> queryOrderDetail(LocalDateTime startTime); }
落地注意事项
- SQL查询返回的列别名必须和
@ColumnResult中定义的name属性完全一致,否则会出现对应字段值为null的问题 - 使用
@ConstructorResult做构造注入时,不要给业务类添加会导致全参构造消失的注解(比如Lombok的@NoArgsConstructor单独使用时会覆盖全参构造,建议搭配@AllArgsConstructor使用) - 如果自定义了
jpa-named-queries.properties的存放位置,可以通过配置项spring.jpa.properties.hibernate.jdbc.named_queries_uri=classpath:你的文件路径指定加载位置 - properties中定义的命名查询优先级高于
@NamedNativeQuery注解中写死的SQL,如果之前已经在注解上配置了resultSetMapping,只需要在properties中写同名的SQL即可覆盖原有SQL,不需要重复配置映射规则
内容的提问来源于stack exchange,提问作者Nathan Coates
相关产品推荐
相关产品推荐

