我一直在想我应该使用哪种类型的投影,所以我做了一个小测试,涵盖了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启动插件(版本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会抛出异常,因为它不知道使用哪个(它需要构造函数参数来确定从DB检索哪些数据)。
问题:
为什么接口投影比检索实体需要更长的时间?返回的每个接口投影实际上都是一个代理。创建该代理的成本如此之高吗?如果是这样,这难道不违背投影的主要目的吗(因为它们的目的是比实体更快)?其他投影看起来很棒。我真的很想对此有所了解。谢谢你。
编辑:这是测试存储库:https://github.com/aurora-software-ks/spring-boot-projections-test以防您想自己运行它。它非常容易设置。自述文件包含您需要知道的一切。
我在旧版本的Spring Data中经历了类似的行为,这是我的看法:https://arnoldgalovics.com/how-much-projections-can-help/
我与Oliver Gierke(Spring Data负责人)进行了交谈,他做了一些改进(这就是为什么你会得到如此“好”的结果 :-) ) 但基本上,抽象与手动编码总是有成本的。
这和其他一切一样是一个权衡。一方面,您获得了灵活性、更容易的开发、更少的维护(希望如此),另一方面,您获得了完全的控制权,有点丑陋的查询模型。
每一个都有它的优点和缺点:
接口投影:允许嵌套、动态和开放投影,但Spring在运行时生成代理。
DTO投影:更快,但不允许嵌套、动态和开放投影。