JPA Projection 详解(接口投影 / 类投影 / 动态投影 / 原生SQL映射)

JPA Projection 详解(接口投影 / 类投影 / 动态投影 / 原生SQL映射)

作者:职业帅哥

适用技术栈:Spring Data JPA / Hibernate

适用场景:大数据量查询优化、DTO 映射、接口隔离、微服务接口瘦身


一、什么是 Projection(投影)?

Projection 是指: > 只查询和返回实体的一部分字段,而不是整个 Entity对象。 这类似于SQL中的 SELECT column1, column2 而非 SELECT *

🎯 为什么要使用 Projection?

优点 说明


减少 IO 避免加载无用字段

提升性能 降低 Hibernate 实体管理成本

避免懒加载陷阱 不触发关联加载

更适合接口返回 天然 DTO

安全 防止敏感字段泄露


二、Projection 分类总览

Spring Data JPA 支持多种投影方式:

类型 特点 推荐指数
Interface Projection 自动代理、零侵入 ⭐⭐⭐⭐⭐
Class Projection (DTO) 构造器映射 ⭐⭐⭐⭐
Dynamic Projection 运行时切换返回类型 ⭐⭐⭐
Native SQL Projection 原生 SQL 映射 ⭐⭐⭐

三、准备示例实体

java 复制代码
@Entity
@Table(name = "user")
public class UserEntity {

    @Id
    private Long id;

    private String username;
    private String email;
    private Integer age;

    private String password;
}

四、接口投影(Interface Projection)⭐⭐⭐⭐⭐

4.1 定义投影接口

java 复制代码
public interface UserSimpleView {

    Long getId();
    String getUsername();
    Integer getAge();
}

✔ 方法名必须与 Entity 字段一致(getter 规则)


4.2 Repository 使用

java 复制代码
public interface UserRepository extends JpaRepository<UserEntity, Long> {

    List<UserSimpleView> findByAgeGreaterThan(Integer age);
}

执行 SQL:

sql 复制代码
select id, username, age from user where age > ?

4.3 嵌套投影

java 复制代码
public interface OrderView {
    Long getId();
    UserView getUser();

    interface UserView {
        String getUsername();
    }
}

4.4 SpEL 计算字段

java 复制代码
public interface UserView {

    String getUsername();

    @Value("#{target.username + ' (' + target.age + ')'}")
    String getDisplayName();
}

五、DTO 类投影(Class Projection)⭐⭐⭐⭐

5.1 DTO 定义

java 复制代码
public class UserDTO {

    private Long id;
    private String username;

    public UserDTO(Long id, String username) {
        this.id = id;
        this.username = username;
    }
}

5.2 JPQL 构造器查询

java 复制代码
@Query("select new com.demo.dto.UserDTO(u.id, u.username) from UserEntity u")
List<UserDTO> findUserDTOs();

必须使用全类名。


5.3 优缺点

优点 缺点
类型安全 需要维护构造器
可复杂计算 JPQL 可读性较差
可脱离实体 修改字段需同步修改 DTO

六、动态投影(Dynamic Projection)⭐⭐⭐

6.1 Repository 定义

java 复制代码
<T> List<T> findByAgeGreaterThan(Integer age, Class<T> type);

6.2 调用示例

java 复制代码
List<UserSimpleView> views =
    repo.findByAgeGreaterThan(18, UserSimpleView.class);

List<UserDTO> dtos =
    repo.findByAgeGreaterThan(18, UserDTO.class);

6.3 使用场景

  • 同一接口支持多个返回结构
  • 后台管理 / 前台接口共用查询

七、原生 SQL Projection

7.1 接口映射

java 复制代码
public interface UserNativeView {
    Long getId();
    String getUsername();
}
java 复制代码
@Query(value = "select id as id, username as username from user", nativeQuery = true)
List<UserNativeView> findNativeUsers();

SQL 别名必须与接口方法名一致。


7.2 DTO 映射

java 复制代码
@SqlResultSetMapping(...)

(略,复杂项目才建议使用)


八、分页 + Projection

java 复制代码
Page<UserSimpleView> findByAgeGreaterThan(Integer age, Pageable pageable);

分页仍然有效。


九、排序 + Projection

java 复制代码
findByAgeGreaterThan(Integer age, Sort sort);

十、常见坑总结

❌ 1. 字段名不一致

接口方法名必须与字段名匹配。


❌ 2. 使用 Lombok Getter 命名异常

确保生成 getXxx() 方法。


❌ 3. JSON 序列化失败

Projection 是代理对象,建议直接返回给前端无问题。


❌ 4. N+1 问题

嵌套投影仍可能触发延迟加载。


十一、性能建议

建议 原因
优先使用接口投影 最轻量
避免返回 Entity 避免脏数据
只查必要字段 减少网络 IO
大列表必须分页 防止 OOM
SQL 日志观察字段 验证投影是否生效

十二、与你现有项目的结合建议

结合你目前的:

  • ✅ 微服务架构
  • ✅ 大量统计查询
  • ✅ 自定义参数转义模块
  • ✅ 多表统计分析

推荐:

Controller 层返回 Projection 接口,而不是 Entity 或 VO。

示例:

java 复制代码
public interface AlarmStatView {
    String getProjectName();
    Long getTotalCount();
}

Mapper:

java 复制代码
List<AlarmStatView> statByProject(...);

直接用于图表接口,零 DTO 转换成本。


十三、参考文档

相关推荐
黎雁·泠崖2 小时前
Java字符串API:String/StringBuffer/StringBuilder详解
java·开发语言
Jack_abu2 小时前
stream().toList()与.collect(Collectors.toList())
java·stream·jdk8
黎雁·泠崖2 小时前
Java核心API之Object类:所有类的根父类
java·开发语言
Remember_9932 小时前
【LeetCode精选算法】位运算专题
java·开发语言·jvm·后端·算法·leetcode
曹牧2 小时前
Java:代理转发配置Nginx
java·开发语言·nginx
洋不写bug2 小时前
JavaEE基础,计算机是如何工作的
java·java-ee·状态模式
码农水水2 小时前
小红书Java面试被问:mTLS(双向TLS)的证书验证和握手过程
java·开发语言·数据库·redis·python·面试·开源
康小庄2 小时前
List线程不安全解决办法和适用场景
java·数据结构·spring boot·spring·list·intellij-idea
上海合宙LuatOS2 小时前
LuatOS框架的使用(1)
java·开发语言·单片机·嵌入式硬件·物联网·ios·iphone