|
|
### 优化Spring Data JPA关联数据读取效率
|
|
|
|
|
|
在使用Spring Data JPA经常会使用 ManyToOne、OneToMany、ManyToMany、OneToOne作为实体之间的关联,如果关联使用不当,很容易造成很严重的性能问题,下面会讲解不同场景下如何优化关联关系提升数据的读取效率。
|
|
|
|
|
|
#### OneToMany/ManyToMany 优化
|
|
|
|
|
|
OneToMany 默认会使用延迟加载策略加载 ToMany 数据,除非必须使用ToMany数据,否则必须使用延迟加载策略。
|
|
|
|
|
|
1. 默认情况使用延迟加载
|
|
|
|
|
|
```java
|
|
|
public class Foo implements Serializable {
|
|
|
|
|
|
private static final long serialVersionUID = 1L;
|
|
|
|
|
|
@Id
|
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
|
private Long id;
|
|
|
|
|
|
@Column(name = "name")
|
|
|
private String name;
|
|
|
|
|
|
@Column(name = "code")
|
|
|
private String code;
|
|
|
|
|
|
@OneToMany(mappedBy = "foo")
|
|
|
private Set<Bar> bars;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
2. 如果必须在读取列表的时候加载ToMany数据,使用JPA的EntityGraph注解,禁止使用默认的抓取策略,会导致 1+N次查询问题,造成严重的性能损耗。
|
|
|
|
|
|
```java
|
|
|
public interface BarRepository extends EntityGraphJpaRepository<Bar, Long> {
|
|
|
|
|
|
@Override
|
|
|
@EntityGraph(attributePaths = "foo")
|
|
|
Page<Bar> findAll(Pageable pageable);
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### ManyToOne/OneToOne 优化
|
|
|
|
|
|
ManyToOne默认不使用延迟加载,会以join的形式直接获取主表的数据,实际使用中需要关注一下几点:
|
|
|
|
|
|
1. 推荐子表关联主表的主键字段,否则会造成1+N次查询,影响性能。
|
|
|
2. 如果因为设计无法实现关联主表的主键子段,且每次读取列表都需要关联带出主表数据的情况,推荐使用EntityGraph注解。
|
|
|
3. 如果子表关联的不是主表的主键,且每次读取列表都无需带出主表信息,主表的entity只是为了数据过滤的话,推荐使用PersistentAttributeInterceptable手动管理entity的延迟加载属性。
|
|
|
|
|
|
以下是PersistentAttributeInterceptable的用法,ManyToOne的抓取策略设置成 LAZY,并将optional设置成false,添加 LazyToOne注解,将值设置成 NO_PROXY,即不走代理,使用自定义的拦截器处理延迟加载。
|
|
|
|
|
|
```java
|
|
|
public class Bar implements PersistentAttributeInterceptable {
|
|
|
|
|
|
@Id
|
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
|
private Long id;
|
|
|
|
|
|
@Column(name = "name")
|
|
|
private String name;
|
|
|
|
|
|
@Column(name = "foo_code")
|
|
|
private String fooCode;
|
|
|
|
|
|
@ManyToOne(fetch = FetchType.LAZY, optional = false)
|
|
|
@JoinColumn(name = "foo_code", referencedColumnName = "code", updatable = false, insertable = false, unique = true)
|
|
|
@LazyToOne(LazyToOneOption.NO_PROXY)
|
|
|
private Foo foo;
|
|
|
|
|
|
@Transient
|
|
|
private PersistentAttributeInterceptor interceptor;
|
|
|
|
|
|
@Override
|
|
|
public PersistentAttributeInterceptor $$_hibernate_getInterceptor() {
|
|
|
return this.interceptor;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void $$_hibernate_setInterceptor(PersistentAttributeInterceptor interceptor) {
|
|
|
this.interceptor = interceptor;
|
|
|
}
|
|
|
|
|
|
public Foo getFoo() {
|
|
|
if (interceptor != null) {
|
|
|
return (Foo)interceptor.readObject(this, "foo", this.foo);
|
|
|
}
|
|
|
return foo;
|
|
|
}
|
|
|
|
|
|
public void setFoo(Foo foo) {
|
|
|
if (interceptor != null) {
|
|
|
this.foo = (Foo) interceptor.writeObject(this, "foo", this.foo, foo);
|
|
|
return;
|
|
|
}
|
|
|
this.foo = foo;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### 扩展EntityGraph
|
|
|
|
|
|
由于Spring Data JPA仅支持注解的形式配置EntityGraph,不支持运行的时候动态指定抓取的字段,推荐使用 spring-data-jpa-entity-graph 来扩展EntityGraph功能。
|
|
|
|
|
|
1. 引入maven依赖
|
|
|
|
|
|
```xml
|
|
|
<dependency>
|
|
|
<groupId>com.cosium.spring.data</groupId>
|
|
|
<artifactId>spring-data-jpa-entity-graph</artifactId>
|
|
|
<version>2.0.6</version>
|
|
|
</dependency>
|
|
|
```
|
|
|
|
|
|
2. 配置JPA reposition
|
|
|
|
|
|
```java
|
|
|
@Configuration
|
|
|
@EnableJpaRepositories(basePackages = "com.aiyun.hepl.repository", repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class)
|
|
|
@EnableTransactionManagement
|
|
|
public class DatabaseConfiguration {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
3. 修改 reposition 的继承接口。
|
|
|
|
|
|
```java
|
|
|
// 原repository
|
|
|
public interface FooRepository extends JpaRepository<Foo, Long> {
|
|
|
|
|
|
}
|
|
|
|
|
|
// 新reposition
|
|
|
|
|
|
public interface FooRepository extends EntityGraphJpaRepository<Foo, Long> {
|
|
|
|
|
|
}
|
|
|
```
|
|
|
|
|
|
注: 除了 EntityGraphJpaRepository,框架还提供EntityGraphJpaSpecificationExecutor、EntityGraphQuerydslPredicateExecutor、EntityGraphCrudRepository、EntityGraphPagingAndSortingRepository、EntityGraphQueryByExampleExecutor几个接口,根据实际情况使用。
|
|
|
|
|
|
4. 调用
|
|
|
|
|
|
除了支持JPA自带的注解外,EntityGraphJpaRepository支持使用com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph类作为入参以实现动态抓取的目的。
|
|
|
|
|
|
```java
|
|
|
Page<Bar> page = barRepository.findAll(pageable, EntityGraphUtils.fromAttributePaths("foo", "user"));
|
|
|
```
|
|
|
|
|
|
|