spring3.x第十章 Spring的事务管理难点剖析

来源:互联网 发布:淘宝运营策划案 编辑:程序博客网 时间:2024/06/05 10:17

10.1 DAO和事务管理的牵绊

10.1.1 JDBC访问数据库

  很多复杂的事物要分步进行,但它们组成一个整体,要么整体生效,要么整体失效。这种思想反映到数据库上,就是多个SQL语句,要么所有执行成功,要么所有执行失败。
  数据库事务有严格的定义,它必须同时满足4个特性,原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabiliy)
  在jdbcWithoutTx.xml中没有配置任何事务管理器,但是数据已经持久到数据库中。默认dataSource数据源的autoCommit被设置为true,这意味着所有通过JdbcTemplate执行的语句马上提交,没有事务。如果设置为false,再次运行将会出错,原因是新增及更改数据的操作都没有提交到数据库。
  对于强调读取速度的应用,数据库本身可能就不支持事务;如使用MyISAM引擎的MySQL数据库。即使配置事务管理器也没有实际用处。

10.1.2 Hibernate访问数据库

  对于Hibernate来说,情况就有点复杂了。因为Hibernate的事务管理器拥有自身的意义,它和Hibernate一级缓存存在密切的关系:当我们调用Session的save、update等方法时,Hibernate并不直接向数据库发送SQL语句,只在提交事务(commit)或flush一级缓存时猜真正向数据库发送SQL。所以,即使底层数据库不支持事务,Hibernate的事务管理也是有一定好处的,不会对数据操作的效率造成负面影响。所以,如果是使用Hibernate数据访问技术,没有理由不配置HibernateTransactionManager事务管理。
  运行正确执行。这说明Hibernate在Spring中,在没有事务管理器的情况下,依然可以正常地进行数据的访问。

10.2 应用分层的迷惑

  笔者还是认为需要分层。

10.3 事务方法嵌套调用的迷茫

10.3.1 Spring事务传播机制回顾

  Spring事务一个被讹传很广说法是:一个事务方法不应该调用另一个事务方法,否则将产生两个事务。结果造成开发人员在设计事务方法时束手束脚。
  Spring对事务控制的支持统一在TransactionDefinition类中描述,该类有以下几个重要的接口方法:

  int getPropagationBehavior(): 事务的传播行为
  int getIsolationLevel(): 事务的隔离级别
  int getTimeout(): 事务的过期时间
  boolean isReadOnly(): 事务的读写特性

  除了事务的传播行为外,事务的其他特性Spring是借助底层资源的功能来完成的,Spring无非只充当个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能。
  所谓是事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。Spring支持以下7种事务传播行为。默认为:PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务种。这是最常见的选择。

10.3.2 相互嵌套的服务方法

10.4 多线程的困惑

10.4.1 Spring通过单实例化Bean简化多线程问题

  由于Spring的事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施(也即Connection实例),再结合IOC和AOP实现高级声明式事务的功能。
  Web容器本身就是多线程的,Web容器为一个HTTP请求创建一个独立的线程(实际上大多数Web容器采用共享线程池),所以由此请求涉及到的Spring容器中Bean也是运行于多线程的环境下。
  在大多数情况下,Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题。
  一个类能够以单实例的方式运行的前提是”无状态”:即一个类不能拥有渣u那个太滑的成员变量。DAO必须持有一个Connection,而Connection即是状态化的对象。所以传统的DAO不能做成单实例的,每次要用时都还必须创建一个新的实例。传统的Service由于内部包含了若干个有状态的DAO成员变量,所以其本身也是有状态的。
  但是在Spring中,DAO和Service都以单实例的方式存在。Spring是通过ThreadLocal将有状态的变量(如Connection等)本地线程化,MAP存储,达到另一个层面上的”线程无关”,使用空间换取时间的方式,从而实现线程安全。
  由于Spring已经通过THreadLocal的设施将Bean无状态化,所以单实例的Service可以成功运行于多线程环境中,Service本身还可以自由地启动独立线程以执行其他的Service。

10.4.2 启动独立线程调用事务方法

  相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。

10.5 联合军种作战的混乱

10.5.1 Spring事务管理器的应对

  如果你采用了一个高端ORM技术(Hibernate),同时采用一个JDBC技术(Spring JDBC)由于前者的会话(Session)是对后者连接(Connection)的封装,Spring会”足够智能地”在同意事务线程让前者的会话封装后者的连接。所以我们只要直接采用前者的事务管理器就可以了。

10.5.2 Hibernate+Spring JDBC混合框架的事务管理

  显式调用了flush()方法,将Session中的缓存同步到数据库中。原因是默认情况下,Hibernate对数据的更改只是记录一级缓存中,要等到事务提交或显式调用flush()方法时才会将一级缓存中的数据同步到数据库中,而提交事务的操作发生在login()方法返回前。
  Spring JDBC无法自动感知Hibernate一级缓存,所以如果不及时调用flush()方法将记录数据更改的一级缓存同步到数据库中,则通过Spring JDBC进行数据更改的结果将被Hibernate一级缓存中的更改覆盖掉,因为Hibernate一级缓存要等到login()方法返回前才同步到数据库!
  使用事务管理器,Hibernate和JDBC使用了数据源同一个连接,但是,事务同步而缓存不同步。最好用Hibernate进行读写操作,而只用Spring JDBC进行读操作。

10.6 特殊方法成漏网之鱼

10.6.1 哪些方法不能实施Spring AOP事务

  由于Spring事务管理是基于接口代理或动态字节码技术,通过AOP实施事务增强的。
  对于基于接口动态代理的AOP事务增强来说,由于接口的方法都必然是public的,这就要求实现类的实现方法也必须是public的(不能是protected、private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用”public”或”public final”修饰符的方法,其他方法不可能被动态代理,相应的也就不能实施AOP增强,即不能进行Spring事务增强。
  基于CGLib字节码动态代理的方案是通过扩展被增强类,动态创建其自雷的方式进行AOP增强植入的。由于使用final、static、private修饰符的方法都不能被子类覆盖,相应的,这些方法将无法实施AOP增强。

10.6.2 事务增强遗漏实例

10.7 数据连接泄漏

10.7.1 底层连接资源的访问问题

  Spring DAO对所有支持的数据访问技术框架都使用模板化技术进行了薄层的封装。只要你的程序都使用Spring DAO的模板(如JdbcTemplate、HibernateTemplate等)进行数据访问,一定不会存在数据连接泄漏的问题。我们无需关注数据连接(Connection)及其衍生品(Hibernate的Session等)的获取和释放操作,模板类已经通过其内部流程替我们完成了,且对开发者是透明的。
  但是由于集成第三方产品、整合遗产代码等原因,可能需要直接访问数据源或直接获取数据连接及其衍生品。这时,如果使用不当,就可能在无意中创造出一个魔鬼般的连接泄漏问题。
  我们知道:当Spring事务方法运行时,就产生一个事务上下文,该上下文在本事务执行线程中针对同一个数据源绑定了一个唯一的数据连接(或其衍生品),所有被该事务上下文传播的方法都共享了这个数据连接。这个数据连接从数据源获取及返回给数据源都在Spring掌控之中,不会发生问题。如果在需要数据连接时,能够获取这个被Spring管控的数据连接,则使用者可以放心使用,无须关注连接释放的问题。
  如何获取这些被Spring管控的数据连接呢?Spring提供了两种方法:其一是使用数据资源获取工具类;其二是对数据源(或其衍生品如Hibernate的SessionFactory)进行代理。

10.7.2 Spring JDBC数据连接泄漏

  获取Connection连接,没有显式的释放该连接。

10.7.3 通过DataSourceUtils获取数据链接
10.7.5 JdbcTemplate如何做到对连接泄漏的免疫

  JdbcTemplate#execute()方法内部,首先都使用DataSourceUtils获取连接,在方法返回之前使用DataSourceUtils释放连接。

10.7.6 使用TransactionAwareDataSourceProxy
10.7.7 其他数据访问技术的等价类

10.8 小结

  Spring声明式事务是Spring最核心、最常用的功能。由于Spring通过IOC和AOP的功能非常透明地实现了声明式事务的功能,需要懂得如何配置。
  在没有事务管理的情况下,DAO照样可以顺利进行数据操作;
  Spring通过事务传播机制可以很好地应对事务方法嵌套调用的情况。
  由于单实例的对象不存在线程安全问题,所以经过事务管理增强的单实例Bean可以很好地工作在多线程环境下;
  混合使用多个数据访问技术框架需要考虑ORM缓存同步的问题。
  Spring AOP增强有两个方案:其一是基于接口的动态代理,其二是基于CGLib动态生成自雷的代理。由于Java语法的特性,有些特殊方法不能被Spring AOP代理,无法享受AOP织入带来的事务增强;
  使用Spring JDBC时如果直接获取Connection,可能会造成连接泄漏。为降低连接泄漏的可能性,尽量使用DataSourceUtils获取数据连接。也可以对数据源进行代理。

0 0
原创粉丝点击