hibernate(九) 二级缓存和事务级别详讲

      序言sql

          这算是hibernate的最后一篇文章了,下一系列会讲解Struts2的东西,而后说完Struts2,在到Spring,而后在写一个SSH如何整合的案例。以后就会在去讲SSM,在以后我本身的我的博客应该也差很少能够作出来了。基本上先这样定下来,开始完成hibernate的东西把。这章结束后,我会将我一些hibernate的资料奉上,供你们一块儿学习。数据库

                                          ---WZY缓存

 

1、概述安全

    这章总的分两大块来说解,session

        第一大块,hibernate的事务管理。,对于hibernate的事务管理来讲,若是以前学过数据库的事务管理,那么在这里就顺风顺水了。若是没学过,第一次遇到,那也不要紧,我会详细解释其中的内容。并发

 

        第二大块,hibernate的二级缓存机制。这个看起来好高大上啊,若是历来没了解过二级缓存的人确定以为他很难,可是学过会发现,真的是so easy。最起码会知道什么是二级缓存,做用是什么。ide

 

2、hibernate的事务管理学习

      2.一、回顾数据库的事务的特性和隔离级别测试

            2.1.一、什么是事务?atom

                  事务是一组业务逻辑,好比A去银行给B转钱,A转了100块给B(update语句更新A的钱减小一百),B收到钱(update语句更新B的钱增长一百),这转钱整个过程称为事务,注意,不要以为A转钱给B,A作完了操做,就是事务了,要记住事务是一组业务逻辑,两个在一块儿才是事务。在好比有一个数字5,如今有一个事务A,事务A作的事情就是将5变为4,该事务要作的事情有,拿到5,而后5-1,而后数据库中的5变为了4,这才算这个事务A真正的成功了,能够那么说,业务逻辑太抽象了,事务就是一组操做,只有整个操做在一块儿才算是一个事务。就向上面说的,转钱也是一个事务,它作的操做就只有两个,A减小钱,B增长钱。

            2.1.二、事务的特性ACID

                A(atomicity):原子性:事务不可被划分,是一个总体,要么一块儿成功,要么一块儿失败

                C(consistence):一致性,A转100给B,A减小了100,那么B就要增长100,增长减小100就是一致的意思

                I(isolation):隔离性,多个事务对同一内容的并发操做。

                D(durability):持久性,已经提交的事务,就已经保存到数据库中,不能在改变了。

            2.1.三、事务隔离性产生的问题

                跟线程安全差很少,多个事务对同一内容同时进行操做,那么就会出现一系列的并发问题。必定要注意,这个是两个或者多个事务同时对一个内容进行操做,是同时,而不是先事务A操做完,而后事务B在操做,这个要理解清楚。

                2.1.3.一、脏读:一个事务 读到 另外一个事务 没有提交的数据。

                    A给B转100块钱(事务A),A在ATM机上转,将100块钱放到ATM机里了,而后ATM机会最后询问A,肯定转帐100给B吗,A还没点肯定这个时候,B在另一个ATM机上就发现帐户上多了100块钱,而后高兴的取走了这100块钱,B取钱(事务B)可是此时A以为不行,以为仍是拿现金给B比较好,而后就点了取消,把放到ATM机中的100块钱给拿了回来。这其中,A转钱给B(事务A)、B取钱(事务B),也就发生了事务B读到了事务A没有提交的数据,也就是脏读。注意:要明白事务是什么,才能理解这些东西。还有,刚开始接触可能会以为这是不可能发生的呀,怎么会读到尚未提交的数据呢?这里只是说明这个是一个问题,现实生活中确定没有呀,银行也不会出现这种问题,由于已经所有解决掉了,可是这种问题确定是存在的,该如何解决呢?这就是咱们后面须要讨论的东西。如今只须要知道事务隔离会产生这种问题就好了。

 

                2.1.3.二、不可重复读:一个事务 读到 另外一个事务 已经提交的数据(update更新语句)

                      解释:有时候不可重复读是一个问题,有时候却不是。这要看业务需求是怎么样的,就比如银行转钱的事情,事务A(A给B转钱),事务B(B取钱),A在ATM机上插卡转钱给B,同时B也将银行卡插入ATM机中准备查看A是否转了钱给B,当A事务结束后,也就是A转帐成功后,B就查到了本身帐户上多了100块钱,这就是事务B读到了事务A已经提交的数据。这个例子不可重复读就不是个问题。此业务逻辑中,就不须要解决这个问题。在别的业务中,可能这个就是个问题了。好比,一个公司,每月15号给员工结算工资,工资数是从上个月15号到这个月15号根据每一个人提交的工做量来结算的,可是会计师A在这个月15号从数据库中拿每一个员工的工做记录量的数据作工资的统计的同时,员工B在次提交了一次工做量(将数据库中B的工做量增长了),此时会计师A在结算员工B的工资时,就会把前面30天的工做量和他今天提交的工做量一块儿算工资,到了下个月15号,又会把员工B此次提交的工做量算在它当前月的总工做量中,这样一来,就至关于给B多算了一次工做量的工资。这样就不合理了。

 

                2.1.3.4、虚度(幻读):一个事务 读到 另外一个事务 已经提交的数据(insert插入语句)

                    这个跟不可重复读描述的问题是同样的,可是其针对的事务不同,不可重复读针对的是作更新的事务,好比转钱呀等,都是作更新的事务,而这个虚读针对的是作插入的事务,好比,在工地有不少人作事,工地有规定,作了事的中午才包饭,到了中午的时候,工地负责人要去给作事的工人买盒饭,就要统计人数,如今工地也会用电脑了,负责人就到电脑上查有多少人作事,来决定买多少盒饭,就在查人数的时候,另外一个专门招工人的负责人招到一个工人,就把该工人的信息输入到数据库里面,而后买盒饭的负责人在电脑上一查数据库,发现有N我的,刚招进来的工人也在其中,而后就也给那个没作事的,刚招进来的员工买了盒饭,这是不符合规定的。这只是举一个这样的例子,帮助你们理解,一个盒饭也不贵,以为无所谓,可是若是是涉及很重要的东西时,就不能出现这种问题。

           2.1.四、事务隔离级别,用于解决隔离问题 

                2.1.4.一、read uncommitted :读未提交,一个事务 读到 另外一个事务 没有提交的数据,存在问题3个,解决0个问题

                2.1.4.二、read committed:读已提交,一个事务 读到 另外一个事务 已经提交的数据,存在问题2个,解决1个问题(脏读问题)

                2.1.4.三、repeatable read:可重复读,一个事务 读到重复的数据,即便另外一个事务已经提交。存在问题1个,解决2个问题(脏读、不可重复读)

                2.1.4.四、serializable:单事务,同时只有一个事务能够操做,另外一个事务挂起(暂停),存在问题0个,解决3个问题(脏读、不可重复读、虚读)

              注意:必定要搞清楚上面三个问题(脏读、不可重复读、幻读)是什么样的状况,你才能知道这四种隔离级别为何可以解决这些问题。切记,若是看个人话仍是以为这几个问题模糊不清,就请留言告诉我你的疑问,由于若是不明白这三个问题,那么后面你将一直会混淆。

           2.1.五、使用MySQL来进行隔离级别的演示

              这里就不写代码了,直接上文字,让大家熟悉一下事务隔离级别的使用。

              顺便说一句,MySQL默认的隔离级别:repeatable read  Oracle默认的隔离级别:read committed

              MySQL默认事务提交的,也就是在cmd中每执行一条sql语句就是一个事务,因此若是你要进行实验就必须先关闭MySQL的自动事务提交,并改成手动,set autocommit=0

              2.1.5.一、read uncommitted 

                A隔离级别:读未提交,会发生脏读问题

                AB同时开始事务,

                A先查询  --正常数据

                B更新,但未提交

                A在查询  --读到B没有提交的数据

                B回滚  --B没有提交数据,回滚的话,就至关于刚才的更新语句并无执行

                A再查询 --读到回滚后的数据,也就是原来的正常数据。

              2.1.5.二、read committed

                A隔离级别:读已提交

                AB同时开启事务

                A 先查询  --正常

                B 更新,但未提交

                A再查询  --获得的仍是以前的数据,并无拿到B没有提交的数据, 解决问题:脏读

                B 提交

                A 再查询 -- 已经提交的数据。问题:不可重复读(到这里就不要在纠结为何不可重复读是个问题了,上面已经解释清楚了,根据不一样的业务,多是问题,也可能不是)

              2.1.5.三、repeatable read

                A隔离界别:可重复读,保证当前事务中读到的是重复的数据

                AB 同时开启事务

                A 先查询  --正常

                B 更新,但未提交

                A 再查询  -- 以前数据,解决:脏读

                B 提交

                A 再查询  -- 以前数据,解决:不可重复读

                A 回滚|提交

                A 再查询  -- 更新后数据,新事务得到最新数据

              2.1.5.三、serializable

                A隔离级别:串行化,单事务

                AB 同时开启事务

                A 先查询  --正常

                B 更新  -- 等待 (对方事务A结束或者超时B才能进行。)

 

          2.1.六、丢失更新问题 lost update

              这个丢失更新问题也是属于事物隔离性产生的问题之一,可是不一样上面所说的三个,上面所说的脏读、不可重复读、虚读,都是一个事务 拿到了 另外一个事务所提交或者未提交的数据而产生的问题,而丢失更新并不拿对方事务所提交的数据,那丢失更新描述的是一个什么样的问题呢?

              A 查询数据,username = 'jack' ,password = '1234'

              B 查询数据,username="jack", password="1234'

              A更新密码,用户名不变 username='jack',password='456'  //A将密码更新完后,将其保存到数据库中了

              B更新用户名,username='rose',password='1234'  //B更新以后,数据库中的数据就为 username='rose',password='1234'

              丢失更新:最后更新数据,将前面更新的数据给覆盖了。那A以后就发现本身刚设置的密码登陆不上了,这就出现了丢失更新问题,解决的方法有两种

            解决方法一:

                  乐观锁

                     认为丢失更新必定不会发生,很是乐观,在数据库表中添加一个字段,能够说是标识字段把,用于记录操做次数的,好比若是对有人拿到了该行记录作了更新操做,该字段就加1。而后下一个拿到该记录的人要先将拿到的记录的标识和数据库中该记录的标识作对比,若是同样,则能够修改,而且修改后标识(版本)+1,若是不同,先从数据库中查询,而后在作更新。举个例子

                     A 查询数据,username = 'jack' ,password = '1234',version=1

                     B 查询数据,username="jack", password="1234',version=1  //AB同时拿到数据库中数据,且version读为1

                     A更新密码,用户名不变 username='jack',password='456',version=2  //先和数据库中该行记录的version作对比,拿到的version是1,跟数据库中同样,因此能作更新,A将密码更新,version+1,而后将其保存到数据库中(注意,这里写的是A更新以后的的数据。 不要搞混了。)

                     B更新用户名,username='rose',password='1234',version=1  //B想要更新时,先和数据库中该条记录的版本号作对比,发现不同,而后查询

                     B从新查询数据, 用户名不变 username='jack',password='456',version=2  //而后在进行对比,此次version同样了,B就能够实现更新操做了。

                     B更新用户名,username='rose',password='456',version=3  //更新后,version+1 

                   

            解决方法二:

                  悲观锁

                     认为丢失更新必定会发生,此时采用数据库锁机制,也就是至关于谁操做了该记录行,就会在上面加把锁,别人进不去,只有等你操做完以后,该锁就释放,别人就能够操做了。跟那个隔离级别单事务差很少。可是锁也分不少种。

                     读锁:共享锁,你们能够一块儿读数据,可是不能一块儿操做(更新,删除,插入等)

                     写锁:排他锁,只能一个进行写,也就是上面咱们说的原理。

        

      2.二、hibernate中对事务产生的隔离性问题以及解决方案

           上面经过很大的篇幅讲解数据库的事务相关问题,就是为了讲解hibernate中的事务作铺垫,懂了上面这些,那么这里就顺风顺水了。

           2.2.一、hibernate中设置事务隔离级别,隔离级别是为了解决事务隔离性产生的问题的

              在hiberante.cfg.xml文件中配置 hibernate.connection.isolation 隔离级别

              有四种隔离级别可选择,后面的数字表示在设置隔离级别的时候,直接写数字也是能够表明对应的隔离级别的,好比 hibernate.connection.isolation 4 跟hibernate.connection.isolation Repeatable read 是同样的。

                Read uncommoitted isolation  1

                Read committed isolation    2

                Repeatable read isolation    4

                Serializable isolation       8

              

          2.2.二、hibernate中丢失更新问题的解决

              悲观锁: 就是认为必定会发生丢失更新问题,采起锁机制

                  User user = (User) session.load(User.class,1,LockMode.UPGRADE);

              乐观锁:

                  hibernate 为Customer表 添加版本字段

                    1) 在User类 添加 private Integer version; 版本字段

                    2) 在User.hbm.xml 定义版本字段

                      <!-- 定义版本字段 -->

                      <!-- name是属性名 -->

                      <version name="version"></version>

                      

                  若是产生了丢失更新就会报异常

                    

 

           总结:

              一、若是知道了数据库中事务的知识,那么在hibernate中就很是简单,只是简单的配置一下就OK了。因此在hibernate的事务讲解这里篇幅就比较少,重要的仍是须要弄懂前面的知识。很重要。   

 

3、hibernate的二级缓存

          说点废话,二级缓存理解起来真的很是很是简单,你们不要以为怕,就三个内容,知道什么是二级缓存,如何使用它,就没了。

      3.一、什么是二级缓存

           咱们知道一级缓存,而且一级缓存的做用范围就在session中,每一个session都有一个本身的一级缓存,而二级缓存也就是比一级缓存的做用范围更广,存储的内容更多,咱们知道session是由sesssionFactory建立出来的,一个sessionFactory可以建立不少个session,每一个session有本身的缓存,称为一级缓存,而sessionFactory也有本身的缓存,存放的内容供全部session共享,也就是二级缓存。 是否是很简单?还不理解看下面我画的一张图就一目了然了。

                

          一级缓存:保存session中,事务范围的缓存(通俗点讲,就是session关闭后,该缓存就没了,其缓存只能在session的事务开启和结束之间使用)

          二级缓存,保存在SessionFactory,进程范围内的缓存(进程包括了多个线程,也就是咱们上面说的意思,A线程可能拿到一个session进行操做,B线程也可能拿到一个session进行操做,可是A和B读能访问到SessionFactory中的缓存,也就是二级缓存,这里只是拿A,B说事,可能有一个线程刚建立出来session,也能拿到二级缓存中的数据)

          

      3.二、二级缓存的做用?优势

           这个可能要到实际开发工做中才会知道二级缓存有哪些用处,如今给出一些我认为比较好的答案,由于我还没真正到去工做,因此目前也只是理解其内容。

          。。。。

 

      3.三、二级缓存的内部结构

         类缓存区域

             好比:session.get(Customer.class,1); //这就是类缓存区域

         集合缓存区域

             customer.getOrders();  //就是存放Orders集合的内容的缓存区域就叫集合缓存区域  

         更新时间戳区域             

         查询缓存区域

             这几个在后面会详细讲到,如今不讲,想直接看的就跳过看后面测试实例。

      3.四、二级缓存的并发访问策略配置。

          为何要讲解这个呢?想一下,二级缓存是sessionFactory中的,其sessionFactory建立出来的session均可以共享它,因此其中就会出现并发问题,也就是咱们一开始讲的一些事务隔离性问题。为了解决这些问题,hibernate也提供了相应的方法,就是二级缓存的并发访问策略,总共有四种,经过一张表格来看把,经过下面这张图,咱们应该就知道了其实跟开始讲的差很少,换了一个名词而已。而且若是要使用二级缓存,就必须配置这个病房访问策略,否则是用不了二级缓存的,就比如你已经有了二级缓存,其中也有内容,可是你没有设置访问方式,就访问不出来。

                    

          在配置二级缓存以后,就要相应的配上其二级缓存并发访问策略。

 

      3.五、如何配置二级缓存?   

          分两大步,第一步单纯配置二级缓存,第二步配置二级缓存的并发访问策略。第三步,配置ehcache.xml

          3.5.一、配置二级缓存

               要使用二级缓存,须要导入其它的组件来帮咱们完成,而hibernate自己是有一个默认的二级缓存组件,可是日常咱们不用,通常是用EhCacheProvider,来了解一下hibernate所能支持的二级缓存组件提供商

                    

               一、咱们使用Ehcache,因此导入其jar包

                    

               二、在hibernate.cfg.xml文件中配置,开启二级缓存,

                     

               三、在hibernate.cfg.xml文件中配置二级缓存提供商 

                    

               开启二级缓存就只须要三步,就上面三步,而后开始配置咱们的并发访问策略。

                    

          3.5.二、配置二级缓存的并发访问策略

               二级缓存组件所能支持的并发访问策略。 为何

                      

               今天咱们要用的就是EHCache,它不支持Transactional这种并发访问策略,因此咱们使用的是read-write这种并发访问策略,read-write提供的就是read committed事务隔离级别,可以防止脏读,具体的功能就看上面的表格。

               配置方案有两种

                    一、xxx.hbm.xml文件中配置

                       能够在<class>标签下配置<cache usage=”read-write”>  类级别      

                       能够在<set>标签下配置<cache usage=”read-write”>  集合级别 

                    二、hibernate.cfg.xml文件中配置

                        

          3.5.三、配置ehcache.xml文件  

              将 ehcache-1.5.0 jar包中 ehcache-failsafe.xml 更名 ehcache.xml 放入 src。 这很是简单。若是使用的别的组件缓存,则在相同位置也有相似的文件。

   

          

      3.六、测试二级缓存的存在

           3.6.一、搭建测试环境

              而且将上面的步骤本身搭建好,三步走,注意:使用的业务逻辑是Dept-Staff,也就是部门和职工的关系。

                一、配置二级缓存,也就是将一些组件缓存提供商弄好

                    

                    

          

                二、配置并发访问策略,这里其实就自由配置了,若是你想在二级缓存中只存取某个类的值,那么就在xxx.hbm.xml中配置一下其访问策略

                    这里,我先只对dept进行read-write的访问策略,而后等会进行测试

                      

               三、添加ehcache.xml

                       

          3.6.二、测试二级缓存是否存在

                      

                      

         3.6.三、注意get/load能够从二级缓存中获取数据,而query的list不能从二级缓存获取数据,可是其查询结果会存入二级缓存。

                       

                      

              小结:

                 hql作的查询可以存入一级缓存和二级缓存,可是不可以从二级缓存中拿数据

                 get\load可以将其查询数据插入一级缓存和二级缓存,也可以从一级二级缓存中拿数据。

        3.6.四、一级缓存数据会同步二级缓存

                       

                      

                注意:若是是用hql来更新修改name,那么就不会同步二级缓存,连一级缓存中的数据也不会改变,hql是直接针对数据库来进行修改的,而set()方法是经过hibernate中的快照区,而不是直接对数据库进行操做。

         3.6.五、测试二级缓存中的四种缓存区域

             3.6.5.一、类缓存区域

                  类缓存区域,经过id查询到的对象,就将其放入类缓存区域,其区域中缓存的都是PO对象,而不是单独的一些属性,值,而是完整的对象,好比上面测试是否有二级缓存,就是将id为2的dept对象存入二级缓存中,而不是就存dept的name,或者是id,若是只存dept的name,或者别的字段属性,那么就会放入查询缓存中,而不是放入类缓存区域。

             3.6.5.二、集合缓存区域

                  记得之前说的集合级联关系吗,其实这个也同样,好比这个Dept-Staff的例子中,dept.getStaffSet(); 这查询出来的就将其放入集合缓存区域,其区域内也所有是PO对象。

                  设置二级缓存的访问策略,因为要测试集合缓存,那么其set中就要设置访问策略,而且注意一点,集合级别的缓存依赖于类级别的缓存,也就是说,set中设置cache还不够,由于要缓存staff,因此必须将staff.hbm.xml也设置访问策略。同时咱们只是测试集合缓存,那么对查询出来的Dept能够不设置访问策略了,有没有他读不要紧,由于咱们不须要用。也就是下图中第一个用红框框起来的,

                        

                      staff.hbm.xml

                      

                  测试从dept获取到的staff是否可以存入二级缓存中。

                        

                        

             3.6.5.三、时间戳缓存区域

                  存放了对于查询结果相关的表进行插入,更新,删除操做的时间戳,Hibernate经过时间戳缓存区域来判断被缓存的查询结果是否过时,若是过时了则从数据库中拿数据,没过时则直接从缓存中拿数据。通俗点讲,就三步

                  一、查询结果放到二级缓存中,此时记录一个时间为T1

                  二、当有操做直接更改了数据库的数据时,好比使用hql语句,就会直接对数据库进行修改,而不会改变缓存中的数据。此时记录时间为T2

                  三、当下次在查询记录时,会先将T1和T2进行比较,若是T2>T1,则说明缓存中的数据不是最新的,那么就从数据库中拿出正确的数据,若是T2<T1,就说明没有对数据库进行过什么修改操做,那么就能够直接从缓存中获取数据。

                  解惑:若是没有T1和T2的比较,那么会出现咱们查询到的数据不是准确的,由于就像上面第二步所说的,数据库的数据会和缓存中的数据不同,什么读不作就从缓存中拿数据,就会出现错误。

                  测试:

                  

                    

 

             3.6.5.四、查询缓存区域

                  这个在将类缓存区域差很少已经解释过了,就是经过查询的结果不是一个po对象,而是一些零散的字段属性,那么就存入该区域,可是使用它是须要两个东西

                  一、在hibernate.cfg.xml文件中配置

                      

                  二、在使用query操做时,须要指定是否从查询缓存中获取数据

                      

                  测试:

                      

                      

                      

 

        以上写的访问策略全是使用的xxx.hbm.xml来配置,如今我将上面例子的使用hibernate.cfg.xml来配置访问策略的代码写一下。

                  

          上图说了两个缓存区域,时间戳缓存区域不用设置,而查询缓存区域就经过别的方式去设置了。 这就是咱们所要讲解的四个缓存区域。很简单把。

 

 

4、总结

         这篇文章本该早写完的,可是中途由于一些事情而耽误了,今天上午终于将它完结了。hiberante我以为差很少就这些东西了。 但愿对你们有所帮助

相关文章
相关标签/搜索