JPA效率优化
n+1问题
当我们使用JPA提供给我们的find方法时,如果查询出来的对象关联着另外10个对象,那么JPA将会发送1+10次查询(这个对象本身要查询一次,然后每个关联对象再查询一次)。此时如果有100个用户正同时发送请求,这个时候就会产生1100次查询,这将会大大增加服务器的开销,因此我们要避免这个问题。
Fetch Join
在JPA中,我们可以使用fetch join来获取我们需要加载的关联实体。
优点:它只需要一次查询就能够获取到我们需要的所有关联实体。
缺点:不够灵活,选择不同的关联实体就要重写JPQL语句或者是用Criteria API来封装一个很好的接口,当一个对象有很多关联实体的时候,要重写很多的JPQL语句。但如果在实际开发中,关联实体的组合数量较少的情况下是可以用的。
NameEntityGraph
这是JPA2.1的新特性,它的主要作用是来设置懒加载需要加载的关联对象,并且支持多层关联,比如account.getRoles().get(0).getPages(),来获取一个账号的某个角色所能访问的页面。
下面来看一个例子,假如有如下3个类
Account
@Entity @Table(name = ACCOUNT) @NamedEntityGraphs({ @NamedEntityGraph(name = "account.all", attributeNodes = {//attributeNodes 来定义需要懒加载的属性 @NamedAttributeNode("student"), @NamedAttributeNode("staff"),//无延伸 @NamedAttributeNode(value = "roles",//要懒加载roles属性中的pages元素 subgraph = "pages"), }, subgraphs = {//subgraphs 来定义关联对象的属性 @NamedSubgraph(name = "pages",//一层延伸 attributeNodes = @NamedAttributeNode("pages")), @NamedSubgraph(name = "role",//两层延伸 attributeNodes = @NamedAttributeNode( value = "role", subgraph = "pages")) }) }) public class Account extends EntityId { @OneToOne @JoinColumn(name = ACCOUNT_STAFF) private Staff staff; @OneToOne @JoinColumn(name = ACCOUNT_STUDENT) private Student student; @Column private String username; @Column private String password; @ManyToMany(cascade = CascadeType.REFRESH) @JoinTable(name = REL_ACCOUNT_ROLE, joinColumns = { @JoinColumn(name = "accounts", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "roles", referencedColumnName = "id") }) @OrderBy(value = "id DESC") private Set<Role> roles = new HashSet<>(); @OneToMany(mappedBy = "account") private Set<AccountPrivilege> accountPrivileges = new HashSet<>(); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Role
@Entity
@Table(name = ROLE,
uniqueConstraints = @UniqueConstraint(columnNames = { “name” }))
public class Role extends EntityId {
@Column private String name; @Enumerated private DomainLevel domainLevel; @ManyToMany @JoinTable(name = REL_ACCOUNT_ROLE, joinColumns = { @JoinColumn(name = "roles", referencedColumnName = "id") }, inverseJoinColumns = { @JoinColumn(name = "accounts", referencedColumnName = "id") }) private Set<Account> accounts = new HashSet<>(); @ManyToMany private Set<Page> pages = new HashSet<>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Page
@Entity
@Table(name = PAGE_PRIVILEGE)
public class Page {
@Id long id; private String pageName; private String url; private String iconClass; private String discription = ""; @ManyToMany(mappedBy = "pages") private Set<Role> roles = new HashSet<>();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
下来我要根据username查询Account,并且需要获取该账号的某个角色能访问的页面,即account.getRoles().get(0).getPages()
AccountRepository
@Transactional(readOnly = true)
@RepositoryRestResource(exported = false)
public interface AccountRepository extends JpaRepository {
//通过@EntityGraph来指定Account类中定义的NamedEntityGraph @EntityGraph(value="account.all",type=EntityGraphType.FETCH) public Account findOneByUsername(String username);
}
1
2
3
4
5
6
7
8
9
调用findOneByUsername即能够查询改Account,并且会同时查询出student、staff、roles、roles.pages。查看控制台的话会发现就生成一条sql语句,即只查询一次。
作者:大浪中航行
来源:CSDN
原文:https://blog.csdn.net/dalangzhonghangxing/article/details/56680629
版权声明:本文为博主原创文章,转载请附上博文链接!