• Spring的事务管理


    虽然我们在用Spring的事务管理,但总体感觉使用的时候未知(未搞懂)的概念还是挺多的,心里总是没有底,在使用的过程中也比较容易用错误的方式来实现。
     
    关于Spring声明式事务,具体可以参考下面这篇文章:
     
     
    目前我们使用的是第五种方式,即全注解方式,这种方式使用比较方便,在spring的配置文件中声明:
     
    ......
        <context:annotation-config />
        <context:component-scan base-package="com.bluesky" />
    
        <tx:annotation-driven transaction-manager="transactionManager" />
    
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
            <property name="configLocation" value="classpath:hibernate.cfg.xml" />
            <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
        </bean>
    
        <!-- 定义事务管理器(声明式的事务) -->
        <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
            <property name="sessionFactory" ref="sessionFactory" />
        </bean>
     
     
    在Service层中需要加入事务的地方加入:
     
    @Transactional
    @Component("userDao")
    public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
        public List<User> listUsers() {
            return this.getSession().createQuery("from User").list();
        }
        ......
    }
     
     
    我们使用@Transactional的时候,都是使用其默认值,但其中的行为如下:
     

    传播属性

     
    @Transactional注解中,默认的传播属性可以在源码中看到,属于PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务,这是最常见的选择。
     
    Propagation propagation() default Propagation.REQUIRED;
     
     
    • PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
    • PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
    • PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
    • PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
    • PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
     
    此外,在Spring中额外提供了另外一个事务管理方式,在EJB中并未对应,只在Spring的事务管理中存在:
     
    PROPAGATION_NESTED:理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的,而Nested事务的好处是他有一个savepoint。
     
    ServiceA {
        //事务属性配置为 PROPAGATION_REQUIRED
        void methodA() {
            try {
                //savepoint
                ServiceB.methodB(); //PROPAGATION_NESTED 级别
            } catch (SomeException) {
                // 执行其他业务, 如 ServiceC.methodC();
            }
        }
    }
     
     
    也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如ServiceC.methodC,继续执行,来尝试完成自己的事务。但是这个事务并没有在EJB标准中定义。
     
    问题:我们当前的所有查询其实都使用的默认传播属性REQUIRED,其实是不合理的,这会造成性能的略微下降;
     

     隔离级别

     
    事务的隔离级别属于数据库的支持,通常分为下面几类,由隔离级别从低到高: 
     
      1. ISOLATION_DEFAULT:这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
      2. ISOLATION_READ_UNCOMMITTED:这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
      3. ISOLATION_READ_COMMITTED: 保证一个事务不能读到另一个并行事务已修改但未提交的数据。数据提交后才能被读取。避免了脏数据。该级别适应于大多数系统。大多数主流数据库默认的级别。
      4. ISOLATION_REPEATABLE_READ:它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。避免了脏读,不可重复读。但是可能出现幻像读。但是也带来更多的性能损失。
      5. ISOLATION_SERIALIZABLE 事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。这是花费最高代价但是最可靠的事务隔离级别。
     
    我们当前的数据库隔离级别,如果没有设置,就为Isolation.DEFAULT,定义显示为使用底层数据源的默认隔离级别。除非DBA有额外设置,对于MySQL InnoDB引擎默认是可重复读的(REPEATABLE READ)
     
    /**
         * Use the default isolation level of the underlying datastore.
         * All other levels correspond to the JDBC isolation levels.
         * @see java.sql.Connection
         */
        int ISOLATION_DEFAULT = -1;
     
     

    READ-ONLY

     
    对于@Transactional注解中的read-only属性,之前感觉我们的理解都不算太透彻,可以参考下面这篇文章:
     
     
    其概念为:从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据);
     
    对于只读查询,可以指定事务类型为readonly,即只读事务,据同事讲,这个时候可以在其中操纵数据库,但是并不会commit到数据源中。
     

    关于只读事务和无事务

     
    那么问题来了,什么时候使用只读事务,什么时候将事务的传播属性改为SUPPORT?
     
    由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段,例如Oracle对于只读事务,不启动回滚段,不记录回滚log。
     
    但是如果连事务都不启动,从理论上讲性能应该更佳,参考其中的一篇blog来讲述这个问题:
     
    • 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;  
    • 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。 【注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务】

     多数据源

     
    如果多个数据源之间不存在分布式事务的话(即此时不会跨数据源对某个服务执行增删改等操作,查询无所谓),数据源之间可以采用直接在spring的配置文件中配置多个数据源,然后分别进行事务的配置。
     
     
    在单数据源时,如果事务配置类型为SUPPORTS且没有嵌套在事务级别中,无论成功执行与否,方法都不会报错,只是无法进行数据库的增删改操作。
     
    如果没有配置事务(服务上没有定义@Transactional注解),则会执行每一步就提交,异常不会影响之前已经提交的修改。
     
    如果设置成REQUIRED,出现异常会导致整体事务的回退,如果不出现异常,一起提交。
     
     
    比如我们有两个数据源,一个为master,一个为slave,这时就需要声明将两个transactionManager纳入事务管理:
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <tx:annotation-driven transaction-manager="transactionManagerSlave"/>
     
    在其中定义两套dataSource, transactionManager, sqlSessionFactory,最重要的是两个MapperScannerConfigurer,通过这个能够正确区分出mapper包使用的事务:
     
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.api.example.pub.dao"/>
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        </bean>
     
     
    这就要求我们要将不同数据源之间使用的Mapper接口,实体类型,以及mybatis mapper文件隔离,以数据源区分。
     
    但此时使用注解声明式事务还测试,还是不能保证正确的事务已经被加上,这是因为当存在多个TransactionManager时,Spring貌似默认使用第一个定义的(加载的)事务管理器,其不知道到底该使用哪个事务管理器。
     
    @Transactional注解加入了一个value属性,用来手动来指定具体使用的事务管理器,可以参考:http://stackoverflow.com/questions/3333133/multiple-transaction-managers-with-transactional-annotation
     
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
    
    	/**
    	 * A qualifier value for the specified transaction.
    	 * <p>May be used to determine the target transaction manager,
    	 * matching the qualifier value (or the bean name) of a specific
    	 * {@link org.springframework.transaction.PlatformTransactionManager}
    	 * bean definition.
    	 */
    	String value() default "";
     
    加入该属性,就可以正常使用@Transactional对应的事务管理器了,需要注意的是,这个我们所说的多个数据源管理并没有涉及到分布式事务,如果需要分布式事务的话这种配置方式肯定是不支持的。 
     
  • 相关阅读:
    document.getElementById()使用方法
    Delphi XE7 发布时间
    JavaScript动态更改页面元素
    TCP/IP-协议族----17、应用层简单
    查看员工信息每个部门的最低工资
    VB6.0“挑衅”.NET!
    MapReduce计数器
    Linux学习记录--命名管道通信
    cocos2d-x V3.0 呼叫加速度计 Acceleration
    Linux Kernel(Android) 加密算法汇总(四)-应用程序调用OpenSSL加密演算法
  • 原文地址:https://www.cnblogs.com/mmaa/p/5789867.html
Copyright © 2020-2023  润新知