hibernate之关于1+N的问题


【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问题出现的原理,那下面用程序来证明:

@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;
    }
}
@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语句


//我们发现,在查询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的语句)

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请求。

@ManyToOne(fetch=FetchType.LAZY)


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

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

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


@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问题的原理和解决办法!有问题举手!





本文出自 “诺言永远依恋小柴、、、” 博客,请务必保留此出处http://1936625305.blog.51cto.com/6410597/1570578

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。