前置知识: 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 设置事务属性(隔离级别,传播行为)