hibernate之关于1+N的问题

简介:

    1+N问题,人叫做N+1问题,至今未统一,在这里我会告诉大家我为什么称之为1+N问题!

    

    什么情况下会产生1+N问题;


   在实际的项目开发中,我们配置的一对多,或者是多对一,在查询的时候会产生一种现象。


   例如,人(Person)和组(Group)


当我们在查询(多的一方)Hibernate会直接发SQL把相关的(一的一方)也查询出来;


当然,这种情况问题说大也不是特别大,但是,当我们数据量较大,数据库的性能就是不得不考虑的事情,ok?


首先我们得弄明白为什么我list() Person的时候,会发查询Group的语句?我们知道在many-to-oneFetchType默认是EAGEREAGER的意思可以理解为急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。所以Hibenrate加载Person属性值的时候发现Person的有一个字段属性与Group有关联,那么,Hibernate默认就立刻发请求将关联对象取出。


但是相反,我们list Group的时候,我们发现就不会出现这种现象,这是因为one-to-many默认的FetchTyp默认是LAZY,懒加载的意思,加载一个实体时,定义懒加载的属性不会马上从数据库中加载。只有我们需要的时候,比如group.getPersons().size() Hibernate检测你需要之后,才会发SQL请求!


ok,上面说了1+N问题出现的原理,那下面用程序来证明:

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
@Entity
@Table (name= "t_group" )
publicclass Group {
     private  Integer id;
     private  String name;
     private  Set<Person> persons=newHashSet<Person>();
     
     @OneToMany
     public  Set<Person> getPersons() {
        returnpersons;
     }
     publicvoid setPersons(Set<Person> persons) {
        this .persons = persons;
     }
     @Id
     @GeneratedValue
     public  Integer getId() {
        returnid;
     }
     publicvoid setId(Integer id) {
        this .id = id;
     }
     @Column (name= "g_name" )
     public  String getName() {
        returnname;
     }
     publicvoid setName(String name) {
        this .name = name;
     }
}
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
@Entity
@Table (name= "p_person" )
publicclass Person {
     private  Integer id;
     private  String name;
     private  Integer age;
     private  Group group;
     
     @ManyToOne
     @JoinColumn (name= "group_id" )
     public  Group getGroup() {
        returngroup;
     }
     publicvoid setGroup(Group group) {
        this .group = group;
     }
     @Id
     @GeneratedValue
     public  Integer getId() {
        returnid;
     }
     publicvoid setId(Integer id) {
        this .id = id;
     }
     @Column (name= "p_name" )
     public  String getName() {
        returnname;
     }
     publicvoid setName(String name) {
        this .name = name;
     }
     @Column (name= "p_age" )
     public  Integer getAge() {
        returnage;
     }
     publicvoid setAge(Integer age) {
        this .age = age;
     }
}


现在我们查询Person,看它发送的SQL语句


1
2
3
4
5
6
7
8
9
10
//我们发现,在查询Person的时候,发送的SQL语句
     @Test
     publicvoid findTest1(){
        Session s=sessionFactory.getCurrentSession();
        s.beginTransaction();
        List<Person> persons=s.createQuery( "from Person" ).list();
        for (Person person:persons){
            System.out.println(person.getName()+ "----" +person.getId());    }
        s.getTransaction().commit();
     }

查看SQL语句

(除了第一条是查询Person的语句,后面10条都是查询Group的语句)

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
11:02:13,036 DEBUGSQL:111 - 
     select
         person0_.id  as  id1_,
         person0_.p_age  as  p2_1_,
         person0_.group_id  as  group4_1_,
         person0_.p_name  as  p3_1_ 
     from
         p_person person0_
11:02:13,078 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,096 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,100 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,102 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,103 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,105 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,107 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,110 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,113 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
11:02:13,115 DEBUGSQL:111 - 
     select
         group0_.id  as  id0_0_,
         group0_.g_name  as  g2_0_0_ 
     from
         t_group group0_ 
     where
         group0_.id=?
张三0 ----1
张三1 ----2
张三2 ----3
张三3 ----4
张三4 ----5
张三5 ----6
张三6 ----7
张三7 ----8
张三8 ----9
张三9 ----10

这里我只需要查询的是Person的信息,只需要一条SQL语句就够了,但是现在多发了10条查询Group的语句,所以在这里我称之1+N问题。OK?


怎么解决Hibernate的1+N问题?


前面提到FetchType对,没错。

第一种方法,就是将many-to-one的FetchType值设为 LAZY  意思是告诉Hibernate  我需要的时候,你再发SQL请求。

1
@ManyToOne (fetch=FetchType.LAZY)


第二种方法,采用createCriteria这种事默认采用表连接的形式,也是可以解决。

第三种方法,left join fetch 做个表连接 也是可以的。

第四种方法,在一的一方加上@BatchSize  指定一个值(指定的值代表就是in()里面的值,发的SQL)也是可以的,但是不建议这么做,毕竟@BatchSize真正解决不了1+N这种问题,最多只能是少发几条SQL而已,大家有兴趣可以慢慢研究!


1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
     publicvoid findTest1(){
        Session s=sessionFactory.getCurrentSession();
        s.beginTransaction();
//     List<Person>persons=(List<Person>)s.createCriteria(Person.class).list();//解决方法1
        List<Person> persons=s.createCriteria(Person. class ).list();
     //  List<Person>persons=s.createQuery("from Person p left join fetch p.groupg").list();//解决方法3
        for (Person person:persons){
            System.out.println(person.getName()+ "----" +person.getId());
//         System.out.println(person.getGroup().getName());
        }
        s.getTransaction().commit();
     }


ok,1+N问题,是Hibernate面试最常考的题,同时也是性能优化一种常见的手段。本文详细讲解了1+N问题的原理和解决办法!有问题举手!











本文转自 小夜的传说 51CTO博客,原文链接:http://blog.51cto.com/1936625305/1570578,如需转载请自行联系原作者
目录
相关文章
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
708 90
|
安全 网络安全 定位技术
使用CDN服务对网页加载速度有何影响,如何选择合适的CDN提供商
使用CDN服务对网页加载速度有何影响,如何选择合适的CDN提供商
|
存储 数据建模 程序员
C 语言结构体 —— 数据封装的利器
C语言结构体是一种用户自定义的数据类型,用于将不同类型的数据组合在一起,形成一个整体。它支持数据封装,便于管理和传递复杂数据,是程序设计中的重要工具。
|
网络协议 Java
elasticsearch7.1 安装启动报错
elasticsearch7.1 安装启动报错
451 1
|
Java API Spring
Spring Boot 中的 AOP 处理
对 Spring Boot 中的切面 AOP 做了详细的讲解,主要介绍了 Spring Boot 中 AOP 的引入,常用注解的使用,参数的使用,以及常用 api 的介绍。AOP 在实际项目中很有用,对切面方法执行前后都可以根据具体的业务,做相应的预处理或者增强处理,同时也可以用作异常捕获处理,可以根据具体业务场景,合理去使用 AOP。
|
域名解析 缓存 负载均衡
【域名解析DNS专栏】DNS解析优化:减少延迟,提升用户体验
【5月更文挑战第24天】本文探讨了DNS解析优化策略,以减少延迟并提升用户体验。DNS解析涉及客户端查询、递归与迭代查询及返回结果。延迟可能源于服务器位置、负载、缓存策略和网络问题。优化措施包括使用高性能DNS服务、优化缓存、实施DNS负载均衡和预取技术。提供的HTML示例展示了DNS预取如何工作。通过评估、选择合适DNS服务、配置缓存、部署负载均衡及持续监控,可实现DNS优化,从而提高网站性能。
1395 0
【域名解析DNS专栏】DNS解析优化:减少延迟,提升用户体验
|
消息中间件 存储 Kafka
Kafka【环境搭建 02】kafka_2.11-2.4.1 基于 zookeeper 搭建高可用伪集群(一台服务器实现三个节点的 Kafka 集群)
【2月更文挑战第19天】Kafka【环境搭建 02】kafka_2.11-2.4.1 基于 zookeeper 搭建高可用伪集群(一台服务器实现三个节点的 Kafka 集群)
521 1
|
IDE JavaScript Java
【云IDE】CSDN云IDE的初探以及实战操作
【云IDE】CSDN云IDE的初探以及实战操作
385 0
|
消息中间件 JavaScript 小程序
Spring Boot 实现跨域的 5 种方式,总有一种适合你,建议收藏
Spring Boot 实现跨域的 5 种方式,总有一种适合你,建议收藏
|
人工智能 Java 大数据
【HTML5+Springboot】农产品质量溯源大数据管理系统源码
【HTML5+Springboot】农产品质量溯源大数据管理系统源码
1500 0