spring事务管理

1、spring事务管理基本介绍

Spring 支持编程式事务管理以及声明式事务管理两种方式。

编程式事务管理是侵入性事务管理,编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,所以并不推荐使用。

声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。

 

Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制就是利用 java.sql.Connection 对象完成对事务的提交。在没有Spring帮我们管理事务之前,我们的做法:

Connection conn = DriverManager.getConnection();
try {  
    conn.setAutoCommit(false);  //将自动提交设置为false                         
    //执行CRUD操作
    ... 
    conn.commit();      //当两个操作成功后手动提交  
} catch (Exception e) {  
    conn.rollback();    //一旦其中一个操作出错都将回滚,所有操作都不成功
    e.printStackTrace();  
} finally {
    conn.colse();
}

事务是一系列的动作,一旦其中有一个动作出现错误,必须全部回滚,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态,避免出现由于数据不一致而导致的接下来一系列的错误。事务的出现是为了确保数据的完整性和一致性,在目前企业级应用开发中,事务管理是必不可少的。

 

在企业级应用中,多用户访问数据库是常见的场景,这就是所谓的事务的并发。事务并发所可能存在的问题:

  1. 脏读:一个事务读到另一个事务未提交的更新数据。
  2. 不可重复读:一个事务两次读同一行数据,可是这两次读到的数据不一样。
  3. 幻读:一个事务执行两次查询,但第二次查询比第一次查询多出了一些数据行。
  4. 丢失更新:撤消一个事务时,把其它事务已提交的更新的数据覆盖了。

 

有了Spring,我们再也无需要去处理获得连接、关闭连接、事务提交和回滚等这些操作,使得我们把更多的精力放在处理业务上。事实上Spring并不直接管理事务,而是提供了多种事务管理器。他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

具体的事务管理机制对 Spring 来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以 Spring 事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是 org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现由各个平台自己实现。

 

事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到事务,这个方法里面的参数是 TransactionDefinition 类,这个类就定义了一些基本的事务属性。事务属性包含了5个方面,如图所示:

 

2、声明式事务管理之基于注解方式

如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
</bean>

然后需要开启事务注解,开启事务注解需要引入命名空间 tx。完整的配置文件如下:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <!--开启组件扫描-->
    <context:component-scan base-package="test, service, dao"></context:component-scan>

    <!--引入外部配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>  <!--通过${}使用外部配置文件的值-->
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>

    <!-- 配置JdbcTmplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

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

然后在需要事务管理的地方加上 @Transactional 注解即可,在类上或者在方法上添加该注解都可以。如果注解是添加到类上,则表示给该类里的所有方法都添加事务;如果是添加到方法上,则表示给该方法添加事务。

 

假设类有一个方法,A给B转账,需要给 A 的账户减少100元,同时需要给 B 增加100元,如果没有使用事务,而执行过程中发生了异常,则可能导致数据发生不一致的问题:

@Transactional
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    @Override
    public void updateUser(User user) {
        userDao.updateUser(user);
    }

    @Override
    public void transferAccount() {
       //减少100元
        userDao.reduceMoney();

        //模拟异常
        int i = 100 / 0;

        //增加100元
        userDao.addMoney();
    }
}

如果没有添加 @Transactional 注解,则 A 减少了100元,但后面发生了异常,导致 B 并没有增加100元,由此导致了数据不一致的问题。如果添加了注解,则发生异常会自动回滚,A账户的金额不会减少,B账户也不会增加。

 

2.1、定义事务的传播行为

多事务方法(事务方法指的是将改变数据库表数据的操作)之间互相调用时,对事务之间的管理就称之为传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

Spring 中定义了七种传播行为:

传播行为 含义
PROPAGATION_REQUIRED(默认值) 0 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务,并在自己的事务内运行
PROPAGATION_SUPPORTS 1 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
PROPAGATION_MANDATORY 2 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
PROPAGATION_REQUIRED_NEW 3 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NOT_SUPPORTED 4 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager
PROPAGATION_NEVER 5 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
PROPAGATION_NESTED 6

表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

定义传播行为只需在 @Transactional 注解后面添加 propagation 参数即可:

@Transactional(propagation = Propagation.REQUIRED)
@Service
public class UserServiceImpl implements UserService {
    
}

 

假设 A 方法调用了 B 方法,给 B 定义了 REQUIRED 传播行为。如果 A 方法已经添加了事务,则 B 方法也在 A 的事务内运行;如果 A 方法没有事务,则 B 方法会启动一个新事务,并在该事务内运行。

 

2.2、定义事务的隔离级别

MySQL数据库为我们提供了四种隔离级别:

  1.  Read uncommitted (读未提交):最低级别,任何情况都无法保证。
  2.  Read committed (读已提交):只可避免脏读的发生。
  3. Repeatable read (可重复读,默认值):可避免脏读、不可重复读的发生。
  4. Serializable (串行化):脏读、不可重复读、幻读均可避免

事务的隔离级别可参考:https://www.cnblogs.com/wenxuehai/p/13485440.html

 

spring 设置事务的隔离级别只需要在 @Transactional 注解后面添加 isolation 参数即可,如下设置为 Read commited 级别:

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
@Service
public class UserServiceImpl implements UserService {
    
}

 

2.3、@Transactional 注解的其它参数

2.3.1、超时时间(timeout)

为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

定义超时时间会规定事务必须在一定时间内进行提交,如果超时会自动进行回滚。

spring 设置事务的超时时间只需要在 @Transactional 注解后面添加 timeout 参数即可。该值默认是 -1,即永不超时。我们可以手动设置超时时间,单位为秒:

@Transactional(timeout = 10)
@Service
public class UserServiceImpl implements UserService {
    
}

 

2.3.2、是否只读(readOnly)

事务的其中一个特性是它是否为只读事务,即只查询。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。

spring 设置事务是否只读只需要在 @Transactional 注解后面添加 readOnly 参数即可,默认为 false,非只读,即可读可写。我们可以手动设置为 true,即只读:

@Transactional(readOnly = true)
@Service
public class UserServiceImpl implements UserService {
    
}

 

2.3.3、回滚(rollbackFor)

设置出现哪些异常时进行事务回滚。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚。但是我们可以设置事务在遇到特定的异常时进行回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

spring 设置事务对哪些异常进行回滚需要在 @Transactional 注解后面添加 rollbackFor 参数,并且该参数值为需要设置的异常的 class。

 

2.3.4、不回滚(noRollbackFor)

设置出现哪些异常不进行事务回滚。

spring 设置事务对哪些异常进行回滚需要在 @Transactional 注解后面添加 noRollbackFor 参数,并且该参数值为需要设置的异常的 class。

 

3、声明式事务管理之基于xml配置方式

通过 spring 的 xml 配置文件来实现事务管理:

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <!--开启组件扫描-->
    <context:component-scan base-package="test, service, dao"></context:component-scan>

    <!--引入外部配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"></property>  <!--通过${}使用外部配置文件的值-->
        <property name="url" value="${prop.url}"></property>
        <property name="username" value="${prop.username}"></property>
        <property name="password" value="${prop.password}"></property>
    </bean>

    <!-- 配置JdbcTmplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 注入dataSource -->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 创建事务管理器-->
    <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="*" propagation="REQUIRED" />
<!--            <tx:method name="transferAccount" propagation="REQUIRED" />-->
        </tx:attributes>
    </tx:advice>

    <!-- 配置切入点和切面 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pt" expression="execution(* service.UserServiceImpl.*(..))" />

        <!-- 配置切面 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
    </aop:config>

</beans>

 

posted @ 2021-04-30 16:57  wenxuehai  阅读(123)  评论(0编辑  收藏  举报
//右下角添加目录