Spring提供编程和声明两种方式的事务管理。spring事务本质式对数据库事务的进一步封装,spring事务还新增了事务传播机制,二者作用都是为了保证在并发情况下的CURD安全。事务的实现需要锁的支持,过多的使用事务会影响整个系统的并发性,尤其使用高级别的事务隔离级别。
编程方式管理事务
第一步:添加事务依赖包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${version}</version>
</dependency>
第二步:将事务管理器注入IOC容器
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
第三步:在业务代码中添加事务
//1 开启事务
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 进行需要处在同一事务中的数据库操作
......
// 2 正常结束,提交事务
transactionManager.commit(status);
} catch (SomeException) {
// 错误处理
......
// 3 事务回滚
transactionManager.rollback(status);
}
声明方式管理事务
Spring声明方式的事务管理,实现方式有两种,第一种通过xml配置文件注入,第二种通过注解注入,二者都是通过spring AOP完成事务拦截和通过动态代理完成事务注入。
基于xml声明事务实现
xml中配置
<!--1 配置事务管理器,事务管理器需要和具体的数据源关联-->
<bean id="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2 配置事务通知,事务通知和事务管理器相关联-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务属性,关联要开始事务的方法,只有一个method子组件-->
<tx:attributes>
<!--设置事务属性:
isolation:事务隔离级级别,默认值为DEFAULT和数据库事务隔离级别一致,如果此处设置隔离级别和数据库隔离级别不一致,以spring隔离级别为准。
time-out:事务执行超时时间,默认值为-1表示永不超时,如果设置了值,单位秒。
read-only:事务是否为制度,默认是false,表示可以进行读写。
rollback-for:用来指定触发事务回滚的异常类型,可以是JDK中Throwable、Exception、Error及其子类,也可以是自定义类
no-rollback-for:用来指定哪些异常虽然发生,但事务依然正常提交。
propagation:事务传播行为,默认值为REQUIRED:如果有当前事务,则加入当前事务,否则创建一个新的事务;
REQUIRES_NEW:如果当前事务存在,则阻塞当前事务,新创建一个事务,否则直接新创建一个事务。
SUPPORTS:如果存在当前事务,则加入当前事务,否则以不加事务方式执行;
NOT_SUPPORT:始终以非事务方式执行,存在当前事务,则挂起当前事务。
NEVER:始终以事务方式执行,如果存在当前事务,会抛出一个异常。
MANDATORY:如果存在当前事务,则加入当前事务,否则抛出异常。
NESTED:如果当前事务存在,新创建一个子事务,当前事务回滚,子事务也要回滚,子事务回滚,当前事务将异常捕捉后,不必回滚。
-->
<tx:method name="*" rollback-for="Exception.class" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 3 配置事务切入点,以及将事务切入点和事务属性关联起来-->
<aop:config >
<!--配置切入点-->
<aop:pointcut id="accountPointCut" expression="execution(* com.noodles.service.impl.*.*(..))"/>
<!--关联切入点和事务属性-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="accountPointCut"></aop:advisor>
</aop:config>
事务传播介绍
业务调用过程中,习惯将调用方称为上游,被调用方称为下游,下游的事务称为当前事务,上下游业务调用过程,上下游都有可能发生异常,Spring提高7种事务传播方式,用户可以自己具体业务内从,灵活设置事务传播方式。
配置的切入点AccountServiceImpl
public class AccountServiceImpl implements AccountService {
@Override
public void transferMoney(Account from, Account to,double amount) throws Exception {
//1 查询账户from
Account accountFrom = accountDao.getAccountByName(from.getName());
//2 查询账户to
Account accountTo = accountDao.getAccountByName(to.getName());
//3 from 账户扣钱 amount
if(accountFrom.getMoney() < amount){
throw new Exception("账户余额不足,accountName="+accountFrom+",余额="+accountFrom.getMoney());
}
accountFrom.setMoney(accountFrom.getMoney()-amount);
accountDao.updateAccount(accountFrom);
int i =1/0;
//4 to 账户加钱
accountTo.setMoney(accountTo.getMoney()+amount);
accountDao.updateAccount(accountTo);
}
}
测试代码
public void transferMoney() throws Exception {
Account from = new Account();
from.setName("张三");
Account to = new Account();
to.setName("李四");
double amount = 100.0;
accountService.transferMoney(from,to,amount);
}
测试结果:
抛出除数为零的异常,已经扣除金额的账户进行回滚。
基于注解声明式事务实现
在xml中开启基于注解的事务
<!--第一步 配置事务管理器,事务管理器需要和具体的数据源关联-->
<bean id="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--第二步 开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager" ></tx:annotation-driven>
配置的切入点使用@Transactional注解
@Transactional添加到方法是对单个方法添加事务@Transactional添加类上,对类中所有的方法注入事务。事务的所有属性都可以在Transanctional的属性值中添加。
public class AccountServiceImpl implements AccountService {
@Transactional
@Override
public void transferMoney(Account from, Account to,double amount) throws Exception {
//1 查询账户from
Account accountFrom = accountDao.getAccountByName(from.getName());
//2 查询账户to
Account accountTo = accountDao.getAccountByName(to.getName());
//3 from 账户扣钱 amount
if(accountFrom.getMoney() < amount){
throw new Exception("账户余额不足,accountName="+accountFrom+",余额="+accountFrom.getMoney());
}
accountFrom.setMoney(accountFrom.getMoney()-amount);
accountDao.updateAccount(accountFrom);
int i =1/0;
//4 to 账户加钱
accountTo.setMoney(accountTo.getMoney()+amount);
accountDao.updateAccount(accountTo);
}
}
测试代码不变。
事务失效情况
1 使用注解方法非public
spring在方法进行动态代理时,会判断方法的修饰符是否为public。一般,被外部调用的方法需要设置成public方法,非public方法一般是类内部调用,根据动态代理实现方式,被代理类方法被外部方法调用时才会生成动态代理类,对应为方法添加事务,需要在新的动态代理类实现。
解决办法:可以使用编程方式在非public方法中添加事务。
2 同一个类内部同时被Transactional修饰的方法进行调用
失效原因时内部类调用,不会生成新的动态代理类,无法为被调用方法添加事务。动态代理实现
解决办法:将当前类注入自己内部,使用该注入对象进行类内方法调用。
3 方法被final或者static修饰
都不能在动态内部类中重写。
4 被@Transactional修饰的方法没有注册到IoC容器
5 多线程调用
public methodA(Account account){
new Thread(()->{
userDao.update(account);
})
...
}
一个线程对应一个数据连接,拥有不同数据库连接的线程无法同时提交和回滚。spring中的事务是通过数据库连接实现的,每个线程中维护着一个map,map的key是数据源,value是数据库连接。
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
6 try catch被滥用
发生的异常被捕捉到后,程序会继续成长运行。
解决办法:添加事务操作的代码不要使用try。
6 表本身不持支事务
数据库引擎使用myisam不支持事务。
解决办法:数据库切换成innodb存储引擎。