文章目录
概述
什么是事务
事务是数据库操作最基本单元。逻辑上一组操作,要么都成功,如果有一个失败所有操
作都失败。
事务的四个特性
- 原子性 Atomicity
- 一致性 Consistency
- 隔离性 Isolation
- 持久性 Durability
搭建环境
模拟转账场景:Lucy给Mary转账,Lucy少钱,Mary多钱。
配置步骤
-
引入相关jar包
druid-1.1.9.jar
mysql-connector-java-5.1.7-bin.jar
spring-jdbc-5.2.6.RELEASE.jar
spring-orm-5.2.6.RELEASE.jar
spring-tx-5.2.6.RELEASE.jar
引入后所有包:
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.1.jar
druid-1.1.9.jar
mysql-connector-java-5.1.7-bin.jar
spring-aop-5.2.6.RELEASE.jar
spring-aspects-5.2.6.RELEASE.jar
spring-beans-5.2.6.RELEASE.jar
spring-context-5.2.6.RELEASE.jar
spring-core-5.2.6.RELEASE.jar
spring-expression-5.2.6.RELEASE.jar
spring-jdbc-5.2.6.RELEASE.jar
spring-orm-5.2.6.RELEASE.jar
spring-tx-5.2.6.RELEASE.jar -
创建数据库和建表
数据库user_db、表t_account
插入数据后表结果:id username money ------ -------- -------- 1 Lucy 1000 2 Mary 1000
-
spring的配置文件注入连接池和jdbcTemplate
-
创建 service和dao并完成对象的创建和注入关系
-
在dao中创建多钱和少钱的方法,在service中创建转账的方法
示例代码
代码结构:
└─src
│ bean.xml
│
└─com
└─spring5
│ Test.java
│
├─dao
│ UserDao.java
│ UserDaoImpl.java
│
└─service
UserService.java
bean.xml:
<!--组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--set方式注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
UserDao接口:
public interface UserDao {
void addMoney();
void reduceMoney();
}
UserDaoImpl类:
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void addMoney() {
String sql = "update t_account set money=money+? where username=?";
jdbcTemplate.update(sql,100, "Mary");
}
@Override
public void reduceMoney() {
String sql = "update t_account set money=money-? where username=?";
jdbcTemplate.update(sql,100, "Lucy");
}
}
UserService类:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void accountMoney(){
// 两个方法是为了更好的看清楚事务案例
userDao.reduceMoney();
userDao.addMoney();
}
}
Test类:
public class Test {
@org.junit.Test
public void testAccount(){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.accountMoney();
}
}
运行后程序没有异常,数据库效果:
id username money
------ -------- --------
1 Lucy 900
2 Mary 1100
场景引入
上面的例子若运行有异常,如下所示:
public void accountMoney(){
// 两个方法是为了更好的看清楚事务案例
userDao.reduceMoney();
int a = 100/0;
userDao.addMoney();
}
数据库效果:
id username money
------ -------- --------
1 Lucy 800
2 Mary 1100
Lucy少钱,而Mary没多钱。这时候就需要用到事务。
Sping事务管理介绍
- 一般把事务添加到service层
- 事务管理方式
编程式事务管理 :1、开启事务 2、执行业务逻辑 3、若业务逻辑没有异常提交事务,若有异常事务回滚。
声明式事务管理:1、基于注解实现 2、基于XML实现 - Spring的声明式事务底层用的是AOP原理
声明式事务管理
注解实现
配置步骤
-
在spring配置文件中配置事务管理器
-
在spring配置文件中开启事务注解
需要引入tx空间xmlns:tx="http://www.springframework.org/schema/tx" http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
-
使用@Transactional注解开启事务。
如果把这个注解添加类上面,这个类里面所有的方法都添加事务,如果把这个注解添加方法上面,为这个方法添加事务。
Spring事务的传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
前两种比较常用,需要掌握。
spring事务的隔离级别
问题
事务有隔离性,多事务操作之间不会产生影响。不考虑隔离性产生很多问题,比如脏读、不可重复读和幻读。
- 脏读:一个未提交事务读取到另一个未提交事务的数据。
比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。 - 不可重复读:一个未提交事务读取到另一提交事务修改数据。一个事务对同一行数据重复读取两次,但是却得到了不同的结果。
比如银行取钱,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。 - 幻读:一个未提交事务读取到另一提交事务添加或删除数据。
比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
解决方法
隔离级别/是否解决问题 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED(读未提交) | 否 | 否 | 否 |
READ_COMMITED(读已提交) | 是 | 否 | 否 |
REPEATABLE_READ(可重复读) | 是 | 是 | 否 |
SERLALIZABLE(串行化) | 是 | 是 | 是 |
Spring隔离级别DEFAULT将使用底层数据库的默认事务隔离级别。MySQL默认隔离级别是REPEATABLE_READ,代码如下:
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.REPEATABLE_READ)
public class UserService {
}
Spring事务的其他参数
- timeout :超时时间
事务需要在一定时间内进行提交,如果不提交进行回滚;默认值是 -1表示不失效,设置时间以秒单位进行计算 - readOnly :是否只读
readOnly 默认值 false,可以进行增删查改操作;设置为true后只能进行查询操作 - rollbackFor :回滚
设置出现哪些异常进行事务回滚 - noRollbackFor :不回滚
设置出现哪些异常不进行事务回滚
示例代码如下:
@Transactional(timeout = 100,readOnly = false,rollbackFor = {ArrayIndexOutOfBoundsException.class,RuntimeException.class})
XML实现
步骤
- 配置事务管理器
- 配置通知
- 配置切入点和切面
相关代码
java代码中去掉@Transactional注解,其他不变。spring配置文件代码如下:
<!--组件扫描-->
<context:component-scan base-package="com.spring5"></context:component-scan>
<!--数据库连接池-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql:///user_db?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="root" />
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--set方式注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--1 创建事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name= "dataSource" ref= "dataSource"></property>
</bean>
<!--2 配置通知-->
<tx:advice id= "txadvice">
<!--配置事务参数-->
<tx:attributes>
<!--指定哪种规则的方法上面添加事务 *表示匹配所有-->
<!--<tx:method name="account*"/>-->
<tx:method name= "accountMoney" propagation= "REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--3 配置切入点和切面-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id= "pt" expression= "execution(* com.spring5.service.UserService.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref= "txadvice" pointcut-ref= "pt"/>
</aop:config>