优化Spring Data JPA关联数据读取效率
在使用Spring Data JPA经常会使用 ManyToOne、OneToMany、ManyToMany、OneToOne作为实体之间的关联,如果关联使用不当,很容易造成很严重的性能问题,下面会讲解不同场景下如何优化关联关系提升数据的读取效率。
OneToMany/ManyToMany 优化
OneToMany 默认会使用延迟加载策略加载 ToMany 数据,除非必须使用ToMany数据,否则必须使用延迟加载策略。
- 默认情况使用延迟加载
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;
}
- 如果必须在读取列表的时候加载ToMany数据,使用JPA的EntityGraph注解,禁止使用默认的抓取策略,会导致 1+N次查询问题,造成严重的性能损耗。
public interface BarRepository extends EntityGraphJpaRepository<Bar, Long> {
@Override
@EntityGraph(attributePaths = "foo")
Page<Bar> findAll(Pageable pageable);
}
ManyToOne/OneToOne 优化
ManyToOne默认不使用延迟加载,会以join的形式直接获取主表的数据,实际使用中需要关注一下几点:
- 推荐子表关联主表的主键字段,否则会造成1+N次查询,影响性能。
- 如果因为设计无法实现关联主表的主键子段,且每次读取列表都需要关联带出主表数据的情况,推荐使用EntityGraph注解。
- 如果子表关联的不是主表的主键,且每次读取列表都无需带出主表信息,主表的entity只是为了数据过滤的话,推荐使用PersistentAttributeInterceptable手动管理entity的延迟加载属性。
以下是PersistentAttributeInterceptable的用法,ManyToOne的抓取策略设置成 LAZY,并将optional设置成false,添加 LazyToOne注解,将值设置成 NO_PROXY,即不走代理,使用自定义的拦截器处理延迟加载。
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功能。
- 引入maven依赖
<dependency>
<groupId>com.cosium.spring.data</groupId>
<artifactId>spring-data-jpa-entity-graph</artifactId>
<version>2.0.6</version>
</dependency>
- 配置JPA reposition
@Configuration
@EnableJpaRepositories(basePackages = "com.aiyun.hepl.repository", repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class)
@EnableTransactionManagement
public class DatabaseConfiguration {
}
- 修改 reposition 的继承接口。
// 原repository
public interface FooRepository extends JpaRepository<Foo, Long> {
}
// 新reposition
public interface FooRepository extends EntityGraphJpaRepository<Foo, Long> {
}
注: 除了 EntityGraphJpaRepository,框架还提供EntityGraphJpaSpecificationExecutor、EntityGraphQuerydslPredicateExecutor、EntityGraphCrudRepository、EntityGraphPagingAndSortingRepository、EntityGraphQueryByExampleExecutor几个接口,根据实际情况使用。
- 调用
除了支持JPA自带的注解外,EntityGraphJpaRepository支持使用com.cosium.spring.data.jpa.entity.graph.domain.EntityGraph类作为入参以实现动态抓取的目的。
Page<Bar> page = barRepository.findAll(pageable, EntityGraphUtils.fromAttributePaths("foo", "user"));