springmvc实现事务
前言
spring事务管理包含两种情况,编程式事务、声明式事务。而声明式事务又包括基于注解@Transactional和tx+aop的方式。编程式事务管理使用TransactionTemplate或者PlatformTransactionManager。对于编程式事务spring推荐使用TransactionTemplate。
声明式事务管理方式
编程式事务TransactionTemplate需要手动在代码中处理事务,不推荐使用,也不符合spring的思想,因为它直接耦合代码,有时间自己看一下源码。
声明式事务管理分类
声明式事务管理也有两种常用的方式,
一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。
显然基于注解的方式更简单易用,更清爽。
- 基于tx和aop名字空间的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice"
pointcut="execution(* com.zshuai.service..*.*(..))" />
</aop:config>
</beans>
配置文件中都有相应的注解,这样无需对后台的代码做配置,只需要注意对应的service层中的方法需要按照配置文件中的传播行为里的方法名字开头,比如想对添加方法加上事务,就需要insertXXX()这样来命名。
对应的service接口中的方法
package com.zshuai.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zshuai.dao.UserInfoMapper;
import com.zshuai.pojo.UserInfo;
import com.zshuai.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoMapper userMapper;
@Override
public void addUser() {
try {
UserInfo pojo = new UserInfo();
pojo.setId(1);
pojo.setUid(1);
pojo.setName("zshuai");
pojo.setPassword("aaaaaaa");
pojo.setSex(1);
pojo.setAddress("北京市海淀区");
userMapper.insert(pojo);
System.out.println("插入完毕");
int i = 1 / 0;
UserInfo entity = userMapper.selectByPrimaryKey(1);
System.out.println(entity.getName() + "查询的名字");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("除数为0");
}
}
}
只需要添加了service注解以及命名格式正确即可。
- 基于@Transactional注解
配置文件:
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务控制的注解支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
会发现配置文件少了好多东西。
使用这样的方式需要在service层中想开启事务的方法上添加@Transactional注解。这样就自动开启了。
package com.zshuai.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.zshuai.dao.UserInfoMapper;
import com.zshuai.pojo.UserInfo;
import com.zshuai.service.UserService;
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserInfoMapper userMapper;
@Transactional
public void addUser() {
try {
UserInfo pojo = new UserInfo();
pojo.setId(1);
pojo.setUid(1);
pojo.setName("zshuai");
pojo.setPassword("aaaaaaa");
pojo.setSex(1);
pojo.setAddress("北京市海淀区");
userMapper.insert(pojo);
System.out.println("插入完毕");
int i = 1 / 0;
UserInfo entity = userMapper.selectByPrimaryKey(1);
System.out.println(entity.getName() + "查询的名字");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("除数为0");
}
}
}
两种添加注解的方式都可以
项目demo结构
demo下载路径:https://download.csdn.net/download/weixin_37701177/11444763
涉及事务的异常处理
Spring的事务管理默认只对出现运行期异常(java.lang.RuntimeException及其子类)进行回滚。
如果一个方法抛出Exception或者Checked异常,Spring事务管理默认不进行回滚。
Throwable分为Error和Exception(RuntimeException)
Throwable是所有异常的根,java.lang.Throwable
Error是错误,java.lang.Error
Exception是异常,java.lang.Exception
所以,涉及事务的方法如果是直接进行try-catch的话,会导致事务回滚失效。
因为默认情况下spring事务只在发生未被捕获的 RuntimeException时才回滚(运行时异常)
try catch这种把整个包裹起来,这种业务方法也就等于脱离了spring事务的管理,因为没有任何异常会从业务方法中抛出,全被捕获并“吞掉”,导致spring异常抛出触发事务回滚策略失效。
解决方案:
解决方案1:
try {
doSomething();
} catch (Exception e) {
throw new RuntimeException();
}
catch的时候new成spring默认的异常RuntimeException();
解决方案2:
try {
doSomething();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚.
解决方案3:
改变默认方式
在@Transaction注解中定义noRollbackFor和RollbackFor指定某种异常是否回滚。
@Transaction(noRollbackFor=RuntimeException.class)
@Transaction(RollbackFor=Exception.class)
这样就改变了默认的事务处理方式。
事务
事务是一组原子性的SQL查询,是一个独立的工作单元。要么所有全部执行成功,要么全部执行失败。这种思想反映到数据库上,就是多条SQL语句,要么所有执行成功,要么所有执行失败。
事务的特性
- 原子性(Atomicity):
表示组成一个事务的多个数据库操作是一个不可分割的原子单元(可以理解为一个整体),只有所有的操作执行成果,整个事务才会提交。事务中的任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库回滚到初始状态。也就是说不可能执行其中的一部分操作。 - 一致性(consistency):
事务操作成功后,数据库所处的状态和它的业务规则是一致的,即数据不会被破坏。如从A账户转账100元到B账户,不管操作成功与否,A账户和B账户的存款总额是不变的。 - 隔离性(isolation):
在并发数据库操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。准确地说,并非要求做操完全无干扰。数据库规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发越弱。 - 持久性(durability):
一旦事务提交成功后,事务中所有数据操作都必须被持久化到数据库中。即使在提交事务后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
锁
锁就是防止其他事务访问指定资源的手段。锁是实现并发控制的主要方法,是多个用户能够同时操纵同一个数据库中的数据而不发生数据不一致现象的重要保障。 一般来说,锁可以防止脏读、不可重复读和幻读。
脏读(Dirty Read)
A事务读取B事务尚未提交的更改操作,并在这个数据的基础上进行操作。如果恰巧B事务回滚,那么A事务读到的数据根本是不被承认的。
幻读(Phantom)
同一事务中,用同样的操作读取两次,得到的记录数不相同。
注意:幻读重点在新增或删除。
不可重复读(Nonrepeatable Read)
不可重复读是指A事务读取了B事务已经提交的更改数据。同一事务中,两次读取同一数据,得到的内容不同。
注意:不可重复读重点在修改。
丢失更新(Lost Update)
事务T1读取了数据,并执行了一些操作,然后更新数据。事务T2也做相同的事,则T1和T2更新数据时可能会覆盖对方的更新,从而引起错误。
数据库锁机制
对于上述并发引发的问题,数据库通过锁机制是可以解决的。
- 按照锁定的对象不同
表锁
行锁。 - 从并发事务锁定的关系
共享锁定
独占锁定。
共享锁定会防止独占锁定,但允许其他的共享锁定。而独占锁定既防止其他的独占锁定,也防止和其他的共享锁定。
事务隔离级别
READ UNCOMMITTED (未提交读)
在READ UNCOMMITTED 级别,事务中的修改,即使没有提交,对其它事务也都是可见的。事务可以读取未提交的数据,这也就是脏读。在实际一般很少使用。
READ COMMITTED (读提交)
READ COMMITTED 一个事务从开始知道提交之前,所做的任何修改对其他事物都是不可见的。
REPEATABLE READ (可重复度)
REPEATABLE READ 解决了脏读的问题。改几倍保证了在同一个事务中多次读取同样记录的结果是一直的。但是理论上,可重复读隔离级别还是无法解决另一个幻读的问题。
该级别是 Mysql 默认的事务隔离级别。
SERIALIZABLE (序列化 、可串行化)
SERIALIZABLE 是最高的格列界别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE 会再读取的每一行数据上都加锁,所以可能导致大量的超时 和锁征用的问题。实际应用中也很少使用到这个隔离级别。
事务隔离界别整理
以上的五个事务隔离级别都是在Connection接口中定义的静态常量,使用setTransactionIsolation(int level) 方法可以设置事务隔离级别。
如:con.setTransactionIsolation(Connection.REPEATABLE_READ)。
注意:事务的隔离级别受数据库的限制,不同的数据库支持的的隔离级别不一定相同。
SpringBoot使用事务注解
首先在启动类上添加开启注解事务管理
然后在需要添加事务的方法上注解即可