spring学习笔记(一)事务的管理和传播特性

来源:互联网 发布:彩票开奖数据采集 编辑:程序博客网 时间:2024/05/19 14:20

何为事务

原子性、一致性、隔离性、持久性

定义事务管理器

常见的事务管理器有JDBC事务、HIBERNATE事务、JTA事务、选择使用事务管理器可以根据项目的需要。
JDBC事务:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean>
HIBERNATE事务:
<bean id="transactionManager" class="org.springframework.orm..DataSourceTransactionManager"><property name="sessionFactory" ref="sessionFactory"<span style="font-family: Arial, Helvetica, sans-serif;">/></span></bean>

定义事务属性

事务隔离

  1、ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别,另外四个与JDBC的隔离级别相对应。
  2、 ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
      这种隔离级别会产生脏读,不可重复读和幻像读。
  3、 ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
  4、ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
  5、ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
具体关于ACID的内容可以关注我的另外一篇博文ACID

事务传播

 事务的传播特性通俗的讲,事务的是会传播的。传播的属性包括是否使用事务、事务是否可以传播、是否可以嵌套事务等。当调用Service层的一个方法的时候,事务保证方法中所有有关数据库更新操作在一个原子中。在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。

   如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法是解决这个问题的,此时就需要考虑事务的传播特性。“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:


REQUIRED
Required属性告诉容器某个特定的方法需要一个事务,如果上下文中已经存在事务,则加入;否则,开启一个事务。这是一种使用最频繁的事务属性,适用于大多数情况;但是,并不绝对,下面我们将会看到对于某些场景。

MANDATORY(强制)

Mandatory属性告诉容器某个特定的方法需要一个事务。但是,不同于Required属性,它无论如何都不会开启新的事务;相反的,它会 “强制”要求该方法被调用时上下文中必须存在事务,否则会抛出TransactionRequiredException异常,提示需要一个事务但没有找 到。

REQUIRESNEW(创建)

RequiresNew属性告诉容器某个特定的方法需要一个新事务的支持。如果上下文中已经存在事务A,则该事务A挂起,并启动一个新的事务B。 当事务B结束后,事务A被唤醒并继续执行。事实上,使用RequiresNew违反了事务的ACID原则,因为新事务会导致原有事务的挂起。该属性在某个行为必须被完成(提交或回滚)而不受外部事务结果影响时十分有用,例如记录日志。大多数交易系统的每一个操作都必须写进日志,无论其 执行结果如何(成功或失败)。假设某个股票交易的方法placement()启动了一个事务,并且调用了一个通用的方法audit()来记录日志。由于 audit()和placement()处于同一个事务的管辖范围之内,因此一旦placement()回滚,audit()记录的日志也会相应的进行回 滚;这就违背了“任何成功或失败的操作都必须记录日志”这一业务逻辑。这个时候,如果将audit()的事务属性设作RequiresNew,则确保了 audit()在一个新的、单独的事务中记录日志,因此不受placement()中外部事务的影响。

SUPPORTS()

Supports属性告诉容器,该方法不需要事务支持,但如果当前上下文中已经存在了一个事务,则加入其中。Supports这是一个相当强大、 相当有用的事务属性。考虑这样一个场景,业务方法A用来查询某个交易者特定时期的交易总量。由于是查询操作,因此这个时候事务并不是必须的,因此我们将其 事务属性设为Supports,来告诉容器在调用方法时不要开启新的事务。但是,如果方法A被某个已经存在事务控制的方法B所调用,那么它就会加入当前事 务;那么,在该事务提交之前,方法B中对数据的任何修改对于方法A来说,都是可见的。下面我们举个更具体的例子来说明Supports的作用:

  假设某位交易者一天的最大交易额是一百万,如果采用Supports作为事务属性,一次超额交易的具体处理流程如下:

      目前为止,当天总共的交易量是900,000
      事务启动
      交易者又进行了一笔200,000的交易

         ————————————

         | 查询(a)---->  交易(b)|      由于查询的时候 没有事务,但是交易方法在调用查询的时候会给查询添加supports事务,因此抛异常。

         ————————————
      调用事务属性为Supports的查询方法,因为是同一个事务,因此得到结果为1,100,000
      业务逻辑判断,已经超过最大交易限额,抛出异常,事务回滚

如果使用NotSupported会发生什么呢?我们不会得到任何异常,因为查询方法不会加入当前事务,因此它看不见当前事务中对数据库的任何修改。

      目前为止,当天总共的交易量是900,000
      事务启动
      交易者又进行了一笔200,000的交易
      调用事务属性为Notupported的查询方法,因为不属于当前事务,因此得到结果为900,000
      业务逻辑判断,没有超过最大交易限额,事务提交(实际已超过当天限额)

NotSupported(事务排斥)

NotSupported属性告诉容器,该方法不需要事务支持;如果当前上下文中已经存在事务,则该事务被挂起直到该方法执行完毕。如果当前上下 文中不存在事务,该方法则在没有事务支持的环境下执行。NotSupported适用于“某些方法在事务控制下有较大可能性会产生异常”的场合。例如在 XA架构的事务处理过程中,调用包含DDL语句的存储过程往往会抛出异常,因此比较好的做法是将其设为NotSupported,暂时挂起当前事务。

Never(事务绝缘)

Never属性告诉容器,该方法必须在无事务的上下文中运行。注意,这与NotSupported不同,后者意味着,如果上下文中存在事务,则将 该事务暂时挂起并在无事务的上下文中运行。但Never却不同,如果当前上下文中已经存在事务,则在调用该方法时会抛出一个异常,提示该方法不能在事务环 境下运行。因为使用Never会导致各种意料之外的运行时异常,因此除非必要,一般不推荐使用

Required vs. Mandatory

Required和Mandatory是两个往往容易被混淆的事务属性。两者都可以运行在事务上下文环境中,但当上下文中不存在事务时,Required会自己开启一个事务,而Mandatory则不会。大多数情况下,你可以采用如下的“最佳实践”来区分何时需要哪种策略:如果某一方 法需要运行在具备事务的环境,但其本身又不负责事务的控制(回滚),则该方法应该标志为Mandatory。可以看出,上述最佳实践是基于事务的所有权而得来的。

RequiresNew vs. Required

另外两个比较容易的事务属性是Spring提供的Nested与RequiresNew。如果当前上下文中存在事务,两者都是启动一个新的事务; 如果当前上下文中不存在事务,则类似于PROPAGATION_REQUIRED,两者也都新建一个事务。那么,它们有什么区别呢?
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 "内部" 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等。当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。另一方面, PROPAGATION_NESTED 开始一个 "嵌套的" 事务,  它是已经存在事务的一个真正的子事务。嵌套事务开始执行时,  它将取得一个 savepoint。如果这个嵌套事务失败, 我们将回滚到此 savepoint。嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
总结一下,使用嵌套事务的场景总共有两个,如下图所示:

  需要事务BC与事务AD一起提交,即:作为事务AD的子事务,事务BC只有在事务AD成功提交时(阶段3成功)才提交。这个需求简单称之为“联合提交”。这一点PROPAGATION_REQUIRED可以做到。
    需要事务BC的回滚不会影响事务AD的提交。这个需求简单称之为“隔离回滚”。这一点,可以通过设置事务属性为PROPAGATION_REQUIRES_NEW做到。

   使用PROPAGATION_REQUIRED满足需求1,但子事务BC的rollback会迫使父事务AD也回滚,不能满足需求2。使用PROPAGATION_REQUIRES_NEW满足需求2,但子事务(严格意义上说,这时不应该称之为子事务)BC是一个全新的事务,父事务(严格意 义上说,这时也不应该称之为父事务)AD的成功与否完全不影响BC的提交,不能满足需求1。
同时满足上述两条需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事务AD执行到B点时,设置了 savePoint。当BC事务成功提交时,PROPAGATION_NESTED的行为与PROPAGATION_REQUIRED一样。只有当事务 AD在D点成功提交时,事务BC才真正提交; 如果阶段3执行异常,导致事务AD 回滚,事务BC也将一起回滚,从而满足了“联合提交”。 当阶段2执行异常,导致BC事务rollback时,因为设置了savePoint的缘故,AD事务可以选择与BC一起rollback或继续阶段3的执 行并保留阶段1的执行结果,从而满足了“隔离回滚”。例如,可以把执行BC事务的方法try-catch起来,在catch中选择是否继续向上抛出异常 (是,则回滚到A处;否,则回滚到B处)。而是用PROPAGATION_REQUIRED时,即使try-catch住BC的异常,AD事务也一定会被 无条件rollback。

readOnly

readOnly不是事务传播特性,但常伴随事务特性出现。这并不是一个强制选项,它只是一个提示,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。


XML事务配置

   spring完成事务管理的根本目的就是让开发更加的专注于的业务逻辑。而不用去关心事务的问题。spring有两种事务实现方式,一种是摒弃的声明事务,一种是配置事务。(默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承这样事务才会回滚!)


<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:mvc="http://www.springframework.org/schema/mvc"xsi:schemaLocation="     http://www.springframework.org/schema/beans       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd            http://www.springframework.org/schema/context       http://www.springframework.org/schema/context/spring-context-3.0.xsd            http://www.springframework.org/schema/tx      http://www.springframework.org/schema/tx/spring-tx.xsd           http://www.springframework.org/schema/aop     http://www.springframework.org/schema/aop/spring-aop-3.0.xsd          http://www.springframework.org/schema/mvc       http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd"><!-- 加载配置文件 --><context:property-placeholder location="config.properties"/><!-- 指定spring注解 --><context:component-scan base-package="com.hskj"/> <!-- 连接池管理 --><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"><property name="driverClass" value="${db.driverClass}"/><property name="jdbcUrl" value="${db.jdbcUrl}"/><property name="user" value="${db.user}"/><property name="password" value="${db.password}"/><property name="initialPoolSize" value="${db.initialPoolSize}"/><!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --><property name="maxIdleTime" value="${db.maxIdleTime}"/><!--连接池中保留的最大连接数。Default: 15 --><property name="maxPoolSize" value="${db.maxPoolSize}"/><property name="minPoolSize" value="${db.minPoolSize}"/><!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --><property name="acquireIncrement" value="${db.acquireIncrement}"/><!--两次连接中间隔时间,单位毫秒。Default: 1000 --><property name="acquireRetryDelay" value="${db.acquireRetryDelay}"/><!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --><property name="acquireRetryAttempts" value="${db.acquireRetryAttempts}"/><!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 获取连接失败后该数据源将申明已断开并永久关闭。Default: false --><property name="breakAfterAcquireFailure" value="${db.breakAfterAcquireFailure}"/></bean><!-- 事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 声明事务 --><tx:advice id="userTxAdvice" transaction-manager="transactionManager"><tx:attributes><tx:method name="delete*" propagation="REQUIRED" read-only="false"rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException" /><tx:method name="insert*" propagation="REQUIRED" read-only="false"rollback-for="java.lang.RuntimeException" /><tx:method name="update*" propagation="REQUIRED" read-only="false"rollback-for="java.lang.Exception" /><tx:method name="find*" propagation="SUPPORTS" /><tx:method name="get*" propagation="SUPPORTS" /><tx:method name="select*" propagation="SUPPORTS" /></tx:attributes></tx:advice><!-- 织如事务(到业务层切面)(到此spring事务管理结束了) --><aop:config><aop:pointcut id="servicePointCut" expression="execution(public * com.liyb.test.spring.tracsaction.service.*.*(..))"/></aop:config><!-- mybatis集成 --><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="configLocation" value="MyBatis-Configuration.xml"/></bean><bean class="org.mybatis.spring.mapper.MapperFactoryBean"><property name="mapperInterface" value="com.hskj.mapper.UserMapper"/><property name="sqlSessionFactory" ref="sqlSessionFactory"/></bean></beans>


0 0