Spring事务管理


前置知识: springAOP, Mybatis

1.什么是事务管理?

当我们在修改数据库的时候,可能会遇到程序抛出异常,导致没有达到预期的效果,并且产生其他意想不到的错误。例如:银行转账,A 向 B 转 100块钱.

A 账户金额-100
…中间操作…(例如日志记录)
B 账户金额+100

当在中间的某一步出现程序抛出异常,将会导致程序退出.那么 A 账户 -100, B 账户没有收到转账.
Spring事务可以解决该问题的发生,程序异常回滚(恢复原来场景),程序正常提交(修改数据库的信息)

2. 声明式事务管理

2.0 构建转账模型

2.0.1 创建Account类

public class Account {
    private String name;
    private Integer money;
    public Account(String name,Integer money){
        this.name=name;
        this.money=money;
    }
    @Override
    public String toString(){
        return name+" "+money;
    }
}

表示账户的姓名和金额

2.0.2 创建数据库表

create table account(
    name varchar(20) primary key,
    money int
);

分别对应java类Account属性

insert into account(name, money) values('张三',2000),('李四',2000);

插入测试数据

2.0.3 创建mybatis接口(注解)

@Service
public interface ChangeDataBase {
    @Update("update account set money=#{money}+money where name=#{name}")
    public void upAccount(@Param("money") int money, @Param("name") String name);
    @Update("update account set money=money-#{money} where name=#{name}")
    public void downAccount(@Param("money") int money, @Param("name") String name);
    @Select("select  * from account")
    public List<Account> selectAccount();
}

upAccount 函数增加钱,downAccount函数减少钱,selectAccount查询所有信息

2.0.4 调用mybatis接口,创建A赚钱给B的函数

@Component
public class TransferAccounts {
    @Autowired
    private ChangeDataBase changeDataBase;
    public void transferAtoB(String A,String B,int money){
        changeDataBase.downAccount(money,A);
        changeDataBase.upAccount(money,B);
    }
}

2.0.5 主函数调用

public static void main(String[] args) throws Exception {
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("springConfig.xml");
        TransferAccounts transferAccounts=applicationContext.getBean("transferAccounts", TransferAccounts.class);
        try {
            transferAccounts.transferAtoB("张三","李四",500);
        }catch (Exception e){
            System.out.println(e.toString());
        }
        ChangeDataBase changeDataBase=applicationContext.getBean("changeDataBase",ChangeDataBase.class);
        List<Account> list=changeDataBase.selectAccount();
        for(Account l:list){
            System.out.println(l);
        }
    }

张三转账给李四500元,并打印

2.0.6 springConfig.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:aop="http://www.springframework.org/schema/aop"
     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/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">
  <!--扫描spring的注解bean-->
  <context:component-scan base-package="......"/>
  <bean id="data" class="org.apache.commons.dbcp2.BasicDataSource">
      <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
      <property name="url" value="jdbc:mysql://你的数据库位置?characterEncoding=UTF-8"/>
      <property name="username" value="你的账号"/>
      <property name="password" value="你的密码"/>
  </bean>
  <!--mybatis的配置-->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
      <property name="dataSource" ref="data"></property>
  </bean>
  <!--扫描mybatis注解包-->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
      <property name="basePackage" value="包位置"></property>
  </bean>
</beans>

以上是springbean扫描,数据库的配置,mybatis注解扫描

正常情况下体会不到事务管理的重要性,如果在张三(A)转账给李四(B)的过程中出现了程序异常,可能会导致张三的账户扣钱了,李四的账户没有得到李四的转账;或李四得到了张三的转账,张三没有扣钱

2.0.7 异常情况

@Component
public class TransferAccounts {
    @Autowired
    private ChangeDataBase changeDataBase;
    public void transferAtoB(String A,String B,int money){
        changeDataBase.downAccount(money,A);
        int i=1/0; //程序在这里出现异常
        changeDataBase.upAccount(money,B);
    }
}

那么程序将会在 int i=1/0处抛出异常,提前退出程序.导致changeDataBase.downAccount(money,A);执行成功,changeDataBase.upAccount(money,B);没有执行的情况.即A账户扣钱,B账户没有增加钱.哪这就引出了下面的spring事务管理

2.1 XML实现事务管理

在springConfig.xml的文件中增加:

	<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
       <property name="dataSource" ref="data"></property>
   </bean>
   <tx:advice id="mytx" transaction-manager="dataSourceTransactionManager">
       <tx:attributes>
           <tx:method name="*"/>
       </tx:attributes>
   </tx:advice>
   <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* *..TransferAccounts.transferAtoB(..))"/>
       <aop:advisor advice-ref="mytx" pointcut-ref="pointcut"></aop:advisor>
   </aop:config>

下面将从下至上讲解什么意思

2.1.1 使用aop导入spring事务管理

<aop:config>
        <aop:pointcut id="pointcut" expression="execution(* *..TransferAccounts.transferAtoB(..))"/>
        <aop:advisor advice-ref="mytx" pointcut-ref="pointcut"></aop:advisor>
</aop:config>

利用AOP在不改变原有代码的情况下,增加spring事务管理.

其中<aop:pointcut id="pointcut" expression="execution(* *..TransferAccounts.transferAtoB(..))"/>将你想要增加事务管理的函数,设置一个切点. 我这里id 设置为pointcut.我想要让 transferAtoB转账函数增加spring事务管理

<aop:advisor advice-ref="mytx" pointcut-ref="pointcut"></aop:advisor> 这是AOP的另一个实现.通常我们设置AOP使用的是<aop:aspect ref=“…”></aop:aspect> 我们在这里面增加 <aop:before method=“…” pointcut-ref=“…”/> 设置通知,但是这个方法只可以引入自己写的切面.下面我来介绍 <aop:advisor adivce-ref=“…” pointcut-ref=“…” />标签:

public class MyAspect implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
    System.out.println("before");
 }
}

在这里我设置自己的一个切面,MyAspect,它实现了 MethodBeforeAdvice 接口,在springConfig.xml配置文件设置bean : <bean id=“myAspect” class=“aspect.MyAspect”> ,然后AOP引入:

<bean id="myAspect" class="aspect.MyAspect"></bean>
  <aop:config>
     <aop:pointcut id="pointcut" expression="execution(* *..TransferAccounts.transferAtoB(..))"/>
       <aop:advisor advice-ref="myAspect" pointcut-ref="pointcut"></aop:advisor>
   </aop:config>

2.1.2 设置aop:advisor中advice-ref引用的事务切面(aspect),

<tx:advice id="mytx" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
</tx:advice>

它与自己设置的MyAspect实现bean不太一样,它使用<tx advice> 标签,transaction-manager用于指定事务管理器。事务管理器负责管理事务的提交、回滚等操作。tx:attributes是设置事务的一些属性,例如propagation,isolation,timeout等等.如果不设置就是默认值.
<tx:method name=“"/> 表示被管理的方法中,所有都使用同一类属性配置(默认值)
当然也可以指定你想要的配置方案,例如:<tx:method= name="update
” timeout=“20” readOnly=“false” /> ,它的意思是将管理的事务中包含 update的 方法设置属性等待时间为20秒,只读为关闭…

2.1.3 设置tx:advice 中的tarnsaction-manager 的值

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

org.springframework.jdbc.datasource.DataSourceTransactionManager 类控制jdbc为底层的数据库事务管理。其中mybatis底层为jdbc实现的,即可以使用该管理器.
<property name=“dataSource” ref=“data”> 中 ref=“data” 在上面 2.0.6 springConfig.xml 配置中有.它就是数据库里面的一些配置.

3.1.4 加入后的效果

加入上面的三个配置,就为引入了spring的事务管理,设置的execution(* *…TransferAccounts.transferAtoB(…)如果出现异常,事务将会回滚,看上去数据库没有变化.反之程序正常,数据库将正常更新.

2.2 注解配置

有了上面的配置经验,再使用注解配置会方便很多.
只需要加入

	<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
      <property name="dataSource" ref="data"></property>
  </bean>
  <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

第一个是事务管理器,第二个是扫描注解, transaction-manager=“dataSourceTransactionManager” 这个就是在扫描到注解的时候,让他使用dataSourceTransactionManager管理器管理.
哪注解是什么?
@Transactional
只需要将他放在你要管理的方法上,如果你需要设置事务属性,可以在它的括号内写入对应的键值。
例如: @Transactional(timeout = 20,readOnly = false)
java源码的改动(添加注解)

@Component
public class TransferAccounts {
   @Autowired
   private ChangeDataBase changeDataBase;
   @Transactional(timeout = 20,readOnly = false)
   public void transferAtoB(String A,String B,int money){
       changeDataBase.downAccount(money,A);
       int i=1/0;
       changeDataBase.upAccount(money,B);
   }
}

2.3 异常处理

spring事务处理通常不处理捕获异常的,只对没有捕获异常的运行时异常.

@Transactional
public void transferAtoB(String A,String B,int money) throws Exception {
        try {
            changeDataBase.downAccount(money,A);
            int i=1/0;
            changeDataBase.upAccount(money,B);
        }catch (Exception e){
            throw new Exception(); //一定要抛出异常
        }
    }

该情况程序将不会被spring事务所管理.哪应该怎么解决

2.3.1 XML处理

<tx:method name="*" rollback-for="java.lang.Exception"/>

只需要在rollback 指定哪些异常抛出会被处理。多个指定异常类型用逗号分隔.

2.3.2 注解处理

    @Transactional(rollbackFor = {java.lang.Exception.class,java.lang.RuntimeException.class})

多个用逗号分隔

3. 编程式事务管理

根据上面声明式事务编程,将transferAtoB 修改为编程式,springConfig.xml 对tx,aop,的配置删去,其他保持不变:

@Component
public class TransferAccounts {
    @Autowired
    private ChangeDataBase changeDataBase;
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    public void transferAtoB(String A,String B,int money) throws Exception {
        TransactionDefinition tf=new DefaultTransactionDefinition();
        //开启事务
        TransactionStatus ts=dataSourceTransactionManager.getTransaction(tf);
        try {
            changeDataBase.downAccount(money,A);
            int i=1/0;
            changeDataBase.upAccount(money,B);
            //提交事务
            dataSourceTransactionManager.commit(ts);
        }catch (Exception e){
            throw new Exception();
        }
    }
}

private DataSourceTransactionManager dataSourceTransactionManager; 就是前面所说的事务管理器.
现在通过bean注入进去.

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

TransactionDefinition 设置事务属性(隔离级别,传播行为)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值