Spring事务管理

一个转账的问题

pom

参考:https://www.cnblogs.com/uncleyong/p/17026215.html

创建表

添加数据

 

实体类

逆向生成实体类,并添加无参构造、带参构造、toString

package com.qzcsbj.bean;


public class Account {

  private long id;
  private String username;
  private double money;

  public Account() {
  }

  public Account(String username, double money) {
    this.username = username;
    this.money = money;
  }

  public long getId() {
    return id;
  }

  public void setId(long id) {
    this.id = id;
  }


  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }


  public double getMoney() {
    return money;
  }

  public void setMoney(double money) {
    this.money = money;
  }

  @Override
  public String toString() {
    return "Account{" +
            "id=" + id +
            ", username='" + username + '\'' +
            ", money=" + money +
            '}';
  }
}

dao层(mapper层)

mapper接口

package com.qzcsbj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qzcsbj.bean.Account;

/**
 * @公众号 : 全栈测试笔记
 * @博客 : www.cnblogs.com/uncleyong
 * @微信 : ren168632201
 * @描述 : <>
 */
public interface AccountMapper extends BaseMapper<Account> {
}

service层

service接口

package com.qzcsbj.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.qzcsbj.bean.Account;

/**
 * @公众号 : 全栈测试笔记
 * @博客 : www.cnblogs.com/uncleyong
 * @微信 : ren168632201
 * @描述 : <>
 */
public interface AccountService extends IService<Account> {
    // 这里只是演示,假设username是唯一的
    public int transferMoney(String fromName,String toName,double money);
}

service实现类

package com.qzcsbj.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qzcsbj.bean.Account;
import com.qzcsbj.mapper.AccountMapper;
import com.qzcsbj.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    @Autowired
    AccountMapper accountMapper;

    public int transferMoney(String fromName, String toName, double money) {
        // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱
        // 账户A
        Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName));
        accountA.setMoney(accountA.getMoney()-money);
        int countA = accountMapper.updateById(accountA);
        // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName));

        // 账户B
        Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName));
        accountB.setMoney(accountB.getMoney()+money);
        int countB = accountMapper.updateById(accountB);
        // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName));

        return countA+countB;
    }
}

测试类
package com.qzcsbj.test;

import com.qzcsbj.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


/**
 * @公众号 : 全栈测试笔记
 * @博客 : www.cnblogs.com/uncleyong
 * @微信 : ren168632201
 * @描述 : <>
 */
@RunWith(SpringJUnit4ClassRunner.class)  // 表示Spring和JUnit整合测试
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransaction {
    @Autowired
    AccountService accountService;

    @Test
    public void testTransferMoney(){
        String fromName = "jack";
        String toName = "tom";
        double money = 10.0;
        int i = accountService.transferMoney(fromName, toName, money);
        if (i==2){
            System.out.println(fromName + "给"+ toName + "转账" + money + "元成功");
        } else {
            System.out.println(fromName + "给"+ toName + "转账" + money + "元成功");
        }
    }
}

结果

表中数据正确

模拟异常

修改service实现类,模拟异常

package com.qzcsbj.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qzcsbj.bean.Account;
import com.qzcsbj.mapper.AccountMapper;
import com.qzcsbj.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;


@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService {

    @Autowired
    AccountMapper accountMapper;

    public int transferMioney(String fromName, String toName, double money) {
        // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱
        // 账户A
        Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName));
        accountA.setMoney(accountA.getMoney()-money);
        int countA = accountMapper.updateById(accountA);
        // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName));

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

        // 账户B
        Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName));
        accountB.setMoney(accountB.getMoney()+money);
        int countB = accountMapper.updateById(accountB);
        // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName));

        return countA+countB;
    }
}

测试结果

运行出异常了,jack的账户扣了钱(mybatis底层是jdbc,默认自动提交事务,所以转出这个操作会成功入库),tom的账户没变

所以,transferMoney这个业务方法需要在事务中运行才可以。

事务(Transaction)简介

事务是一系列的动作,是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态。

在企业级应用程序开发中,事务管理是必不可少的技术,用来确保数据的完整性和一致性。

Spring事务管理的核心接口

org.springframework.transaction下三个接口是Spring中事务的顶级接口:平台事务管理器、事务定义接口、事务状态接口

PlatformTransactionManager接口中:

三个方法的作用:

  获取TransactionStatus对象,从而管理事务

  提交事务

  事务过程执行异常,回滚事务

TransactionDefinition接口中:

上面五个方法:  

  获取事务的传播行为
  获取事务的隔离级别
  获取事务的超时机制,默认超时时间,默认值是-1,使用数据库底层的超时时间,如果底层数据库事务系统没有设置超时值,那么就是none,表示没有超时限制,也就是不超时
  获取事务是否只读事务,增删改是读写,查是只读
  获取当前事务名称
事务的传播行为:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
Spring定义了如下七中传播行为,这里以A业务和B业务之间如何传播事务为例说明(A业务调用B业务):

1、PROPAGATION_REQUIRED :required , 必须。默认值,A如果有事务,B将使用该事务;如果A没有事务,B将创建一个新的事务。

2、PROPAGATION_SUPPORTS:supports ,支持。A如果有事务,B将使用该事务;如果A没有事务,B将以非事务执行。

3、PROPAGATION_MANDATORY:mandatory ,强制。A如果有事务,B将使用该事务;如果A没有事务,B将抛异常。

4、PROPAGATION_REQUIRES_NEW :requires_new,必须新的。如果A有事务,将A的事务挂起,B创建一个新的事务;如果A没有事务,B创建一个新的事务。

5、PROPAGATION_NOT_SUPPORTED :not_supported ,不支持。如果A有事务,将A的事务挂起,B将以非事务执行;如果A没有事务,B将以非事务执行。

6、PROPAGATION_NEVER :never,从不。如果A有事务,B将抛异常;如果A没有事务,B将以非事务执行。

7、PROPAGATION_NESTED :nested ,嵌套。如果A当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
  
事务的隔离级别:定义一个事务可能受其他并发事务影响的程度
  并发事务引起的问题:脏读、不可重复读、幻读
Spring事务管理中,定义了如下的隔离级别:

1、ISOLATION_DEFAULT:使用后端数据库默认的隔离级别(不同的数据隔离级别不同)

2、ISOLATION_READ_UNCOMMITTED:【读未提交】最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

3、ISOLATION_READ_COMMITTED:【读已提交】允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

4、ISOLATION_REPEATABLE_READ:【可重复读】对同一字段的多次读取结果都是一致的,可以阻止脏读和不可重复读,但幻读仍有可能发生

5、ISOLATION_SERIALIZABLE:【串行化】最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,性能差

TransactionStatus这个接口中:

6个方法的作用:

  是否是新的事务

  是否有保存点

  设置回滚

  是否回滚

  刷新

  是否完成

这个接口描述的是控制事务执行和查询事务状态的方法。

mybatis底层是jdbc,所以需要配置DataSourceTransactionManager这个类

配置声明式事务:基于aop的xml配置

配置applicationContext.xml(基于这里<https://www.cnblogs.com/uncleyong/p/17026215.html>的xml添加如下内容)

下面只配置了一个方法需要织入事务

    <tx:advice id="adviser" transaction-manager="transactionManager">
        <!--配置哪些方法需要配代理织入事务-->
        <tx:attributes>
            <tx:method name="transferMoney"/>
        </tx:attributes>
    </tx:advice>

配置AOP切入点

    <aop:config>
        <aop:pointcut id="pointcut" expression="execution(* com.qzcsbj.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="adviser" pointcut-ref="pointcut"/>
    </aop:config>

测试报错

数据库回滚了,说明事务起作用了

其它配置

propagation:事务的传播性,非必须,默认值REQUIRED
isolation:隔离级别,非必须,mysql填可重复读REPEATABLE_READ;如果不配置就是默认isolation="DEFAULT"
timeout="-1",事务超时时间,单位是秒,非必须;默认值是-1,使用数据库底层的超时时间,如果底层数据库事务系统没有设置超时值,那么就是none,表示没有超时限制,也就是不超时;
read-only:只读,非必须,默认值是false,查询设置true,非查询设置false
no-rollback-for:非必须,示不被触发进行回滚的 Exception(s)
rollback-for:非必须,表示将被触发进行回滚的 Exception(s)

配置声明式事务:基于aop的注解配置

applicationContext.xml配置(基于这里<https://www.cnblogs.com/uncleyong/p/17026215.html>的xml添加如下内容)

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

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

可以在方法上加注解

    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout = -1,readOnly = false,rollbackFor ={Exception.class})
    public int transferMoney(String fromName, String toName, double money) {
        // 账户A给账户B转钱,这里只是演示,假设账户A的钱≥要转出的钱
        // 账户A
        Account accountA = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", fromName));
        accountA.setMoney(accountA.getMoney()-money);
        int countA = accountMapper.updateById(accountA);
        // int countA = accountMapper.update(accountA,new QueryWrapper<Account>().eq("username",fromName));

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

        // 账户B
        Account accountB = accountMapper.selectOne(new QueryWrapper<Account>().eq("username", toName));
        accountB.setMoney(accountB.getMoney()+money);
        int countB = accountMapper.updateById(accountB);
        // int countB = accountMapper.update(accountB,new QueryWrapper<Account>().eq("username",toName));

        return countA+countB;
    }

测试异常

自动回滚了

也可以在类上加注解,表示全局,而方法上加注解表示局部,如果全局和局部都有注解,那么方法上的注解优先。

【bak】

原文会持续更新,原文地址:https://www.cnblogs.com/uncleyong/p/17035170.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值