Hibernate-04 Hibernate 多表操作 Hibernate优化 检索策略

本地SQL



本地sql也支持命名查询。

可以将sql语句定义在hbm文件中,也可以使用注解。



本地命名sql注解定义



如果执行这个命名的sql会产生异常




出现问题的原因:是hibernate不知道执行select * from t_customer后如果将结果封装。



@NamedNativeQuery是指定结果封装的映射 ,@SqlResultMapping是具体的配置封装映射的类和属性.

多表操作

SQL多表操作

1.交叉连接  CROSS JOIN  会产生迪卡尔积

SELECT * FROM t_customer CROSS JOINt_order;



2.内连接  INNER JOIN … ON

SELECT * FROM t_customer AS c INNER JOIN t_order AS o ONc.id=o.c_customer_id;

注意同上面的交叉连接相同 , select*会因为用户表和订单表中有相同的id而导致报错, 所以我们可以通过更改其中一个id的名字来处理.



使用内连接它只能将有关联的数据得到。(比如没有订单的客户就查询不出来)

要查询出二者没有关联的数据, 就用外连接.(比如查询显示出没有订单的客户)

隐式内连接  使用 "逗号"将表分开,使用WHERE来消除迪卡尔积

SELECT * FROM t_customer AS c ,t_order o WHEREc.id=o.c_customer_id;

3.外连接  左外LEFT OUTERJOIN    右外RIGHT OUTER JOIN

OUTER可以省略

SELECT * FROM t_customer c LEFT OUTER JOINt_order o ON c.id=o.c_customer_id;




生成的相当于是一个中间表,  如果此时有三张表关联的话相当于这个生成的中间表再与第三张表再关联,如此类推, 任意多张表的联接都可以理清.



HQL多表操作

         Hql多表操作分类:

1.      交叉连接

2.      内连接

a)        显示内连接

b)        隐式内连接

c)        迫切内连接

3.      外连接

左外连接

迫切左外连接

右外连接

注意:在hibernate中有迫切连接的概念,而sql中没有。

内连接

显示内连接

显示内连接使用的是inner join …with

使用with编写条件



隐式内连接

隐式内连接和我们在sql中操作不一样,sql是通过”,”运算符来关联  , 但隐式是通过where后的点来联接的.



迫切内连接

迫切内连接得到的结果是直接封装到PO类中,而内连接得到的是Object[]数组,数组中封装的是PO类对象。

迫切内连接:相当于Hibearnte会发出一条SELECT语句将映射的实体所有的属性一次性全部查出来,封装到实体中。



外连接


注意:使用迫切左外连接时,如果需要指定条件进行查询时,使用的是where,而不是with。




Hibernate事务管理

事务介绍

问题:什么是事务?

         事务就是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全都失败。

问题:事务四个特性?

         原子性:不可分割

         一致性:事务在执行前后,要保证数据的一致。

         隔离性:一个事务在执行的过程中,不应该受到其它事务的干扰。

         持久性:事务一旦结束,数据持久化到数据库。

问题:不考虑事务的隔离性,会产生什么问题?

         脏读:一个事务读取到另一个事务的未提交数据

不可重复读:一个事务读取到另一个事务提交的数据(主要是指update),会导致两次读取的结果不一致。

虚读(幻读): 一个事务读取到另一个事务提交的数据(主要是指insert),会导致两次读取结果不一致.

问题:对于上述问题如何解决?

         我们可以通过设置隔离级别来解决.

         READ_UNCOMMITED读取未提交,它引发所有的隔离问题

         READ_COMMITTED  读已提交,阻止脏读,可能发生不可重复读与虚读.

         REPEATABLE_READ重复读  阻止脏读,不可重复读 可能发生虚读

SERIALIZABLE 串行化解决所有问题 不允许两个事务,同时操作一个目标数据。(效率低下)

ORACLE 默认的是事务隔离级别  READ_COMMITTED

MYSQL 默认的事务隔离级别  REPEATABLE_READ

Hibernate中设置事务隔离级别

hibernate.connection.isolation

它可取的值有 1 2 4 8

1代表的事务隔离级别为READUNCOMMITTED

2代表的事务隔离级别为READ COMMITTED

4.代表的事务隔离级别为 REPEATABLEREAD

8代表的事务隔离级别为SERIALIZABLE

在hibernate.cfg.xml文件中配置




Hibernate中session管理

 Hibernate提供了三种管理session的方式:

1.      Session对象的生命周期与本地线程绑定(ThreadLocal)

2.      Session对象的生命周期与JTA事务绑定(分布式事务管理)

3.      Hibernate委托程序来管理Session的生命周期

我们之前所使用的是第三种 ,通过程序获取一个Session对象,使用它,最后session.close();

在实际开发中我们一般使用的是前两种:

         主要介绍关于本地线程绑定Session。

         步骤:

1.      需要在hibernate.cfg.xml文件配置


2.      在获取session时不要在使用openSession而是使用getCurrentSession()方法。




关于getCurrentSession使用时的注意事项:


上述代码执行后,会产生问题




原因:使用getCurrentSession获取的与线程绑定的session对象,在事务关闭时,session对象也会close,简单说,就不需要我们在手动close, 将最后一句注掉就行了;

Hibernate优化方案

HQL优化

1.使用参数绑定

         1.使用绑定参数的原因是让数据库一次解析SQL,对后续的重复请求可以使用用生成好的执行计划,这样做节省CPU时间和内存。

         2.避免SQL注入

2.尽量少使用NOT

         如果where子句中包含not关键字,那么执行时该字段的索引失效。

3.尽量使用where来替换having

Having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销

4.减少对表的查询

         在含有子查询的HQL中,尽量减少对表的查询,降低开销

5.使用表的别名

当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列上,这样一来,可以减少解析时间并减少列歧义引起的语法错误。

6.实体的更新与删除

         在hibernate3以后支持hql的update与delete操作



批量update:





批量delete:






一级缓存优化

一级缓存也叫做session缓存,在一个hibernatesession有效,这级缓存的可干预性不强,大多于hibernate自动管理,但它提供清除缓存的方法,这在大批量增加(更新)操作是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear.




检索策略(抓取策略)

延迟加载

延迟加载是hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。

load方法采用的策略延迟加载.

get方法采用的策略立即加载。

检索策略分为两种:

1.      类级别检索

2.      关联级别检索

类级别检索

类级别检索是通过session直接检索某一类对应的数据,例如

Customer c=session.load(Customer.class,1)

Session.createQuery(“fromOrder”)

类级别检索策略分为立即检索与延迟检索,默认是延迟检索,类级别的检索策略可以通过<class>元素的lazy属性来设置 ,默认值是true

在hbm配置文件中设置




在类中使用注解


如果将lazy设置为false,代表类级别检索也使用立即检索。这时load与get就一样,都是立即检索。


测试类:


如果对一个延迟代理对象进行初始化?



延迟对象初始化:

/**

     * 解决延迟加载Session关闭问题

     */

    @Test

    public void test01() {

       SessionFactory sessionFactory =newConfiguration().configure().buildSessionFactory();

       Session session = sessionFactory.openSession();

      

       session.beginTransaction();

      

       Customer customer = session.load(Customer.class, 1);

       // 注意:Hibernate.initialize方法调用要放在session.close之前

       Hibernate.initialize(customer);

      

       session.getTransaction().commit();

       session.close();

 

       System.out.println(customer);

      

       sessionFactory.close();

    }


关联级别检索

         查询到某个对象,获得其关联的对象或属性,这种称为关联级别检索,例如

         c.getOrders().size()

         c.getName()

         对于关联级别检索我们就要研究其检索策略(抓取策略)

检索策略(抓取策略)

抓取策略介绍

 指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。

一对一 <one-to-one>

一对多(多对一)<set>下有<one-to-many> <many-to-one>

多对多<set>下有<many-to- many>

我们主要是在<set>与<many-to-one>或<one-to-one>上设置fetch  lazy

例如:查询一个客户,要关联查询它的订单

客户是一的一方,在客户中有set集合来描述其订单,在配置中我们是使用

<set>

         <one-to-many>

</set>..

可以在set标签上设置两个属性  fetch   lazy

Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查

Lazy 控制SQL语句何时发送

例如:在查询一个订单时,要查询客户信息

<many-to-one> 或<one-to-one>

也可以设置fetch  lazy

Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查

Lazy 控制SQL语句何时发送

总结:

讲解抓取策略

在两方面设置

<set fetch=”” lazy=””>

<many-to-one fetch=”” lazy=””>

<one-to-one>

注解配置抓取策略

问题:如何使用注解来设置

在<set>设置的fetch与lazy可以使用下面注解来描述

在一的一方给多方配置抓取策:”



在<many-to-one>或<one-to-one>上如何设置 fetch与lazy

在多的一方配置抓取策略:





一方set上的fetch与lazy(对抓取多方的设置)

set上的fetch与lazy它主要是用于设置关联的集合信息的抓取策略。

Fetch配置中可取值有:

1.      SELECT 多条简单的sql   (默认值)

2.      JOIN 采用迫切左外连接

3.      SUBSELECT将生成子查询的SQL  ()

lazy可设置的值有:

1.      TURE 延迟检索   (默认值)

2.      FALSE 立即检索

3.      EXTRA 加强延迟检索(及其懒惰)



第一种组合

会首先查询客户信息,当需要订单信息时c.getOrders().size()的时候,才会关联查询订单信息。

第二种组合

当查询客户信息时,就会将订单信息也查询,也就是说订单信息没有进行延迟。

第三种组合

当查询客户信息时,不会查询订单信息,当需要订单的个数时,也不会查询订单信息,

只会通过count来统计订单个数。

当我们使用size(),contains()或isEmpty()方法时不会查询订单信息(是真的懒)。

第四种组合

如果fetch选择的是join方案,那么lazy它会失效

生成SQl将采用的是迫切左外连接(left outer join fetch)

会立即查询。



Hibernate生成的sql语句l:


第五种组合

会生成子查询,但是我们在查询订单时采用的是延迟加载

消除类中的黄色警告的方法,在类名上添加一个@SuppressWarnings


注意:因为上述代码就查询的是一条记录, 所以根本不需要子查询. 注意:SUBSELECT对单条数据的查询和SELECT效果一样。(数据库中要准备>1条的CustomerOrder数据

Hibernate中生成的sql语句:



首先明确什么是子查询:一个子查询必须被圆括号包围起来(经常是SQL聚集函数的圆括号)。甚至相互关联的子查询(引用到外部查询中的别名的子查询)也是允许的。

HQL子查询只可以在select或者where子句中出现。

明显我们可以看出:

首先只查客户信息, 当我们需要订单的时候再查订单.

在查询订单的个数时 , 采用了子查询 , 是先把客户所有的id查出来 , 然后采用in的方案来确定订单的查询条件.

第六种组合

会生成子查询,在查询客户信息时,就会将订单信息也查询出来(没有懒加载)

第七种组合

在查询订单时,只会根据情况来确定是否要查询订单信息,如果不需要,例如我们

程序中size操作,及其懒惰那么就只会发出select count(*) fromOrder where c_customer_id=?

只查询count而不用去查询订单信息因此也不会涉及到子查询了.

多方的fetch与lazy

<set fetch lazy>它主要是设置在获取到一的一方时,如果去查询多的一方。

在<many-to-one>或<one-to-one>如果去查询对方。

对于程序就是在多的一方如何查询一的主方信息

例如:获取到一个订单对象,要查询客户信息。

Fetch可取值:

         select默认值,代表发送一条或多条简单的select语句

         join  发送一条迫切左外连接

lazy可取值

         false不采用延迟加载

         proxy默认值 是否采用延迟,需要另一方的类级别延迟策略来决定

         no-proxy不用研究



第一种组合


注意:Customer的类级别延迟策略



当我们执行时,会首先发送一条sql只查询订单信息,客户信息会延迟,只有真正需要客户信息时,才会发送sql来查询客户信息.

第二种组合

注意:Customer的类级别延迟策略



当查询订单时,就会将客户信息也查询到,原因是Customer它的类级别延迟为false,也就是立即查询。

第三种组合

当查询订单时,不会对客户信息进行延迟,立即查询客户信息

第四种组合


如果fetch值为join,那么lazy失效。

会发送一条迫切左外连接来查询,也就立即查询。

Hibernate中生成的sql语句:



批量抓取

我们在查询多个对象的关联对象时,可以采用批量抓取方式来对程序进行优化.

要想实现批量抓取:

可以在配置文件中 batch-size属性来设置

可以使用注解 @BatchSize(size=4)   其中4是一次抓取的条数

可以采用批量抓取来解决N+1问题.

查询客户,查询订单


----------------------------


使用的都是默认的抓取策略:


为了解决N+1的问题:

可以在客户配置文件中配置batch-size,是在<set>标签上:

同理如果是配置hbm.xml映射文件的话可以在<set>标签中配置BatchSize:



为什么设置批量抓取呢, 因为我们在查客户以后要抓订单所以我们规定设置一下批量抓取的个数.

配置批量抓取后Hibernate中生成的sql:



首先查客户信息, 但是在查客户的订单信息时 , 它不再是每一个客户发一条sql来查询订单 , 而是直接通过一个in的方式把我们的所有客户的订单查询出来, 这样就减少了与数据库的交互.hibernate主要是通过in来提高查询效率的 ,其中in后的问号个数就是我们设置的批量抓取数量, 如果此时我们有三个订单, 但是我们设置的批量抓取个数是2的话我们可以发现执行的时候Hibernate生成的sql中在之后还需要生成一条sql来查询第三个order的信息.所以抓取数量要根据我们的需求和环境来设定, 但是尽量不要过大,不超过50 .


查询订单,查询客户


查订单再查客户的情况的N+1问题解决, 并不是同以上的解决方案在order上设置批量抓取来解决.我们需要明确下面的东西.

订单与客户,客户它是一个主表,订单是一个从表。

在设置批量抓取时都是在主表中设置

在配置文件中在主表的<class>标签上设置batch-size

在注解使用中

         



如果是要在映射配置文件上设置, 我门可以在<class>标签上设置BatchSize.

注意:无论是根据哪一方来查询别一方,在进行批量抓取时,都是在父方来设置

         如果是要查询子信息,那么我们是在<set>上来设置batch-size,如果是从子方来查询父方,

         也是在父方设置在<class>设置batch-size.
        

父与子区分:

         有外键的表是子(从)表 , 关联方就是父(主)表

补充:

养成复杂功能分解的习惯,将很长的代码写成子程序的形式来完成.



猜你喜欢

转载自blog.csdn.net/changudeng1992/article/details/78220407