Spring Data 中使用Record记录实现投影

Java 16 中引入的 Java Records 允许轻松定义透明数据载体。对于维护依赖于 JPA 、Spring Data的应用程序的开发人员来说,Records 可能是使用数据库投影的绝佳选择。

记录不是实体:记录只能用作投影。
流行的 JPA 实现(如 Hibernate)依赖于无参数构造函数、非最终字段、设置器和非最终类的实体来创建代理,而记录不鼓励或明确禁止所有这些操作。

Spring Data 有多种使用记录的方法:

自动映射
如果记录的组件与跟踪实体的字段匹配,Spring Data 可以自动处理查询返回映射,如下例所示:

public interface AdvocateRepo 
    extends CrudRepository<AdvocateEntity, Integer> {
    
    Iterable<AdvocateRecord> findByRegion(String region);
}

记录Record
这里的组件 Record AdvocateRecord 与 以@Entity 标注的AdvocateEntity类的字段相匹配:

public record AdvocateRecord(
int id, 
String fName, 
String lName, 
String region, 
int twitterFollowers) {}

以@Entity 标注的AdvocateEntity类

public class AdvocateEntity {
    @Id
    private int id;
    private String fName;
    private String lName;
    private String region;
    private int twitterFollowers;
...

查询
Spring Data 还允许在 @Query 中提供 JPQL 查询:

public interface AdvocateRepo 
    extends CrudRepository<AdvocateEntity, Integer> {
    @Query("""
           SELECT 
           new com.bk.records.AdvocateNameRecord(a.fName, a.lName)
           FROM AdvocateEntity a
           WHERE region = ?1
          
""")
    Iterable<AdvocateNameRecord> findNamesByRegion(String region);
}

自定义 repo 实现
Spring Data 还支持自定义 repo 实现,它也可用于处理查询返回到 Record 类的映射。要使用自定义 repo 实现,请定义一个接口:

public interface CustomAdvocateRepo {
    Iterable<AdvocateNameRecord> findAllNameRecords();
}

在对Spring Data repo的extends中添加接口:

public interface AdvocateRepo 
    extends CrudRepository<AdvocateEntity, Integer>,
     CustomAdvocateRepo {
}

并提供 repo 的实现。本例中使用了 RowMapper 来处理查询结果的映射:

public class CustomAdvocateRepoImpl implements CustomAdvocateRepo {
    private JdbcTemplate jdbcTemplate;

    protected CustomAdvocateRepoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    class AdvocateRecordDtoRowMapper 
    implements RowMapper<AdvocateNameRecord> {
        @Override
        public AdvocateNameRecord 
        mapRow(ResultSet rs, int rowNum) throws SQLException {
            return new AdvocateNameRecord(
                    rs.getString("f_name"), rs.getString("l_name"));
        }
    }

    @Override
    public Iterable<AdvocateNameRecord> findAllNameRecords() {
        return jdbcTemplate.query(
        
"SELECT f_name, l_name FROM advocates"
            new AdvocateRecordDtoRowMapper());

    }
}