基于可靠消息方案的分布式事务(二):Java中的事务

前言:在上一篇文章 基于可靠消息方案的分布式事务:Lottor介绍 中介绍了常见的分布式事务的解决方案以及笔者基于可靠消息方案实现的分布式事务组件Lottor的原理,并展现了应用的控制台管理。在正式介绍Lottor的具体实现以前,本文首先将会介绍Java中的事务管理,具体来讲是Spring的事务管理。PS:有不少读者提问Lottor是否开源,这里统一回答:是开源的,Lottor目前在笔者所在公司的内部项目应用,而且笔者在将耦合的业务代码重构,将会在下一篇文章同步更新到GitHub,敬请期待。本文较长,适合电脑阅读。若是已对Java中的事务了解,可略过本文,欢迎关注本系列文章。java

Java 事务的类型

事务管理是应用系统开发中必不可少的一部分。Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。 常见的容器事务如Spring事务,容器事务主要是J2EE应用服务器提供的,容器事务大可能是基于JTA完成,这是一个基于JNDI的,至关复杂的API实现。首先介绍J2EE开发中的两个事务:JDBC事务和JTA事务。spring

JDBC 事务

JDBC的一切行为包括事务是基于一个Connection的,在JDBC中是经过Connection对象进行事务管理。在JDBC中,经常使用的和事务相关的方法是: setAutoCommit、commit、rollback等。sql

默认状况下,当咱们建立一个数据库链接时,会运行在自动提交模式(Auto-commit)下。这意味着,任什么时候候咱们执行一条SQL完成以后,事务都会自动提交。因此咱们执行的每一条SQL都是一个事务,而且若是正在运行DML或者DDL语句,这些改变会在每一条SQL语句结束的时存入数据库。有时候咱们想让一组SQL语句成为事务的一部分,那样咱们就能够在全部语句运行成功的时候提交,而且若是出现任何异常,这些语句做为事务的一部分,咱们能够选择将其所有回滚。数据库

public static void main(String[] args) {
        Connection con = null;
        try {
            con = DBConnection.getConnection();
            con.setAutoCommit(false);
            insertAccountData(con, 1, "Pankaj");
            insertAddressData(con, 1, "Albany Dr", "San Jose", "USA");
        }
        catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
            try {
                con.rollback();
                System.out.println("JDBC Transaction rolled back successfully");
            }
            catch (SQLException e1) {
                System.out.println("SQLException in rollback" + e.getMessage());
            }
        }
        finally {
            try {
                if (con != null)
                    con.close();
            }
            catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
复制代码

上面的代码实现了一个简单的插入帐号和地址信息的功能,经过事务来控制插入操做,要么都提交,要么都回滚。express

JDBC为使用Java进行数据库的事务操做提供了最基本的支持。经过JDBC事务,咱们能够将多个SQL语句放到同一个事务中,保证一个 JDBC 事务不能跨越多个数据库!因此,若是涉及到多数据库的操做或者分布式场景,JDBC事务就无能为力了。编程

JTA 事务

一般,JDBC事务就能够解决数据的一致性等问题,鉴于他用法相对简单,因此不少人关于Java中的事务只知道有JDBC事务,或者有人知道框架中的事务(好比Hibernate、Spring)等。可是,因为JDBC没法实现分布式事务,而现在的分布式场景愈来愈多,因此,JTA事务就应运而生。后端

JTA(Java Transaction API),Java事务API容许应用程序执行分布式事务,也就是说事务能够访问或更新两个或更多网络上的计算机资源。JTA指定事务管理器和分布式事务系统中涉及的各方之间的标准Java接口:应用程序,应用程序服务器和控制对受事务影响的共享资源的访问的资源管理器。一个事务定义了彻底成功或根本不产生结果的逻辑工做单元。数组

Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。咱们能够将资源管理器看作任意类型的持久化数据存储;事务管理器承担着全部事务参与单元的协调与控制。JTA 事务有效的屏蔽了底层事务资源,使应用能够以透明的方式参入到事务处理中;可是与本地事务相比,XA 协议的系统开销大,在系统开发过程当中应慎重考虑是否确实须要分布式事务。若确实须要分布式事务以协调多个事务资源,则应实现和配置所支持 XA 协议的事务资源,如 JMS、JDBC 数据库链接池等。服务器

JTA里面提供了 java.transaction.UserTransaction ,里面定义了下面几个方法:微信

  • begin()- 开始一个分布式事务,(在后台 TransactionManager 会建立一个 Transaction 事务对象并把此对象经过 ThreadLocale 关联到当前线程上 )
  • commit()- 提交事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所表明的事务提交)
  • rollback()- 回滚事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所表明的事务回滚)
  • getStatus()- 返回关联到当前线程的分布式事务的状态 (Status 对象里边定义了全部的事务状态)
  • setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚

值得注意的是,不是使用了UserTransaction就能把普通的JDBC操做直接转成JTA操做,JTA对DataSource、Connection和Resource 都是有要求的,只有符合XA规范,而且实现了XA规范的相关接口的类才能参与到JTA事务中来。

关于XA规范,读者能够根据前一篇文章进行补充,目前主流的数据库都支持XA规范。使用的流程以下:

要想使用用 JTA 事务,那么就须要有一个实现 javax.sql.XADataSource 、 javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将能够参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。XAConnection 是参与 JTA 事务的 JDBC 链接。

要使用JTA事务,必须使用XADataSource来产生数据库链接,产生的链接为一个XA链接。

XA链接(javax.sql.XAConnection)和非XA(java.sql.Connection)链接的区别在于:XA能够参与JTA的事务,并且不支持自动提交。

除了数据库,还有JBoss、JMS的消息中间件ActiveMQ等不少组件都是遵照XA协议,2PC和3PC也是符合XA规范的。具体JTA更多的内容,本文再也不展开,后面有机会专门深刻介绍JTA事务。

Spring 事务管理

Spring支持编程式事务管理和声明式事务管理两种方式。Spring全部的事务管理策略类都继承自PlatformTransactionManager接口,类图以下:

PlatformTransactionManager类图

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}
复制代码

其中,#getTransaction(TransactionDefinition)方法根据指定的传播行为返回当前活动的事务或建立一个新的事务,这个方法里面的参数是TransactionDefinition类,这个类定义了一些基本的事务属性。 其余两个方法,分别是提交和回滚,传入TransactionStatus事务状态值。

事务属性

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";
	// 定义事务的传播规则
    Propagation propagation() default Propagation.REQUIRED;
	// 指定事务的隔离级别
    Isolation isolation() default Isolation.DEFAULT;
	// 对于长时间运行的事务定义超时时间
    int timeout() default -1;
	// 指定事务为只读
    boolean readOnly() default false;
	// rollback-for指定事务对哪些检查型异常应当回滚而不提交
    Class<? extends Throwable>[] rollbackFor() default {};
	// 致使事务回滚的异常类名字数组
    String[] rollbackForClassName() default {};
	// no-rollback-for指定事务对哪些异常应当继续执行而不回滚
    Class<? extends Throwable>[] noRollbackFor() default {};
	// 不会致使事务回滚的异常类名字数组
    String[] noRollbackForClassName() default {};
}
复制代码

其实经过 @Transactional能够了解事务属性的定义。事务属性描述了事务策略如何应用到方法上,事务属性包含5个方面:

  • 传播行为
  • 隔离级别
  • 回滚规则
  • 事务超时
  • 是否只读

Spring 事务传播属性

传播行为定义了客户端与被调用方法之间的事务边界,即传播规则回答了这样的一个问题,新的事务应该被启动仍是挂起,或者方法是否要在事务环境中运行。Spring定义了7个以PROPAGATION_开头的常量表示它的传播属性。在TransactionDefinition中定义了以下的常量:

名称 解释
PROPAGATION_REQUIRED 0 支持当前事务,若是当前没有事务,就新建一个事务。这是最多见的选择,也是Spring默认的事务的传播。
PROPAGATION_SUPPORTS 1 支持当前事务,若是当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 2 支持当前事务,若是当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 3 新建事务,若是当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 4 以非事务方式执行操做,若是当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 5 以非事务方式执行,若是当前存在事务,则抛出异常。
PROPAGATION_NESTED 6 若是当前存在事务,则在嵌套事务内执行。若是当前没有事务,则进行与PROPAGATION_REQUIRED相似的操做。

Spring 隔离级别

隔离级别定义了一个事务可能受其余并发事务影响的程度。多事务并发可能会致使脏读、幻读、不可重复读等各类读现象。在TransactionDefinition中定义了以下的常量:

水果 价格 数量
ISOLATION_DEFAULT -1 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED 1 这是事务最低的隔离级别,它充许另一个事务能够看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED 2 保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 4 这种事务隔离级别能够防止脏读,不可重复读。可是可能出现幻读。
ISOLATION_SERIALIZABLE 8 这是花费最高代价可是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。

是否只读

若是事务只对后端的数据库进行读操做,数据库能够利用事务ID只读特性来进行一些特定的优化。经过将事务设置为只读,你就能够给数据库一个机会,让他应用它认为合适的优化措施。由于是否只读是在事务启动的时候由数据库实施的,因此只有对那些具有可能启动一个新事务的传播行为(PROPAGATION_REQUIRED,PROPAGATION_REQUIRED_NEW,PROPAGATION_NESTED)的方法来讲,才有意义。

事务超时

为了使应用程序很好地运行,事务不能运行太长时间。由于超时时钟会在事务开始时启动,因此只有对那些具有可能启动一个新事务的传播行为(同上几种)的方法来讲,才有意义。默认设置为底层事务系统的超时值,若是底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务回滚

事务回滚规则定义了哪些异常会致使事务回滚而哪些不会。默认状况下,事务只有在遇到运行时期异常才回滚,而在遇到检查型异常时不会回滚。就是抛出的异常为RuntimeException的子类(Errors也会致使事务回滚),而抛出checked异常则不会致使事务回滚。能够明确的配置在抛出那些异常时回滚事务,包括checked异常。也能够明肯定义那些异常抛出时不回滚事务。还能够经过编程的setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后所能执行的惟一操做就是回滚。

配置 Spring 事务管理器

Spring中使用xml进行以下的配置便可:

<!-- 配置事务管理器 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
复制代码

若是是Spring Boot,咱们只须要设置好DataSource便可,DataSourceTransactionManagerAutoConfiguration会自动配置好DataSourceTransactionManager

@Bean
		@ConditionalOnMissingBean(PlatformTransactionManager.class)
		public DataSourceTransactionManager transactionManager( DataSourceProperties properties) {
			DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
					this.dataSource);
			if (this.transactionManagerCustomizers != null) {
				this.transactionManagerCustomizers.customize(transactionManager);
			}
			return transactionManager;
		}
复制代码

配置了事务管理器后,事务固然仍是得咱们本身去操做,Spring提供了两种事务管理的方式:编程式事务管理和声明式事务管理,下面分别看一下如何应用。

编程式事务管理

编程式事务管理咱们能够经过PlatformTransactionManager实现来进行事务管理,一样的Spring也为咱们提供了模板类TransactionTemplate进行事务管理,下面主要介绍模板类。

模板类

咱们须要在配置文件中配置:

<!--配置事务管理的模板-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
        <!--定义事务隔离级别,-1表示使用数据库默认级别-->
        <property name="isolationLevelName" value="ISOLATION_DEFAULT"></property>
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"></property>
    </bean>
复制代码

TransactionTemplate帮咱们封装了许多代码,节省了咱们的工做。专门建了一张account表,用于模拟存钱的一个场景。

//BaseSeviceImpl.java
    @Override
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 若是flag 为 true ,抛出异常
        if (flag) {
            throw new Exception("has exception!!!");
        }
    }
    //获取总金额
    @Override
    public Integer sum() {
        return dao.sum();
    }
复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-test.xml"})
public class TransactionTest{
    @Resource
    private TransactionTemplate transactionTemplate;
    @Autowired
    private BaseSevice baseSevice;

    @Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    baseSevice.insert("INSERT INTO account VALUES (100);",false);
                    baseSevice.insert("INSERT INTO account VALUES (100);",false);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    e.printStackTrace();
                }
           }
        });
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
}
复制代码

对于抛出Exception类型的异常且须要回滚时,须要捕获异常并经过调用status对象的setRollbackOnly()方法告知事务管理器当前事务须要回滚。

声明式事务管理

声明式事务管理有两种经常使用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解,随着Spring和Java的版本愈来愈高,越趋向于使用注解的方式。

基于tx和aop命名空间的xml配置文件

<tx:advice id="advice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="insert" propagation="REQUIRED" read-only="false" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="pointCut" expression="execution (* com.blueskykong.service.*.*(..))"/>
        <aop:advisor advice-ref="advice" pointcut-ref="pointCut"/>
    </aop:config>
复制代码
@Test
    public void transTest() {
        System.out.println("before transaction");
        Integer sum1 = baseSevice.sum();
        System.out.println("before transaction sum: "+sum1);
        System.out.println("transaction....");
        try{
            baseSevice.insert("INSERT INTO account VALUES (100);",true);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("after transaction");
        Integer sum2 = baseSevice.sum();
        System.out.println("after transaction sum: "+sum2);
    }
复制代码

基于@Transactional注解

最经常使用的方式,也是简单的方式。只须要在配置文件中开启对注解事务管理的支持。

<tx:annotation-driven transaction-manager="transactionManager"/>
复制代码

而Spring boot启动类必需要开启事务,而开启事务用的注解就是@EnableTransactionManagement

@Transactional(rollbackFor=Exception.class)
    public void insert(String sql, boolean flag) throws Exception {
        dao.insertSql(sql);
        // 若是flag 为 true ,抛出异常
        if (flag){
            throw new Exception("has exception!!!");
        }
    }
复制代码

指定出现Exception异常的时候回滚,遇到检查性的异常须要回滚,默认状况下非检查性异常,包括error也会自动回滚。

总结

本文主要介绍了Java事务的类型:JDBC事务、JTA(Java Transaction API)事务、容器事务。简要介绍了JDBC事务和JTA事务,详细介绍了Spring的事务管理,Spring对事务管理是经过事务管理器来实现的。Spring提供了许多内置事务管理器实现,如数据源事务管理器DataSourceTransactionManager、集成JPA的事务管理JpaTransactionManager等。分别介绍了Spring事务属性包含的5个方面,以及Spring提供的两种事务管理的方式:编程式事务管理和声明式事务管理。其中声明式的事务管理,最为便捷、使用的更多。经过本文的介绍,但愿读者在接触分布式事务时,首先对Java中的事务可以熟悉。JTA事务时,其实也引出了分布式事务的相关概念,对应2PC和3PC的XA规范。

推荐阅读

基于可靠消息方案的分布式事务

订阅最新文章,欢迎关注个人公众号

微信公众号

参考

  1. Spring的事务管理机制
  2. JTA 深度历险 - 原理与实现
  3. Java中的事务——JDBC事务和JTA事务
  4. Spring事务管理详解
相关文章
相关标签/搜索