Spring 事务管理详解

摘要:
我们知道,在Java EE的开发过程中,使用服务方法来处理主要的业务逻辑,而业务逻辑的处理往往伴随着对数据库的多次操作。以我们生活中常见的转移为例。要实现将账户A转移到账户B的功能,服务方法必须有两个操作:首先从要转移的金额中减去账户A的金额,然后将相应的金额添加到账户B。这两个操作必须成功,才能成功转移;如果任何一方失败,另一方必须后退。

事务的概念

  我们知道,在JavaEE的开发过程中,service方法用于处理主要的业务逻辑,而业务逻辑的处理往往伴随着对数据库的多个操作。以我们生活中常见的转账为例,service方法要实现将A账户转账到B账户的功能,则该方法内必定要有两个操作:先将A账户的金额减去要转账的数目,然后将B账户加上相应的金额数目。这两个操作必定要全部成功,方才表示本次转账成功;若有任何一方失败,则另一方必须回滚(即全部失败)。事务指的就是这样一组操作:这组操作是不可分割的,要么全部成功,要么全部失败。

事务的特性

事务具有ACID四个特性:

  • 原子性(Atomicity):事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
  • 一致性(Consistency):事务在完成后数据的完整性必须保持一致
  • 隔离性(Isolation):多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间的数据要相互隔离
  • 持久性(Durability):一个事务一旦被提交,它对数据库中数据的改变应该是永久性的,即使数据库发生故障也不应该对其有任何影响

Spring 事务管理接口

  Spring 事务管理为我们提供了三个高层抽象的接口,分别是TransactionProxyFactoryBean,TransactionDefinition,TransactionStatus

1.PlatformTransactionManager事务管理器

Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,Spring框架并不直接管理事务,而是通过这个接口为不同的持久层框架提供了不同的PlatformTransactionManager接口实现类,也就是将事务管理的职责委托给Hibernate或者iBatis等持久化框架的事务来实现

org.springframework.jdbc.datasource.DataSourceTransactionManager:使用JDBC或者iBatis进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager:使用hibernate5版本进行持久化数据时使用
org.springframework.orm.jpa.JpaTransactionManager:使用JPA进行持久化数据时使用
org.springframework.jdo.JdoTransactionManager:当持久化机制是jdo时使用
org.springframework.transaction.jta.JtaTransactionManager:使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用

PlatformTransactionManager接口源码:

public interface PlatformTransactionManager {
    //事务管理器通过TransactionDefinition,获得“事务状态”,从而管理事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //根据状态提交
    void commit(TransactionStatus var1) throws TransactionException;
   //根据状态回滚
    void rollback(TransactionStatus var1) throws TransactionException;
}
2.TransactionDefinition定义事务基本属性

  org.springframework.transaction.TransactionDefinition接口用于定义一个事务,它定义了Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

2.1隔离级别

什么是事务的隔离级别?我们知道,隔离性是事务的四大特性之一,表示多个并发事务之间的数据要相互隔离,隔离级别就是用来描述并发事务之间隔离程度的大小
在并发事务之间如果不考虑隔离性,会引发如下安全性问题:
脏读 :一个事务读到了另一个事务的未提交的数据
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致
幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致
在 Spring 事务管理中,为我们定义了如下的隔离级别:
ISOLATION_DEFAULT:使用数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED:最低的隔离级别,允许读取已改变而没有提交的数据,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED:允许读取事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ:对同一字段的多次读取结果都是一致的,除非数据事务本身改变,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE:最高的隔离级别,完全服从ACID的隔离级别,确保不发生脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

2.2传播行为

Spring事务传播机制规定了事务方法和事务方法发生嵌套调用时事务如何进行传播,即协调已经有事务标识的方法之间的发生调用时的事务上下文的规则
Spring定义了七种传播行为,这里以方法A和方法B发生嵌套调用时如何传播事务为例说明:
PROPAGATION_REQUIRED:A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务
PROPAGATION_SUPPORTS:A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行
PROPAGATION_MANDATORY:A如果有事务,B将使用该事务;如果A没有事务,B将抛异常
PROPAGATION_REQUIRES_NEW:A如果有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务
PROPAGATION_NOT_SUPPORTED:A如果有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行
PROPAGATION_NEVER:A如果有事务,B将抛异常;A如果没有事务,B将以非事务执行
PROPAGATION_NESTED:A和B底层采用保存点机制,形成嵌套事务

2.3是否只读

如果将事务设置为只读,表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务

2.4事务超时

事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。在 TransactionDefinition 中以 int 的值来表示超时时间,默认值是-1,其单位是秒

2.5回滚规则

回滚规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚

3.TransactionStatus事务状态

org.springframework.transaction.TransactionStatus接口用来记录事务的状态,该接口定义了一组方法,用来获取或判断事务的相应状态信息
TransactionStatus接口源码:

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();// 是否是新的事物

    boolean hasSavepoint();// 是否有恢复点

    void setRollbackOnly();// 设置为只回滚

    boolean isRollbackOnly();// 是否为只回滚

    void flush();// 刷新

    boolean isCompleted();// 是否已完成
}

Spring 事务管理实现方式

Spring 事务管理有两种方式:编程式事务管理声明式事务管理
编程式事务管理通过TransactionTemplate手动管理事务,在实际应用中很少使用,我们来重点学习声明式事务管理
声明式事务管理有三种实现方式:基于TransactionProxyFactoryBean的方式基于AspectJ的XML方式基于注解的方式
我们以用户转账为例来学习这三种不同的实现方式,首先来搭建转账环境
1.建表,初始化数据库

create table account
(
  id    bigint auto_increment primary key,
  name  varchar(32) not null,
  money bigint      not null,
  constraint account_name_uindex
  unique (name)
);
insert into account (name, money) values('Bill', 2000),('Jack', 2000);

数据库原始数据:
数据库.JPG

2.创建DAO实现类

public class TransferDaoImpl extends JdbcDaoSupport implements TransferDao {

    /**
     * @param name 账户名称
     * @param amount 支出金额
     */
    @Override
    public void payMoney(String name, Long amount) {

        String sql = "update account set money=money-? where name=?";
        this.getJdbcTemplate().update(sql, amount, name);
    }

    /**
     * @param name 账户名称
     * @param amount 收入金额
     */
    @Override
    public void collectMoney(String name, Long amount) {

        String sql = "update account set money=money+? where name=?";
        this.getJdbcTemplate().update(sql, amount, name);
    }
}

3.创建Service实现类(事务管理类)

public class TransferServiceImpl implements TransferService {

    private TransferDao transferDao;

    public void setTransferDao(TransferDao transferDao) {
        this.transferDao = transferDao;
    }
     /**
     * @param source 支出方账户名称
     * @param name 收入方账户名称
     * @param amount 转账金额
     */
    @Override
    public void transferMoney(String source, String destination, Long amount) {
        transferDao.payMoney(source, amount);
        int i = 100/0;//此处用于测试抛异常时是否会回滚
        transferDao.collectMoney(destination, amount);
    }
}

4.创建Spring核心配置文件

    <!-- 读取db.properties配置信息 -->
    <context:property-placeholder location="db.properties"></context:property-placeholder>
    <!-- 配置c3p0数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${db.driverClass}" />
        <property name="jdbcUrl" value="${db.url}" />
        <property name="user" value="${db.username}" />
        <property name="password" value="${db.password}" />
    </bean>

    <bean id="transferDao" class="com.tx.dao.impl.TransferDaoImpl">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="transferService" class="com.tx.service.impl.TransferServiceImpl">
        <property name="transferDao" ref="transferDao" />
    </bean>
基于TransactionProxyFactoryBean的方式

在spring核心配置文件中添加事务管理器的配置和TransactionProxyFactoryBean代理对象

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

    <!--配置业务层的代理-->
    <bean id="transferServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <!--配置目标对象-->
        <property name="target" ref="transferService" />
        <!--注入事务管理器-->
        <property name="transactionManager" ref="transactionManager" />
        <!--注入事务属性-->
        <property name="transactionAttributes">
            <props>
                <!--
                    prop的格式:
                        * PROPAGATION :事务的传播行为
                        * ISOLATION :事务的隔离级别
                        * readOnly :是否只读
                        * -Exception :发生哪些异常回滚事务
                        * +Exception :发生哪些异常不回滚事务
                -->
                <prop key="transfer*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Resource(name="transferServiceProxy")
    TransferService transferServiceProxy;

    @Test
    public void contextLoads() {
        //注意,此处引入的是代理对象transferServiceProxy,而不是transferService
        transferServiceProxy.transferMoney("Bill","Jack", 200L);
    }
}

运行结果:

java.lang.ArithmeticException: / by zero

    at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
    at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变
数据库.JPG

基于AspectJ的XML方式

在spring核心配置文件中添加事务管理器的配置、事务的增强以及切面

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

   <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!--配置切面-->
    <aop:config>
        <aop:pointcut id="pointcut1" expression="execution(* com.tx.service.impl.*ServiceImpl.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" />
    </aop:config>

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Test
    public void contextLoads() {
        transferService.transferMoney("Bill","Jack", 200L);
    }
}

运行结果:

java.lang.ArithmeticException: / by zero

    at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:22)
    at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

执行service事务方法时抛出异常,事务回滚,数据库中数据未发生改变
数据库.JPG

基于注解的方式

在spring核心配置文件中添加事务管理器的配置和开启事务注解

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

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />

在事务方法中添加@Transaction注解

    @Transactional
    public void transferMoney(String source, String destination, Long amount) {

        transferDao.payMoney(source, amount);
        int i = 100/0;
        transferDao.collectMoney(destination, amount);
    }

测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringTransactionApplicationTests {

    @Autowired
    TransferService transferService;

    @Test
    public void contextLoads() {
        transferService.transferMoney("Bill","Jack", 200L);
    }
}

运行结果:
```
java.lang.ArithmeticException: / by zero

at com.tx.service.impl.TransferServiceImpl.transferMoney(TransferServiceImpl.java:24)
at com.tx.service.impl.TransferServiceImpl$$FastClassBySpringCGLIB$$5196ddf2.invoke(<generated>)

```
数据库中数据位发生任何改变
数据库.JPG

总结

在声明式事务管理的三种实现方式中,基于TransactionProxyFactoryBean的方式需要为每个进行事务管理的类配置一个TransactionProxyFactoryBean对象进行增强,所以开发中很少使用;基于AspectJ的XML方式一旦在XML文件中配置好后,不需要修改源代码,所以开发中经常使用;基于注解的方式开发较为简单,配置好后只需要在事务类上或方法上添加@Transaction注解即可,所以开发中也经常使用

免责声明:文章转载自《Spring 事务管理详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Qt中文乱码问题(比较清楚,同一个二进制串被解释成不同的语言)创建odoo数据库时出现错误原因下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

springboot 实时监控 spring-boot-starter-actuator 包

对java工程实时监控方式很多,本文主要讲在springboot框架中的监控。 springboot框架,自带了actuator监控,在pom中引入jar包即可,如下 1.引入jar <dependency> <groupId>org.springframework.boot</groupId> <artifac...

EF架构~关系表插入应该写在事务里,但不应该是分布式事务

回到目录 这个标题很有意思,关系表插入,就是说主表和外表键在插入时,可能会有同步插的情况,如在建立主表时,扩展表需要同步完成数据的初始化工作,而对于多表插入时,我们为了保证数据的一致性会针它写在事务中,而对于.net中的事件,它在一些情况下,会不那么单纯,对于ef和linq to sql来说,你的事务如果出现多次提交动作(submitchange | sa...

稳稳的掌握“数据库连接池

摘要 如何打造高性能的数据库连接池框架,可以从哪些角度进行优化,连接池的大量优化实践如何为你的系统保驾护航,本专题将带你走进连接池的世界,为你一一揭晓。 大家可能会有这样疑问:连接池类似于线程池或者对象池,就是一个放连接的池子,使用的时候从里面拿一个,用完了再归还,功能非常简单,有什么可讲的。 可能还会有这样的疑问:高性能这么高大上,一个小小的连接池,如何...

解读SQL 内存数据库的细节

相信大家对内存数据库的 概念并不陌生,之前园子里也有多位大牛介绍过SQL内存数据库的创建方法,我曾仔细 拜读过,有了大致了解,不过仍有很多细节不清晰,比如: (1)内存数据库是把整个数据库放到内存中的吗? (2)数据都在内存里面,那宕机或者断电了,数据不是没有了吗? (3)据在内存是怎么存放的,还是按照页的方式吗,一行的大小有限制吗? (4)内存数据...

PL/pgSQL学习笔记之四

http://www.postgresql.org/docs/9.1/static/plpgsql-structure.html 39.2. PL/pgSQL 的结构 PL/pgSQL是一种块式结构的语言。完整的函数定义必须是一个块。一个块的定义形式如下: [<<label>> ] [DECLARE declaratio...

Oracle redo与undo浅析

http://liwenshui322.iteye.com/blog/1488949 一. 什么是redo(用于前滚数据) redo也就是重做日志文件(redo log file),Oracle维护着两类重做日志文件:在线(online)重做日志文件和归档(archived)重做日志文件。这两类重做日志文件都用于恢复;其主要目的是,万一实例失败或介质失败,...