zoukankan      html  css  js  c++  java
  • 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>
  • 相关阅读:
    获取SqlServer2005表结构
    SQL SERVER 2005连接其它数据库并导入数据表
    vs2008安装失败问题
    Elmah使用方法
    使用postman发送请求,body为空
    docker的简单使用
    mongodb5最新版本的安装和向外暴露端口
    初探gin框架
    img图片的src指定为网络中随便找的图片链接,但是控制台报错get请求403
    父元素为flex布局时,设置最后一个子元素靠右,其他靠左
  • 原文地址:https://www.cnblogs.com/wenxuehai/p/14722662.html
Copyright © 2011-2022 走看看