为什么接口投影比 Spring Data JPA with Hibernate 中的构造函数投影和实体投影慢得多?
Why are interface projections much slower than constructor projections and entity projections in Spring Data JPA with Hibernate?
我一直在想我应该使用哪种投影,所以我做了一个小测试,其中涵盖了5种类型的投影(基于文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections):
1.实体投影
这只是 Spring 数据存储库提供的标准 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 数据中遇到了类似的行为,这是我的看法:https://arnoldgalovics.com/how-much-projections-can-help/
我和 Oliver Gierke(Spring 数据负责人)谈过,他做了一些改进(这就是为什么你会得到如此“好”的结果:-))但基本上总是要付出代价的抽象与手动编码。
与其他一切一样,这是一种权衡。一方面,您获得了灵活性、更容易的开发、更少的维护(希望如此),另一方面,您获得了完全的控制权,但查询模型有点丑陋。
各有优缺点:
界面投影:
允许嵌套、动态和开放投影,但 Spring 在运行时生成代理。
DTO 投影:
更快,但不允许嵌套、动态和开放投影。
我一直在想我应该使用哪种投影,所以我做了一个小测试,其中涵盖了5种类型的投影(基于文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections):
1.实体投影
这只是 Spring 数据存储库提供的标准 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 数据中遇到了类似的行为,这是我的看法:https://arnoldgalovics.com/how-much-projections-can-help/
我和 Oliver Gierke(Spring 数据负责人)谈过,他做了一些改进(这就是为什么你会得到如此“好”的结果:-))但基本上总是要付出代价的抽象与手动编码。
与其他一切一样,这是一种权衡。一方面,您获得了灵活性、更容易的开发、更少的维护(希望如此),另一方面,您获得了完全的控制权,但查询模型有点丑陋。
各有优缺点:
界面投影: 允许嵌套、动态和开放投影,但 Spring 在运行时生成代理。
DTO 投影: 更快,但不允许嵌套、动态和开放投影。