Hiberante面试

JAVA学习 专栏收录该内容
8 篇文章 0 订阅

1:进行更新操作的时候,为什么一般都是需要先获取到数据库中原数据,然后再把新对象的内容进行复制,然后再执行更新操作呢?比如下面的代码(1)而不是直接代码(2)

  1. 其中Persion为一个pojo类
  2. 参数:currentPerson表示从controller层获取到从页面中传递过来的内容---这个很好理解吧。
  3. Service层中:
  4. public void updatePerson(Person currentPerson){
  5. //获取到需要修改内容在数据库中原始的内容信息,也就是调用sesssion.load(id)这hibernate方法,也可以是get方法
  6. Person person = personDao.loadPerson(currentPerson.getId());
  7. //将传递过来的对象的属性内容进行复制到获取的对象中
  8. BeanUtil.copyProperties(currentPerson , person);
  9. //进行hibernate中的session更新操作
  10. personDao.updatePerson(person);
  11. }

代码(2)内容:

  1. 其中Persion为一个pojo类
  2. 参数:currentPerson表示从controller层获取到从页面中传递过来的内容---这个很好理解吧。
  3. Service层中:
  4. public void updatePerson(Person currentPerson){
  5. //进行hibernate中的session更新操作
  6. personDao.updatePersoncurrentPerson)
  7. }
解答: 在上面的service层的这个更新的方法中(当然也可以是在controller层或者是strus2的方法中,只是调用的形式有变化而已),注意到了是先获取到数据库中对应的主键id的内容,然后再根据修改的内容进行的更新操作的。当然,这个问题并不是一定会的,可以说是一种规范吧,就是能够更安全。原因就在于,在hibernate的工作机制中,对应在同一区域(Hibernate缓存区)和时间是不能够有相同主键内容的数据存在的,也就是说不能有两个相同主键的持久化类存在。更为通俗的讲解就是,如果当前需要更新的实体,刚好的主键id对应着Hibernate缓存区中,已经存在了,那么这样就会报一个different object with the same identitifer的错误,很明显就是说存在了相同id的两个持久化类了,所以,通过先拿到当前修改id的原始内容,然后进行修改属性内容之后,再把改对象放入到更新操作中,就相当于从hibernate缓存区先取出已经存在的,然后修改了,再放回去,这样就肯定保证了只会存在一个唯一了,所以就防止不会出现上述的这个错误了。当然,如果不进行先获取,再保存也可以,只是这样增加了安全性,所以记住,如果进行修改操作,那么就需要这样的步骤才是最安全的。

2:如何解决hibernate中,懒加载load方法出现,no session的错误?

解答:这个问题其实是开发中,很经过会遇到的一个问题。之所以出现这样的问题是在于,session对象当执行了相应的crud操作之后,就会结束生命周期了,而当session接受之后,然后又访问刚获取到的对象的相应的内容,那么因为是load()方法是进行懒加载,当真正进行使用该对象的时候,再会真正的去获取,而此时session对象已经关闭了,所以肯定就会出现上面的问题了。就代码解释就是如下的代码:

  1. 其中Persion为一个pojo类
  2. 参数:id表示从要进行获取到主键id---这个很好理解吧。
  3. Dao层中:
  4. public void loadOpPerson(Long id){
  5. Person person = this.hibernateTemplate.load(Person.class , id);
  6. person.getName(); //执行到这就会报no session的错误
  7. }
原理上面,我解释过了,所以,来说一下如何解决这个问题,其实很好解决,那就是扩大session的生命周期,这样不就可以么吗?所以就可以采取在web.xml中配置一个
OpenSessionInViewFilter过滤器,具体代码如下:
  1. <filter>
  2. <filter-name>OpenSessionInViewFilter</filter-name>
  3. <filter- class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
  4. </filter>
  5. <filter-mapping>
  6. <filter-name>OpenSessionInViewFilter</filter-name>
  7. <url-pattern>*.action</url-pattern>
  8. </filter-mapping>
注意,这个一定要配置在strus2的核心过滤器前面(如果采取的是strus2的框架,如果用的springmvc那么同理)。为什么配置了这个过滤器就可以防止上面的问题呢?它是如何扩大session对象的生命周期的呢?好的,就来说明一下,这个看一下底层代码,一下就能够发现原因了,看一下流程图

这个图其实就已经说明了两个问题。第一,为什么要写在strus2核心过滤器前面。第二,它的工作原理,干的事情是什么。总的来说,就是通过这个过滤器来扩大了session的生命周期,这样的话周期变大了,当然也是有影响的,但是解决了这个面试题的问题了,也是可取的,而且开发中也就是这样。(PS:再多说一点额外内容,就是我们在使用Spring进行配引用Hibernate中的sessionFactory的时候,还记得是否,我们的那么名字,一般取得就是sessionFactory呢?其实,不是一般,而是肯定是这样的,这个问题,我们就可以从OpenSessionInViewFilter里面的源代码可以分析,因为在这里面定义了一个final的字符串,然后名字就是sessionFactory,而在后面获取sess方法中,就是通过去加载对应的这个名字的bean,然后实现的,所以,如果现在Spring的这个配置文件中,没有这个名字对应的内容?它还会加载出来吗?不信,你可以试试哦~!一发出请求,就是会提示找不到sessionFactory,然后我们一般就觉得很奇怪,明明配置了,怎么就是找不到呢?那么原因就是上面的了)

3:如何解决Hibernate中出现并发所引起的数据库内容读取问题,如何进行解决?

解答:对于这个问题中,是非常常见的,但是Hibernate提供了很好的支持,就是通过锁机制,这就可以采取悲观锁与乐观锁的机制来解决这个问题。

方法一:悲观锁---------锁的粒度为数据库

悲观锁,它是对数据库而言的,数据库悲观了,他感觉每一个对他操作的程序都有可能产生并发。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

首先说一条SQL语句如下:

select * from student where name=”xiaoming” forupdate 
其实这就是一个非常依赖于数据库的悲观锁的使用,同理,Hibernate之所以也能够这样,也就是通过数据库中的锁机制来实现的。代码如下:
  1. String hqlStr = "from Student s where s.name=‘xiaoming‘";
  2. Query query = session.createQuery(hqlStr);
  3. query.setLockMode( "s",LockMode.UPGRADE); // 加锁
  4. List userList = query.list(); // 执行查询,获取数据
这里Hibernate通过使用数据库的 for update子句实现了悲观锁机制。
Hibernate的加锁模式有:
LockMode.NONE :无锁机制。
LockMode.WRITE :Hibernate在 Insert和 Update记录的时候会自动获取。
LockMode.READ :Hibernate在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate内部使用,如Hibernate为了保证 Update过程中对象不会被外界修改,会在 save 方法实现中自动为目标对象加上 WRITE锁。
LockMode.UPGRADE :利用数据库的 for update 子句加锁。
LockMode. UPGRADE_NOWAIT : Oracle的特定实现,利用 Oracle的 for update nowait子句实现加锁。
上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查询开始之前(也就是 Hiberate生成 SQL之前)设定加锁,才会真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update子句的 Select SQL加载进来,所谓数据库加锁也就无从谈起。

方法二:乐观锁----------锁的粒度为表,而且当出现了问题之后,才采取措施

乐观锁,从名字来看,就肯定比悲观锁有着更为乐观的态度了,就是说悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个"version"字段来实现。
乐观锁的工作原理:读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
Hibernate为乐观锁提供了三 种实现:
1.基于version----------最常用
2.基于timestamp-----------较常用
3.为遗留项目添加添加乐观锁------不常用

方式一:基于version的使用

(1)对需要进行锁控制的数据库表中,添加一个字段,名字随便,一般就叫version,类型只有为long,integer,short,timestamp,calendar,也就是只能为数字或timestamp类型。

(2)如果通过配置文件进行配置的hibernate映射文件,那么就在对应的实体映射文件中添加如下代码:

<versionname="version"  column="VERSION" type="integer" />
如果是通过注解的形式进行的hibernate实体配置,那么很简单,只需要在对应的version字段用注解@version,即可,是不是简单很多呢?

(3)在POJO实体类中,实现对于version字段的set()和get()方法,这个和普通的属性一样,都需要这两个方法

方式二:基于timestamp

这个使用一样,只是需要把映射文件中的标签改一下即可,

  1. <!--timestamp标签用于指定表示版本号的字段信息-->
  2. <timestamp name= "version"column= "version"></timestamp>
好了,下面就用一个test例子来验证一下是否有锁控制机制了哦。。。
  1. //开启两个session
  2. Sessionsession1=sessionFactory.openSession();
  3. Session session2=sessionFactory.openSession();
  4. Student stu1=(Student)session1.createQuery( "from Student s wheres.name='xiaoming'").uniqueResult();
  5. Student stu2=(Student)session2.createQuery( "from Student s wheres.name='xiaoming'").uniqueResult();
  6. //首先看一下,拿出来的数据的version版本是否一致-----------结果是是一致,都是数据库当前的版本
  7. System.out.println( "v1="+stu1.getVersion()+ "--v2="+stu2.getVersion());
  8. //开始第一个事务
  9. Transactiontx1=session1.beginTransaction();
  10. //修改内容
  11. stu1.setName( "xiaohong");
  12. //事务提交
  13. tx1.commit();
  14. //当进行了事务提交之后,也就是数据的更新操作,再看看此时两个version是否一致---------结果显示version不一样了
  15. System.out.println( "v1="+stu1.getVersion()+ "--v2="+stu2.getVersion());
  16. //同理,再将第二个事务进行提交
  17. Transactiontx2=session2.beginTransaction();
  18. stu2.setName( "xiaobai");
  19. //提交
  20. tx2.commit();
-----OK,编译的时候是没有任何问题的,但是一运行就发现,哇塞,怎么报红,报了一个错,并且错误显示 Exception in thread "main" org.hibernate.StaleObjectStateException:Row was updated or deleted by another transaction (or unsaved-value mapping wasincorrect): 
其实,原因很简单,就是因为第一个事务提交对数据之后,这条数据的内容被更新,然后数据库中的版本控制字段version相比原来的版本+1,而当第二个事务再提交更新操作的时候,因为当前的数据的版本是原来的版本,而数据库中的版本比它高了,所以就认为这条数据是条无效的数据,那么结果很明显就是会发现刚才的错误了呗。。这样的话,是不是对于并发操作的问题就提供了一个很好的方法呢?

4:请说说,事务的类型

其实这个问题是比较刁钻的,但是现在分布式处理越来越频繁,所以对于项目中的事务处理也更加的繁琐。

(1)首先说一下事务的类型:

第一种:本地事务:在单个 EIS 或数据库的本地并且限制在单个进程内的事务。本地事务不涉及多个数据来源

第二种:全局事务:资源管理器管理和协调的事务,可以跨越多个数据库和进程。资源管理器一般使用 XA 二阶段提交协议与“企业信息系统”(EIS) 或数据库进行交互

(2)那么通过概念,其实,我们已经可以知道对于一般的话,我们通常都是使用的本地事务,因为一般的情况很少有进行跨数据源的事务操作。好吧,~~如果不是很明白的话,那么通过下面这个例子,我来解释一下。

示例:转账,,,这个很常见吧。。。但是,如果发生是在不同的数据源呢?比如一个在mysql,一个在oracle里面呢?

像我们平常学习的事务都是本地事务的知识。如果现在还用本地事务的处理方法进行的话,那么就是下面的操作:请继续看

  1. connection = mysql 连接mysql
  2. connection.setAutoCommit( false); 不自动提交
  3. 1> update mysql_table set amount=amount-xx where id=aaa 发生扣钱,假设是在mysql数据库扣钱的。
  4. 2> update oracle_table set amount=amount+xx where id=bbb 发生在oracle数据库
  5. connection.commit();
  6. 执行这两条语句,然后通过connection对象提交事务.我们这样子做只能确保这两个语句在同一个数据库mysql里面实现在同一个事务里执行。 但是问题是我们现在是要连接到oracle数据库,是不是需要connection2啊?
  7. connection = mysql 连接mysql
  8. connection2 = oracle 连接oracle
  9. connection.setAutoCommit( false); 不自动提交
  10. 1> update mysql_table set amount=amount-xx where id=aaa 发生扣钱,假设是在mysql数据库扣钱的。
  11. 2> update oracle_table set amount=amount+xx where id=bbb 发生在oracle数据库
  12. connection.commit();
  13. connection2.setAutoCommit( false);
  14. connection2.commit();
OK,我们利用原来学习过的知识,要保证这个在同一个事务中进行,那么就必须保证是在同一个connection里面,但是我们上面却是在两个connection中,那么肯定就无法保证是在同一个事务中进行了是么?那么问题就来了,本地事务无法解决这个问题,这时候  全局事务就闪亮登场了。

来来,看看全局事务如何进行处理:

  1. JPA.getUserTransaction().begin(); 首先要全局事务的API,不需要我们编写,通常容器已经提供给我们了,我们只需要begin一下
  2. connection = mysql 连接mysql
  3. connection2 = oracle 连接oracle
  4. connection--> update mysql_table set amount=amount-xx where id=aaa 发生扣钱,假设是在mysql数据库扣钱的。
  5. connection2--> update oracle_table set amount=amount+xx where id=bbb 发生在oracle数据库
  6. JPA.getUserTransaction().commit();
哇塞,是的哈,通过这样的方式就能够解决问题了。。。不对不对,还有一个问题,这个全局事务是如何确保是进行事务提交还是回滚呢?

咳咳,重点中的重点内容,那就是 全局事务 存在一个 二次提交协议--------------简单点说就是,它会判断里面进行的操作,返回的结果都是true,那么就说明将要进行提交操作,而如果不管任何结果返回的是false,那么就进行回滚操作。。。。当当当当,,,是不是就清晰很多了呢?

好吧。如果想知道更加详细的解析那就是看看下面的(这个是进行百度查询到的网友分享,感谢感谢~!)

如果你先执行第一条语句,执行的结果先预提交到数据库,预提交到数据库了,数据库会执行这条语句,然后返回一个执行的结果,这个结果假如我们用布尔值表示的话,成功就是true,失败就是false.然后把执行的结果放入一个(假设是List)对象里面去,接下来再执行第二条语句,执行完第二条语句之后(也是预处理,数据库不会真正实现数据的提交,只是说这条语句送到数据库里面,它模拟下执行,给你返回个执行的结果),假如这两条语句的执行结果在List里面都是true的话,那么这个事务就认为语句是成功的,这时候全局事务就会提交。二次提交协议,数据库在第一次提交这个语句时,只会做预处理,不会发生真正的数据改变,当我们在全局事务提交的时候,这时候发生了第二次提交,那么第二次提交的时候才会真正的发生数据的改动。 
   如果说在执行这两条语句中,有一个出错了,那么List集合里就有个元素为false,那么全局事务就认为你这个事务是失败的,它就会进行回滚,回滚的时候,哪怕你的第二条语句在第一次提交的时候是成功的,它在第二次提交的时候也会回滚,那么第一次的更改也会恢复到之前的状态,这就是二次提交协议。

(3)那么Hibernate中是如何进行设置的呢~!!!!别急,看下面----------

  1. 1.如果使用的是本地事务(JDBC事务)
  2. <property name= "hibernate.current_session_context_class">thread</property> //注意:只针对一个数据库(或者说是某一种数据库)进行操作
  3. 2. 如果使用的是全局事务(JTA事务---------JTA是Java transaction API 的缩写哦)
  4. <property name= "hibernate.current_session_context_class">jta</property>
总结点说:如果在项目中有需要进行不同中数据库源的数据操作(比如mysql和oracle结合)那么就需要进行全局事务的管理,如果是使用的同一种数据源的话,那么可以考虑使用本地事务即可哦。
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值