本文共 5233 字,大约阅读时间需要 17 分钟。
1+N问题,也有人叫做N+1问题,至今未统一,在这里我会告诉大家我为什么称之为1+N问题!
什么情况下会产生1+N问题;
在实际的项目开发中,我们配置的一对多,或者是多对一,在查询的时候会产生一种现象。
例如,人(Person)和组(Group)
当我们在查询人(多的一方)Hibernate会直接发SQL把相关的组(一的一方)也查询出来;
当然,这种情况问题说大也不是特别大,但是,当我们数据量较大,数据库的性能就是不得不考虑的事情,ok?
首先我们得弄明白为什么我list() Person的时候,会发查询Group的语句?我们知道在many-to-one的FetchType默认是EAGER,EAGER的意思可以理解为急加载,加载一个实体时,定义急加载的属性会立即从数据库中加载。所以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问题的原理和解决办法!有问题举手!