在 JPA 投影查询中使用 Record
Java 16 中引入的 Java Record 允许轻松地定义数据类(Data Class),这非常适合用于 JPA 中的投影查询。
Record 不能作为实体类
Record 只能用于投影查询。像 Hibernate 等流行的 JPA 实现创建代理对象时需要无参构造函数、非 final
字段、setter 方法和非 final
的实体类。而这些特性在 Record 中要么被不鼓励使用,要么被明确禁止使用。
Record 和 JPA
如果你在应用中直接使用 JPA,有几种不同的方法可以将记 Record 整合到 DAO 层中。
CriteriaBuilder
Record
可与 CriteriaBuilder
一起使用,如下:
public List<AdvocateRecord> findAllWithCriteriaBuilder() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<AdvocateRecord> cq
= cb.createQuery(AdvocateRecord.class);
Root<AdvocateEntity> root = cq.from(AdvocateEntity.class);
cq.select(cb.construct(
AdvocateRecord.class,
root.get("id"),
root.get("fName"),
root.get("lName"),
root.get("region"),
root.get("twitterFollowers")));
TypedQuery<AdvocateRecord> q = em.createQuery(cq);
return q.getResultList();
}
TypedQuery
Record
也可以与 TypedQuery
一起使用,但需要在 JPQL 查询中提供完整类路径的构造函数。
public List<AdvocateNameRecord>
findAdvocateNamesByRegionTypedQuery(String region) {
TypedQuery<AdvocateNameRecord> query = em.createQuery("""
SELECT
new com.bk.records.AdvocateNameRecord(a.fName, a.lName)
FROM AdvocateEntity a
WHERE region = :region
""", AdvocateNameRecord.class);
query.setParameter("region", region);
return query.getResultList();
}
NativeQuery
Record
也可与 NativeQuery
一起使用。需要提供一个 Mapping 来处理从查询到 Record 字段的映射,就像下面示例中的 AdvocateNameRecordMapping
一样:
public List<AdvocateNameRecord>
findAdvocateNamesByIdNativeQuery(int id) {
Query query = em.createNativeQuery("""
SELECT
f_name, l_name
FROM advocates
WHERE id = :id
""",
"AdvocateNameRecordMapping");
query.setParameter("id", id);
return query.getResultList();
}
Mapping 定义
AdvocateNameRecordMapping
Mapping 定义在 AdvocateEntity
实体类中:
@Entity
@Table(name = "advocates")
@SqlResultSetMapping(
name = "AdvocateNameRecordMapping",
classes = @ConstructorResult(
targetClass = AdvocateNameRecord.class,
columns = {
@ColumnResult(name = "f_name"),
@ColumnResult(name = "l_name")}))
public class AdvocateEntity {
...
Record 和 Spring Data
与直接使用 JPA 一样,使用 Spring Data 时也有几种使用 Record
的方法。
自动映射
如果 Record 与实体的字段相匹配,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
public class AdvocateEntity {
@Id
private int id;
private String fName;
private String lName;
private String region;
private int twitterFollowers;
...
}
Query
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);
}
自定义 Repository 实现
Spring Data 还支持 自定义 Repository 实现,这些实现也可用于处理查询返回到 Record 类的映射。
要使用自定义 Repository 实现,需要定义一个接口:
public interface CustomAdvocateRepo {
Iterable<AdvocateNameRecord> findAllNameRecords();
}
Spring Data Repository 继承该接口:
public interface AdvocateRepo
extends CrudRepository<AdvocateEntity, Integer>,
CustomAdvocateRepo {
}
并提供 Repository 的实现。本例中使用了 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());
}
}
进阶阅读
- 在 Spring Boot 3 中使用 Java Record
- 在 JPA 和 Sping Data JPA 中使用 Java Record
- https://thorben-janssen.com/java-records-hibernate-jpa
- https://vladmihalcea.com/java-records-jpa-hibernate/
- https://openjdk.java.net/jeps/395
参考:https://wkorando.github.io/sip-of-java/015.html