事务管理

原创
2016/11/09 17:22
阅读数 19

事务具有四个特性:

  • 原子性(Atomic):事务是由一个或多个活动所组成的一个工作单元。原子性确保事务中的所有操作全部发生或全部不发生。如果所有的活动都成功了,事务也就成功了。如果任意一个活动失败了,整个事务也失败并回滚。
  • 一致性(Consistent):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态。现实的数据不应该被毁坏。 
  • 隔离性(Isolated):事务允许多个用户对相同的数据进行操作,每个用户的操作不会与其他用户纠缠在一起。因此,事务应该被彼此隔离,避免发生同步读写相同数据的事情(注意的是,隔离性往往涉及到锁定数据库中的行或表)。
  • 持久性(Durable):一旦事务完成,事务的结果应该持久化,这样就能从任何的系统崩溃中恢复过来。这一般涉及将结果存储到数据库或其他形式的持久化存储中。

 Spring提供了对编码式事务和声明式事务管理的支持。编码式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。选择编码式事务还是声明式事务很大程度上是在细粒度控制和易用性之间进行权衡。

spring并不直接管理事务,而是提供了多种事务管理器,它们将事务管理的职责委托给JTA或其他持久化机制所提供的平台先关的事务实现。

事务管理器(org.framework.*) 使用场景
jca.cci.connection.CciLocalTransactionManager 使用Spring对Java EE连接器架构(Java EE Connector Architecture,JCA)和通用客户端接口(Common Client Interface,CCI)提供支持
jdbc.datasource.DataSourceTransactionManager 用于Spring对JDBC抽象的支持,也可用于使用IBATIS进行持久化的场景
jms.connection.JmsTransactionManager 用于JMS 1.1+
jms.connection.JmsTransactionManager102 用于JMS 1.0.2
org.hibernate3.HibernateTransactionManager 用于Hibernate3进行持久化
orm.jdo.JdoTransactionManager 用于JDO进行持久化
orm.jpa.JpaTransactionManager 用于Jave持久化API(Java Persistence API,JPA)进行持久化
transaction.jta.JtaTransactionManager 需要分布式服务或者没有其他的事务管理器满足需求
transaction.jta.OC4JJtaTransactionManager 用于Oracle的OC4J JEE容器
transaction.jta.WebLogicJtaTransactionManager 需要使用分布式服务并且应用程序运行在WebLogic中
transaction.jta.WebLogicUowTransactionManager 需要WebSphere中UOWManager所管理的事务

 

事务属性:

在Spring中,声明式事务是通过事务属性(transaction attribute)来定义的。事务属性描述了事务策论如何应用到方法上。事务属性包含5个方面:

  • 传播行为:事务的第一个方面是传播行为(propagation behavior)。传播行为定义了客户端与被调用方法之间的事务边界。spring定义了7中不同的传播行为
传播行为 含义
PROPAGATION_MANDATORY 表示该方法必须在事务中运行。如果当前事务不存在,则会抛出一个异常
PROPAGATION_NESTED 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套事务可以独立于当前事务进行单独地提交或者回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为是有所差异的。可以参考资源管理器的文档来确定它们是否支持嵌套时事务
PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_REQUIRED 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事物中运行。否则,会启动一个新的事务
PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager,则需要访问TransactionManager
PROPAGATION_SUPPORTS 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
  •  隔离级别:声明式事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。事务的并发可能导致以下问题:
  1. 脏读(Dirty Reads):脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
  2. 不可重复读(Nonrepeatable read):不可重复读发生在一个事务执行相同的查询两次或者两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间更新了数据。
  3. 幻读(Phantom read):幻读与不可重复读类似。他发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据。在随后的查询中,第一个事务(T1)就回发现多了一些原本不存在的数据。

    在理想情况下,事务之间是完全隔离的,从而就可以防止这些问题发生。但是完全的隔离会导致性能问题,因为它通常会涉及锁定数据库中的记录(有时候甚至是整张表)。侵占性的锁定会阻碍并发性,要求事务互相等待已完成各自的工作。

    考虑到完全隔离会导致性能问题,而且不是所有的应用程序都需要完全的隔离,所以有时候应用程序需要在事务隔离上有一定的灵活性。因此,就回有多重隔离级别:

隔离级别 含义
ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据。可以组织脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ 对同一个字段多次读取结果是一致的,除非数据是被本事务自己所修改。可以组织脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE 完全服从ACID的隔离级别,确保组织脏读、不可重复读以及幻读。这是最慢的事务隔离级别,因为它通常是完全锁定事务相关的数据库表来实现的

并不是所有的数据源都支持以上所列的隔离级别。

  • 只读:声明式事务的第三个属性是它是否为只读事务。如果事务只对后端数据库进行读操作,数据库可以利用事务的只读特性来进行一些特定的优化。因为只读优化实在事务启动的时候由数据库实施的只有对那些具备启动一个新事物的传播行为(PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED)的方法来说,将事务声明为只读才有意义。
  • 事务超时:为了使应用程序很好的运行,事务不能运行太长时间。因此声明式事务下一个特性就是超时(timeout)。

          假设事务运行时间特别长。因为事务可能涉及对数据库的锁定,所以长时间的事务会不必要的占用数             据库资源。通常我们声明一个事务在指定的秒数后自动回滚,而不是等待期结束。

          因为超时时钟会在事务开启时启动,所以,只有对那些具备可能启动一个新事物的传播行为                             (PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED)的方法           来说,生命事务超时才有意义。

  • 回滚规则:事务最后一个特性就是回滚规则。这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有在遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。但是你可以声明事务在遇到特定的检查型异常时像遇到运行时异常那样回滚。同样,你也可以声明事务在遇到特定的异常时不回滚,即使这些异常时运行期异常。

配置demo:

       <!--声明式事务  配置事务管理器-->
       <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"/>
       </bean>
       <!--定义事物传播行为-->
       <tx:advice id="demoAdvice" transaction-manager="transactionManager">
              <tx:attributes>
                     <tx:method name="save*" propagation="REQUIRED"/>
                     <tx:method name="insert*" propagation="REQUIRED"/>
                     <tx:method name="add*" propagation="REQUIRED"/>
                     <tx:method name="update*" propagation="REQUIRED"/>
                     <tx:method name="modify*" propagation="REQUIRED"/>
                     <tx:method name="del*" propagation="REQUIRED"/>
                     <!--定义隔离级别  isolation        设置只读read-onlu     超时timeout  回滚规则rollback-for可以自己定义异常 和no-rollback-for指定不回滚 -->
                     <tx:method name="delete*" propagation="REQUIRED" isolation="SERIALIZABLE" timeout="10" rollback-for="com.cn.untils.exception.***Exception"  no-rollback-for="IOException"/>
                     <tx:method name="select*" propagation="REQUIRED" read-only="true"/>
                     <tx:method name="get*" propagation="REQUIRED" read-only="true"/>
                     <tx:method name="find*" propagation="REQUIRED" read-only="true"/>
              </tx:attributes>
       </tx:advice>
       <!--配置事务通知-->
       <aop:config>
              <aop:pointcut id="serviceMethod" expression="execution(* com.zc.dao.*.*(..))"/>
              <aop:advisor advice-ref="demoAdvice" pointcut-ref="serviceMethod"/>
       </aop:config>

测试类:

public class MyBatis extends BaseTest{

    @Autowired
    private DemoService demoService;
    @Test
    public void selecttest(){
        Demo demo = new Demo();
        demo.setName("测试");
        demo.setStpositionRule(1);
        demo.setStpositionValue(Float.parseFloat("2"));
        demoService.addDemo(demo);
    }
}
@Service
public class DemoServiceImpl implements DemoService{

    @Autowired
    private DemoDao demoDao;

    @Override
    public void addDemo(Demo demo) {
        demoDao.insertDemo(demo);
        String a = null;
        a.toString();
        List<Demo> list = demoDao.selectDemos(demo);
        System.out.println(list.toString());
    }

    @Override
    public List<Demo> findDemo(Demo demo) {
        return null;
    }
}

这里提一个有趣的问题,就是虽然最终事务回滚了但是实际上插入语句已经对数据库进行操作了。比如我的ID主键是自动增长的,虽然最终事务回滚了但是我的序列已经被占用了。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部