Hibernate的事务和并发02

驴友部门

驴友部门

2016-02-19 14:57

想不想get新技能酷炫一下,今天图老师小编就跟大家分享个简单的Hibernate的事务和并发02教程,一起来看看吧!超容易上手~

  12.2.1.非托管环境

  如果Hibernat持久层运行在一个非托管环境中,数据库连接通常由Hibernate的连接池机制 来处理。

  

代码内容
session/transaction处理方式如下所示:
//Non-managed environment idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}

  你不需要显式flush() Session - 对commit()的调用会自动触发session的同步。

  调用 close() 标志session的结束。 close()方法重要的暗示是,session释放了JDBC连接。

  这段Java代码是可移植的,可以在非托管环境和JTA环境中运行。

  你很可能从未在一个标准的应用程序的业务代码中见过这样的用法;致命的(系统)异常应该总是 在应用程序顶层被捕获。换句话说,执行Hibernate调用的代码(在持久层)和处理 RuntimeException异常的代码(通常只能清理和退出应用程序)应该在不同 的应用程序逻辑层。这对于你设计自己的软件系统来说是一个挑战,只要有可能,你就应该使用 J2EE/EJB容器服务。异常处理将在本章稍后进行讨论。

  请注意,你应该选择 org.hibernate.transaction.JDBCTransactionFactory (这是默认选项).

  12.2.2.使用JTA

  如果你的持久层运行在一个应用服务器中(例如,在EJB session beans的后面),Hibernate获取 的每个数据源连接将自动成为全局JTA事务的一部分。Hibernate提供了两种策略进行JTA集成。

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)

  如果你使用bean管理事务(BMT),可以通过使用Hibernate的 Transaction API来告诉 应用服务器启动和结束BMT事务。因此,事务管理代码和在非托管环境下是一样的。

  

代码内容
// BMT idiom
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
// do some work
...
tx.commit();
}
catch (RuntimeException e) {
if (tx != null) tx.rollback();
throw e; // or display error message
}
finally {
sess.close();
}
在CMT方式下,事务声明是在session bean的部署描述符中,而不需要编程。 除非你设置了属性hibernate.transaction.flush_before_completion和 hibernate.transaction.auto_close_session为true, 否则你必须自己同步和关闭Session。Hibernate可以为你自动同步和关闭 Session。你唯一要做的就是当发生异常时进行事务回滚。幸运的是, 在一个CMT bean中,事务回滚甚至可以由容器自动进行,因为由session bean方法抛出的未处理的 RuntimeException异常可以通知容器设置全局事务回滚。这意味着 在CMT中,你完全无需使用Hibernate的Transaction API 。

  请注意,当你配置Hibernate事务工厂的时候,在一个BMT session bean中,你应该选择 org.hibernate.transaction.JTATransactionFactory,在一个 CMT session bean中选择org.hibernate.transaction.CMTTransactionFactory。 记住,同时也要设置org.hibernate.transaction.manager_lookup_class。

  如果你使用CMT环境,并且让容器自动同步和关闭session,你可能也希望在你代码的不同部分使用 同一个session。一般来说,在一个非托管环境中,你可以使用一个ThreadLocal 变量来持有这个session,但是单个EJB方法调用可能会在不同的线程中执行(举例来说,一个session bean调用另一个session bean)。如果你不想在应用代码中被传递Session对 象实例的问题困扰的话,那么SessionFactory 提供的 getCurrentSession()方法就很适合你,该方法返回一个绑定到JTA事务 上下文环境中的session实例。这也是把Hibernate集成到一个应用程序中的最简单的方法!这个当 前的session总是可以自动同步和自动关闭(不考虑上述的属性设置)。我们的session/transaction 管理代码减少到如下所示:

  

代码内容
// CMT idiom
Session sess = factory.getCurrentSession();
// do some work
...

  换句话来说,在一个托管环境下,你要做的所有的事情就是调用 SessionFactory.getCurrentSession(),然后进行你的数据访问,把其余的工作 交给容器来做。事务在你的session bean的部署描述符中以可声明的方式来设置。session的生命周期完全 由Hibernate来管理。

  对after_statement连接释放方式有一个警告。因为JTA规范的一个很愚蠢的限制,Hibernate不可能自动清理任何未关闭的ScrollableResults 或者Iterator,它们是由scroll()或iterate()产生的。你must通过在finally块中,显式调用ScrollableResults.close()或者Hibernate.close(Iterator)方法来释放底层数据库游标。(当然,大部分程序完全可以很容易的避免在CMT代码中出现scroll()或iterate()。)

  12.2.3.异常处理

  如果 Session 抛出异常 (包括任何SQLException), 你应该立即回滚数据库事务,调用 Session.close() ,丢弃该 Session实例。Session的某些方法可能会导致session 处于不一致的状态。所有由Hibernate抛出的异常都视为不可以恢复的。确保在 finally 代码块中调用close()方法,以关闭掉 Session。

  HibernateException是一个非检查期异常(这不同于Hibernate老的版本), 它封装了Hibernate持久层可能出现的大多数错误。我们的观点是,不应该强迫应用程序开发人员 在底层捕获无法恢复的异常。在大多数软件系统中,非检查期异常和致命异常都是在相应方法调用 的堆栈的顶层被处理的(也就是说,在软件上面的逻辑层),并且提供一个错误信息给应用软件的用户 (或者采取其他某些相应的操作)。请注意,Hibernate也有可能抛出其他并不属于 HibernateException的非检查期异常。这些异常同样也是无法恢复的,应该 采取某些相应的操作去处理。

  在和数据库进行交互时,Hibernate把捕获的SQLException封装为Hibernate的 JDBCException。事实上,Hibernate尝试把异常转换为更有实际含义 的JDBCException异常的子类。底层的SQLException可以 通过JDBCException.getCause()来得到。Hibernate通过使用关联到 SessionFactory上的SQLExceptionConverter来 把SQLException转换为一个对应的JDBCException 异常的子类。默认情况下,SQLExceptionConverter可以通过配置dialect 选项指定;此外,也可以使用用户自定义的实现类(参考javadocs SQLExceptionConverterFactory类来了解详情)。标准的 JDBCException子类型是:

  JDBCConnectionException - 指明底层的JDBC通讯出现错误

  SQLGrammarException - 指明发送的SQL语句的语法或者格式错误

  ConstraintViolationException - 指明某种类型的约束违例错误

  LockAcquisitionException - 指明了在执行请求操作时,获取 所需的锁级别时出现的错误。

  GenericJDBCException - 不属于任何其他种类的原生异常

  12.3.乐观并发控制(Optimistic concurrency control)

  唯一能够同时保持高并发和高可伸缩性的方法就是使用带版本化的乐观并发控制。版本检查使用版本号、 或者时间戳来检测更新冲突(并且防止更新丢失)。Hibernate为使用乐观并发控制的代码提供了三种可 能的方法,应用程序在编写这些代码时,可以采用它们。我们已经在前面应用程序长事务那部分展示了 乐观并发控制的应用场景,此外,在单个数据库事务范围内,版本检查也提供了防止更新丢失的好处。

  12.3.1.应用程序级别的版本检查(Application version checking)

  未能充分利用Hibernate功能的实现代码中,每次和数据库交互都需要一个新的 Session,而且开发人员必须在显示数据之前从数据库中重 新载入所有的持久化对象实例。这种方式迫使应用程序自己实现版本检查来确保 应用程序事务的隔离,从数据访问的角度来说是最低效的。这种使用方式和 entity EJB最相似。

  // foo is an instance loaded by a previous Session

(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)

  session = factory.openSession();

  Transaction t = session.beginTransaction();

  int oldVersion = foo.getVersion();

  session.load( foo, foo.getKey() ); // load the current state

  if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();

  foo.setProperty("bar");

  t.commit();

  session.close();

  version 属性使用 来映射,如果对象 是脏数据,在同步的时候,Hibernate会自动增加版本号。

  当然,如果你的应用是在一个低数据并发环境下,并不需要版本检查的话,你照样可以使用 这种方式,只不过跳过版本检查就是了。在这种情况下,最晚提交生效 (last commit wins)就是你的应用程序长事务的默认处理策略。 请记住这种策略可能会让应用软件的用户感到困惑,因为他们有可能会碰上更新丢失掉却没 有出错信息,或者需要合并更改冲突的情况。

  很明显,手工进行版本检查只适合于某些软件规模非常小的应用场景,对于大多数软件应用场景 来说并不现实。通常情况下,不仅是单个对象实例需要进行版本检查,整个被修改过的关 联对象图也都需要进行版本检查。作为标准设计范例,Hibernate使用长生命周期 Session的方式,或者脱管对象实例的方式来提供自动版本检查。

  12.3.2.长生命周期session和自动版本化

  单个 Session实例和它所关联的所有持久化对象实例都被用于整个 应用程序事务。Hibernate在同步的时候进行对象实例的版本检查,如果检测到并发修 改则抛出异常。由开发人员来决定是否需要捕获和处理这个异常(通常的抉择是给用户 提供一个合并更改,或者在无脏数据情况下重新进行业务操作的机会)。

  在等待用户交互的时候, Session 断开底层的JDBC连接。这种方式 以数据库访问的角度来说是最高效的方式。应用程序不需要关心版本检查或脱管对象实例 的重新关联,在每个数据库事务中,应用程序也不需要载入读取对象实例。

  

代码内容
// foo is an instance loaded earlier by the Session
session.reconnect(); // Obtain a new JDBC connection
Transaction t = session.beginTransaction();
foo.setProperty("bar");
t.commit(); // End database transaction, flushing the change and checking the version
session.disconnect(); // Return JDBC connection

  foo 对象始终和载入它的Session相关联。 Session.reconnect()获取一个新的数据库连接(或者 你可以提供一个),并且继续当前的session。Session.disconnect() 方法把session与JDBC连接断开,把数据库连接返回到连接池(除非是你自己提供的数据 库连接)。在Session重新连接上数据库连接之后,你可以对任何可能被其他事务更新过 的对象调用Session.lock(),设置LockMode.READ 锁定模式,这样你就可以对那些你不准备更新的数据进行强制版本检查。此外,你并不需要 锁定那些你准备更新的数据。

  假若对disconnect()和reconnect()的显式调用发生得太频繁了,你可以使用hibernate.connection.release_mode来代替。

  如果在用户思考的过程中,Session因为太大了而不能保存,那么这种模式是有 问题的。举例来说,一个HttpSession应该尽可能的小。由于 Session是一级缓存,并且保持了所有被载入过的对象,因此 我们只应该在那些少量的request/response情况下使用这种策略。而且在这种情况下, Session 里面很快就会有脏数据出现,因此请牢牢记住这一建议。

  此外,也请注意,你应该让与数据库连接断开的Session对持久层保持 关闭状态。换句话说,使用有状态的EJB session bean来持有Session, 而不要把它传递到web层(甚至把它序列化到一个单独的层),保存在HttpSession中。

  12.3.3.脱管对象(deatched object)和自动版本化

  这种方式下,与持久化存储的每次交互都发生在一个新的Session中。 然而,同一持久化对象实例可以在多次与数据库的交互中重用。应用程序操纵脱管对象实例 的状态,这个脱管对象实例最初是在另一个Session 中载入的,然后 调用 Session.update(),Session.saveOrUpdate(), 或者 Session.merge() 来重新关联该对象实例。

  

代码内容
// foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
Transaction t = session.beginTransaction();
session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
t.commit();
session.close();

  Hibernate会再一次在同步的时候检查对象实例的版本,如果发生更新冲突,就抛出异常。

  如果你确信对象没有被修改过,你也可以调用lock() 来设置 LockMode.READ(绕过所有的缓存,执行版本检查),从而取 代 update()操作。

  12.3.4.定制自动版本化行为

  对于特定的属性和集合,通过为它们设置映射属性optimistic-lock的值 为false,来禁止Hibernate的版本自动增加。这样的话,如果该属性 脏数据,Hibernate将不再增加版本号。

  遗留系统的数据库Schema通常是静态的,不可修改的。或者,其他应用程序也可能访问同一数据 库,根本无法得知如何处理版本号,甚至时间戳。在以上的所有场景中,实现版本化不能依靠 数据库表的某个特定列。在的映射中设置 optimistic-lock="all"可以在没有版本或者时间戳属性映射的情况下实现 版本检查,此时Hibernate将比较一行记录的每个字段的状态。请注意,只有当Hibernate能够比 较新旧状态的情况下,这种方式才能生效,也就是说, 你必须使用单个长生命周期Session模式,而不能使用 session-per-request-with-detached-objects模式。

  有些情况下,只要更改不发生交错,并发修改也是允许的。当你在 的映射中设置optimistic-lock="dirty",Hibernate在同步的时候将只比较有脏 数据的字段。

  在以上所有场景中,不管是专门设置一个版本/时间戳列,还是进行全部字段/脏数据字段比较, Hibernate都会针对每个实体对象发送一条UPDATE(带有相应的 WHERE语句 )的SQL语句来执行版本检查和数据更新。如果你对关联实体 设置级联关系使用传播性持久化(transitive persistence),那么Hibernate可能会执行不必 要的update语句。这通常不是个问题,但是数据库里面对on update点火 的触发器可能在脱管对象没有任何更改的情况下被触发。因此,你可以在 的映射中,通过设置select-before-update="true" 来定制这一行为,强制Hibernate SELECT这个对象实例,从而保证, 在更新记录之前,对象的确是被修改过。

  12.4.悲观锁定(Pessimistic Locking)

  用户其实并不需要花很多精力去担心锁定策略的问题。通常情况下,只要为JDBC连接指定一下隔 离级别,然后让数据库去搞定一切就够了。然而,高级用户有时候希望进行一个排它的悲观锁定, 或者在一个新的事务启动的时候,重新进行锁定。

  Hibernate总是使用数据库的锁定机制,从不在内存中锁定对象!

  类LockMode 定义了Hibernate所需的不同的锁定级别。一个锁定 可以通过以下的机制来设置:

  当Hibernate更新或者插入一行记录的时候,锁定级别自动设置为LockMode.WRITE。

  当用户显式的使用数据库支持的SQL格式SELECT ... FOR UPDATE 发送SQL的时候,锁定级别设置为LockMode.UPGRADE

  当用户显式的使用Oracle数据库的SQL语句SELECT ... FOR UPDATE NOWAIT 的时候,锁定级别设置LockMode.UPGRADE_NOWAIT

  当Hibernate在可重复读或者是序列化数据库隔离级别下读取数据的时候,锁定模式 自动设置为LockMode.READ。这种模式也可以通过用户显式指定进行设置。

  LockMode.NONE 代表无需锁定。在Transaction结束时, 所有的对象都切换到该模式上来。与session相关联的对象通过调用update() 或者saveOrUpdate()脱离该模式。

  "显式的用户指定"可以通过以下几种方式之一来表示:

  调用 Session.load()的时候指定锁定模式(LockMode)。

  调用Session.lock()。

  调用Query.setLockMode()。

  如果在UPGRADE或者UPGRADE_NOWAIT锁定模式下调 用Session.load(),并且要读取的对象尚未被session载入过,那么对象 通过SELECT ... FOR UPDATE这样的SQL语句被载入。如果为一个对象调用 load()方法时,该对象已经在另一个较少限制的锁定模式下被载入了,那 么Hibernate就对该对象调用lock() 方法。

  如果指定的锁定模式是READ, UPGRADE 或 UPGRADE_NOWAIT,那么Session.lock()就 执行版本号检查。(在UPGRADE 或者UPGRADE_NOWAIT 锁定模式下,执行SELECT ... FOR UPDATE这样的SQL语句。)

  如果数据库不支持用户设置的锁定模式,Hibernate将使用适当的替代模式(而不是扔出异常)。 这一点可以确保应用程序的可移植性。

展开更多 50%)
分享

猜你喜欢

Hibernate的事务和并发02

编程语言 网络编程
Hibernate的事务和并发02

Hibernate的事务和并发01

编程语言 网络编程
Hibernate的事务和并发01

s8lol主宰符文怎么配

英雄联盟 网络游戏
s8lol主宰符文怎么配

第一个成功的Hibernate实例02

编程语言 网络编程
第一个成功的Hibernate实例02

Hibernate和Jive缓存策略的比较

编程语言 网络编程
Hibernate和Jive缓存策略的比较

lol偷钱流符文搭配推荐

英雄联盟 网络游戏
lol偷钱流符文搭配推荐

配制Spring事务和JdbcTemplate使用

编程语言 网络编程
配制Spring事务和JdbcTemplate使用

Weblogic81和Hibernate 的集成问题

编程语言 网络编程
Weblogic81和Hibernate 的集成问题

lolAD刺客新符文搭配推荐

英雄联盟
lolAD刺客新符文搭配推荐

Structs的基本配置

Structs的基本配置

网页制作中注意应用HTML标签问题

网页制作中注意应用HTML标签问题
下拉加载更多内容 ↓