1 前言
上一篇写了Spring的AOP
这一篇写一下Spring的事务
2 事务回顾
首先先回顾以下数据库中事务的相关概念
2.1 事务的概念
事务指逻辑上的一组操作,这组操作要么全部成功,要么全部失败。
2.2 事务的特性
事务有四个特性:原子性、一致性、隔离性、持久性
- 原子性(Atomicity):指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 隔离性(Isolation):多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性(Durability):指一个事务一旦被提交,它对数据库中数据的改变就是永久性的。
四个特性首字母合在一起就是 ACID,事务的四个特性。
2.3 事务的隔离问题
如果事务不考虑隔离性,可能会引发如下问题:
2.3.1 脏读
脏读:指一个事务读取了另外一个事务未提交的数据。
例如A有100元,B有100元,此时A向B转100元,在未完成的时候,A又向C转了100元,系统读取了转账前的数值,判定可以转,完成两笔交易以后,A就会是-100元,正常情况下银行卡不应该出现负数金额,所以就是出问题了。
tips:信用卡可以出现负数金额,见参考链接4
2.3.2 不可重复读
不可重复读:指在一个事务内,读取表中的某一行数据,多次读取结果不同。
不可重复读和脏读的区别是:脏读是读取了前一事务未提交的数据,不可重复读是读取了前一事务已提交的数据。
2.3.3 虚读(幻读)
虚读(幻读)是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一样。
2.4 隔离级别
MySQL数据库定义了四种隔离级别:
- 串行化(Serializable):可避免脏读、不可重复读、虚读情况的发生。
- 可重复读(Repeatable):可避免脏读、不可重复读情况的发生。(MySQL默认级别)
- 读已提交(Read committed):可避免脏读情况发生。
- 读未提交(Read uncommitted):最低级别,以上情况均无法保证。
3 Spring中的事务
Spring中可以通过XML实现事务,也可以通过注解实现事务,首先介绍两个通用的东西,然后介绍具体实现。
PROPAGATION和隔离级别可以在下图的类中找到,spring-tx需要在pom.xml中导入。
3.1 事务配置
Spring中有七种PROPAGATION值,PROPAGATION的值指明了对方法要怎么使用事务
- PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。(默认)
- PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务的方式执行。
- PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED:以非事务方式操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED:支持当前事务,如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务
3.2 隔离级别
隔离级别有五个值
- ISOLATION_DEFAULT:使用数据库默认的隔离级别
- ISOLATION_READ_UNCOMMITTED:读未提交
- ISOLATION_READ_COMMITTED:读已提交
- ISOLATION_REPEATABLE:可重复读
- ISOLATION_SERIALIZABLE:串行化
3.3 导入依赖、连接数据库
不管基于什么实现,都需要导入spring事务相关的依赖,在pom.xml导入下列依赖,放在<dependencies></dependencies>
标签中
<!-- Spring 事务 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!-- MySQl connector -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
在springmvc的配置文件中连接数据库,使用的是mysql5,mysql8需要修改连接语句和使用的驱动。
<!-- Spring事务 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 驱动类 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<!-- 连接数据库 -->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"></property>
<!-- 用户名 -->
<property name="user" value="root"></property>
<!-- 密码 -->
<property name="password" value=""></property>
</bean>
3.4 基于XML实现的事务
3.4.1 无事务实现
数据库操作类AccountDaoImpl
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import edu.spring.dao.AccountDao;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void set1() {
this.getJdbcTemplate().update("update account set name = 'aa' where id = 1");
}
@Override
public void set2() {
this.getJdbcTemplate().update("update account set name = 'ba' where id = 2");
}
}
服务类AccountServiceImpl
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import edu.spring.dao.AccountDao;
import edu.spring.service.AccountService;
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void changeNames() {
accountDao.set1();
// int i=1/0;
accountDao.set2();
System.out.println("ok");
}
}
在spring配置文件中引入aop、tx
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
</beans>
在sping配置文件中注册
<!-- 数据库操作 -->
<bean id="accountDao" class="edu.spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 对象 -->
<bean id="accountService" class="edu.spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
使用getBean获取并调用其方法,注意bean的名称。
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("tranConfig.xml");
AccountService accountService=(AccountService) applicationContext.getBean("accountService");
accountService.changeNames();
3.4.2 加入事务
<!-- 开启/注册事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 自定义事务通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="changeNames" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 事务的aop -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* edu.spring.service.*.*(..))"/>
</aop:config>
要注意:
- id引用是否填写正确
- 方法名是否和类中定义的相同
- propagation和isolation的值是否符合需求
开启后可以将accountServiceImpl中注释的除零错误取消,看看是否是以事务的状态执行。
之所以用到aop是因为事务是使用aop实现的。
3.5 基于注解实现的事务
在spring的配置文件中引入tx
<?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: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/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
</beans>
还是之前的类和数据库,需要在AccountServiceImpl中加入注解,在类名的上一行
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import edu.spring.dao.AccountDao;
import edu.spring.service.AccountService;
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void changeNames() {
accountDao.set1();
// int i=1/0;
accountDao.set2();
System.out.println("ok");
}
}
在配置文件中注册并加入事务、注解。
<!-- Spring事务 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm"></property>
<property name="user" value="root"></property>
<property name="password" value=""></property>
</bean>
<bean id="accountDao" class="edu.spring.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="edu.spring.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 开启/注册事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解 -->
<tx:annotation-driven/>
4 结语
1.csdn写博客的时候,使用<font color=""></font>
可以修改标签内字体的颜色。具体见参考链接3.
2.本文仅演示了对数据的操作,在service中调用dao的方法,并不涉及account的实现,需要现在数据库中有相关数据才行。
3.事务配置还有只读、超时和回滚规则,具体见参考链接5。
4.本文没有给出接口的内容,需要自己实现。
5 参考链接
1.事务的概念
2.Spring中propagation的7种事务配置
3.优快云编辑博客时,文字大小、颜色、字体的处理
4.银行卡余额是负数是什么情况?
5.spring 中 isolation 和 propagation 详解
6.事务跟springAOP有什么关系呀