spring事务相关

本文深入探讨事务的四个关键特性:原子性、一致性、隔离性和持久性,并详细阐述Spring框架下事务的基本属性,包括传播行为、隔离规则、回滚规则、事务超时和是否只读。文章还提供了一系列具体的例子,演示如何在Java中利用Spring事务进行编程式和基于AOP的事务管理,同时介绍了JDBC处理事务的方式。此外,文章详细解释了事务配置的各个方面,包括配置数据库连接池、JdbcTemplate、事务管理器和事务模板,以及如何通过XML和注解配置事务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

事务简介

事务是对数据库操作的一组序列。当操作序列中的所有操作都成功执行时,事务执行成功。当序列中任一操作失败时,此时对数据库的操作返回至事务未执行的状态。

事务的4个特性

事务的特性可以总结为ACID
原子性(Atomicity):事务里的操作看做一个整体,是不可分割的原子单位,其所有操作要么全部成功,要么全部失败;
一致性(Consistency):事务一旦完成,其业务结果必须有一致性,而不是部分成功,部分失败;
隔离性(Isolation):不同的事务并行,必须不能互相影响各自事务的运行;
永久性(Durability):事务一旦完成,无论发生什么系统错误,其结果有不可逆性,不受影响。

事务并行带来的可能问题

多个事务并发运行,处理相同的数据,可能会带来一下三个问题:
脏读:一个事务在事务过程中读取另一个事务修改但未提交的数据,如果修改的数据被回滚,那么第一个事务获取的数据就是无效的;
不可重复读:在一次事务中进行了两次或以上的查询,但是两次查询的结果不一致。因为另一个事务在查询过程中进行了更新(重点是修改);
幻读:与不可重复读类似。发生在第一个事务先查询了几条数据,然后另一个事务又插入或者删除了数据,在随后的查询中,第一个事务就多了或者少了一些原本不存在的数据(重点是新增或者删除)。
为了解决上述问题,事务定义了几个隔离级别:
Read uncommitted:读未提交,最低级别的事务隔离级别,基本不用,会产生脏读情况。对于其他事务未提交的修改操作,本事务查询都会查得到。
Read committed:读已提交。一个事务要等待其他事务提交后才能读取数据。但不能避免不可重复读情况,即在事务的两次查询过程中,另一个事务修改了数据,则两次查询结果不一致;
Repeatable read:可重复读。一个事务过程中的多次查询结果是一致的。(幻读:假设表里有十条记录。ab事务各开启,b事务插入一条并提交(此时b事务查询应该是11条),a在当前事务查询仍旧是10条,等a提交事务后再提交,就变成11条??这其实已经解决了幻读问题了吧);
串行化:所有事务必须串行执行。

Spring事务的基本属性

spring通过一些配置,可以定义事务策略如何应用到方法上。其包含了一下5个部分:事务的传播行为,隔离规则,回滚规则,事务超时,是否只读。

回滚规则

定义了事务方法中哪些异常会导致事务回滚而哪些异常不会回滚。在spring事务中,默认的回滚规则是只有遇到运行时异常才会回滚(即除数为0,数组越界等异常),而已检查异常(即操作文件流等需要进行try。。。catch等的异常)默认是不会回滚的。可以通过在注解中设置@Transactional()rollbackFor=Exception.class)来确保所有的异常都能被回滚。另外,如果异常被try。。。catch了,则spring无法捕获异常了,此时即使有运行时异常也不会回滚,因此在程序中,可以在catch中继续抛出异常throw e来确保事务方法遇到异常能进行回滚。

传播行为:

当事务方法被另一个事务方法调用时,应当制定事务应如何传播。根据是否需要事务环境可以分为以下其中:
PROPAGATION_REQUIRED:表示当前的方法必须运行在事务中,如果当前事务存在,那就运行在该事务中,否则启动一个新的事务。这是默认的传播行为。
PROPAGATION_SUPPORTS:表示当前的方法可以运行在事务中,如果当前事务存在,那就运行在该事务中,否则就在没有事务的环境下运行。
PROPAGATION_MANDATORY:表示当前的方法必须运行在事务中,如果当前事务存在,那就运行在该事务中,否则就报异常。
PROPAGATION_REQUIRED_NEW:表示当前的方法必须运行在自己的事务中,一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
PROPAGATION_NOT_SUPPORTED:表示当前的方法不应该运行在自己的事务中,,在该方法执行期间,当前事务会被挂起。
PROPAGATION_NEVER:表示当前的方法不应该运行在事务中,在该方法执行期间,如果有事务则会抛出异常。
PROPAGATION_NESTED:表示如果当前已经有一个事务,那么当前方法将会嵌套一个新的事务,新的事务独立于当前事务进行单独的提交或者回滚,如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。
具体例子可参考https://segmentfault.com/a/1190000013341344#articleHeader16

事务的隔离级别:

见上,主要是分四种,读未提交,读已提交,可重复读,串行化。

事务的超时

超时设置主要是为了避免事务长时间运行会导致锁住后端的数据库,设置超时后,如果在规定的时间内没有执行完毕,那么就会自动的回滚事务。

是否只读

定义事务是否为只读事务,只读事务不对数据库进行修改。只读事务中,从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!

事务的配置

jdbc处理事务

    /*
     * jdbc操作事务
     */
    @Test
    public void demo6(){

         Connection conn =null;
        try {
              conn = getConn();
             conn.setAutoCommit(false);

             String sql="update bank set money=money+? where username=?";
             PreparedStatement pstmt=(PreparedStatement) conn.prepareStatement(sql);
             pstmt.setInt(1, 100);
             pstmt.setString(2,"andy");
             pstmt.executeUpdate();

             int i=1/0;

             String sql2="update bank set money=money-? where username=?";
             PreparedStatement pstmt2=(PreparedStatement) conn.prepareStatement(sql);
             pstmt2.setInt(1, 100);
             pstmt2.setString(2,"李琳");
             pstmt2.executeUpdate();

             conn.commit();

        } catch (Exception e) {
            // TODO: handle exception
            try {
                conn.rollback();
            } catch (Exception e2) {
                // TODO: handle exception
            }
        }

    }

    public static Connection getConn(){

        DriverManagerDataSource dataSource=new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        Connection conn=null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn=(Connection) DriverManager.getConnection("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8", "root", "123456");
        } catch (Exception e) {
            // TODO: handle exception
        }
        return  conn;
    }

其标准用法是:

try{
     conn = getConn();
    conn.setAutoCommit(false);
    ...
    conn.commit();
}catch(){
   conn.rollback();
}

编程式事务

jdbc的事务每次都需要commit和rollback等待,spring将这些操作抽象出来建立模板TransactionTemplate,我们可以使用这个模板,通过模板中的回调方式来完成事务中的业务逻辑。
首先在xml中配置事务模板:

<!-- 配置数据库连接池 -->

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>

    </bean>

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

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

    <!--配置事务模板  -->
    <bean id="transcationTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>



    <!-- 2.Dao -->
    <bean name="accountDao" class="com.test.spring.txcode.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 3.Service -->
    <bean name="accountService" class="com.test.spring.txcode.service.AccountServiceImpl">
        <property name="ad" ref="accountDao"></property>
        <!-- <property name="tt" ref="transactionTemplate" ></property> -->
    </bean> 

然后在代码中,完成事务模板的回调

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // TODO Auto-generated method stub
                //减钱
                ad.decreaseMoney(from, money);
                int i = 1/0;
                //加钱
                ad.increaseMoney(to, money);
            }
        });

上面的编程式事务虽然解决了jdbc事务中每次的 conn.setAutoCommit(false); conn.commit();等操作,但是仍然需要为每个事务编写回调方法,既麻烦又会有代码耦合的问题,其实spring事务可以通过基于aop配置,通过xml或者注解完成。

基于springAOP的xml配置事务

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 配置数据库连接池 -->

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>

    </bean>

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

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

    <!--配置事务  -->

    <!-- 2.Dao -->
    <bean name="accountDao" class="com.test.spring.tx.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 3.Service -->
    <bean name="accountService" class="com.test.spring.tx.service.AccountServiceImpl">
        <property name="ad" ref="accountDao"></property>
        <!-- <property name="tt" ref="transactionTemplate" ></property> -->
    </bean> 

   <!--配置事务的增强,类似于通知  -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
                <!--定义每个方法的通知属性  -->
                <tx:method name="transfer" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

   <!--配置切入点  -->
   <aop:config>
        <aop:pointcut  expression="execution(* com.test.spring.tx.service.AccountServiceImpl.*(..))" id="pointcut1"></aop:pointcut>
        <!-- <aop:advice advice-ref="txAdvice" pointcut-ref="pointcut1"></aop:advice> -->
          <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1" />
   </aop:config>



</beans>

通过xml将事务通知切入到需要被事务管理的方法(业务逻辑中)

通过注解配置

只需在xml配置文件中加入如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
     http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- 配置数据库连接池 -->

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>

    </bean>

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

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

    <!--配置事务  -->

    <!-- 2.Dao -->
    <bean name="accountDao" class="com.test.spring.tx.dao.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 3.Service -->
    <bean name="accountService" class="com.test.spring.tx.service.AccountServiceImpl">
        <property name="ad" ref="accountDao"></property>
        <!-- <property name="tt" ref="transactionTemplate" ></property> -->
    </bean> 

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



</beans>

然后在需要被事务管理的方法中头上加入@Transactional(isolation=Isolation.DEFAULT)即可。
Spring的事务管理机制实现的原理,其实就是通过一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的 方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值