我一直在想应该使用哪种投影方式,所以我进行了一些测试,涵盖了5种不同的投影方式(基于文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections):
1. 实体投影
这只是Spring Data存储库提供的标准findAll()
。这里没有什么花哨的东西。
服务:
List<SampleEntity> projections = sampleRepository.findAll();
实体:
@Entity
@Table(name = "SAMPLE_ENTITIES")
public class SampleEntity {
@Id
private Long id;
private String name;
private String city;
private Integer age;
}
2. 构造函数映射
服务:
List<NameOnlyDTO> projections = sampleRepository.findAllNameOnlyConstructorProjection();
代码库:
@Query("select new path.to.dto.NameOnlyDTO(e.name) from SampleEntity e")
List<NameOnlyDTO> findAllNameOnlyConstructorProjection();
数据传输对象:
@NoArgsConstructor
@AllArgsConstructor
public class NameOnlyDTO {
private String name;
}
3. 接口投影
服务:
List<NameOnly> projections = sampleRepository.findAllNameOnlyBy();
存储库:
List<NameOnly> findAllNameOnlyBy();
界面:
public interface NameOnly {
String getName();
}
4. 元组投影
服务:
List<Tuple> projections = sampleRepository.findAllNameOnlyTupleProjection();
代码库:
@Query("select e.name as name from SampleEntity e")
List<Tuple> findAllNameOnlyTupleProjection();
5. 动态投影
服务:
List<DynamicProjectionDTO> projections = sampleRepository.findAllBy(DynamicProjectionDTO.class);
代码库:
<T> List<T> findAllBy(Class<T> type);
数据传输对象:
public class DynamicProjectionDTO {
private String name;
public DynamicProjectionDTO(String name) {
this.name = name;
}
}
一些额外信息:
该项目使用gradle spring boot插件(版本2.0.4)构建,其中底层使用了Spring 5.0.8。数据库:H2内存数据库。
结果:
Entity projections took 161.61 ms on average out of 100 iterations.
Constructor projections took 24.84 ms on average out of 100 iterations.
Interface projections took 252.26 ms on average out of 100 iterations.
Tuple projections took 21.41 ms on average out of 100 iterations.
Dynamic projections took 23.62 ms on average out of 100 iterations.
-----------------------------------------------------------------------
One iteration retrieved (from DB) and projected 100 000 objects.
-----------------------------------------------------------------------
注:
可以理解检索实体需要一些时间,Hibernate 跟踪这些对象的变化、延迟加载等。
构造函数投影非常快速,在 DTO 方面没有任何限制,但需要在 @Query
注释中进行手动对象创建。
接口投影非常慢。请参见问题。
元组投影是最快的,但不是最方便的投影方式。它们需要在 JPQL 中指定别名,并且必须通过调用 .get("name")
而不是 .getName()
来检索数据。
动态投影看起来很酷也很快,但必须恰好有一个构造函数。没有更多,也没有更少。否则,Spring Data 会抛出异常,因为它不知道使用哪一个(它需要构造函数参数来确定从数据库检索哪些数据)。
问题:
为什么接口投影比检索实体需要更长时间?每个接口投影返回实际上都是代理。创建该代理非常昂贵吗?如果是这样,那么它是否打败了投影的主要目的(因为它们本来就应该比实体更快)?其他投影看起来很棒。我真的很想了解一些见解。谢谢。
编辑:
这是测试存储库:https://github.com/aurora-software-ks/spring-boot-projections-test,以防您想要运行它。它非常容易设置。自述文件包含您需要了解的所有内容。