set global transaction isolation level read uncommitted;
set global transaction isolation level read committed;
set global transaction isolation level repeatable read;
set global transaction isolation level serializable;
>
> 需要重启客户端会话
>
>
>
* 开启事务:
start transaction;
或者
begin;
* 提交事务:
commit;
* 回滚事务:
rollback;
### 1.2 Spring事务管理介绍
#### 1.2.1 编程式事务
* 什么是编程式事务?
编程式事务简单的来说就是采用编程的方式来管理事务,编程式事务需要将事务管理的代码写入到业务方法中,相对于核心业务而言,事务管理的代码显然**属于非核心业务**,**对核心业务代码的侵入性太大**;而且如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的**代码冗余**。
我们之前使用的JDBC API来操作事务就是编程式事务;
#### 1.2.2 声明式事务
* 什么是声明式事务?
声明式事务以声明的方式来实现事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
>
> 声明式事务简单的来说就是通过一些表达式声明(拦截)一堆需要被事务管理的方法,然后被声明(拦截)的方法通过AOP的方式进行事务管理(执行目标方法之前开启事务,执行目标方法之后提交或者回滚事务)
>
>
>
显然声明式事务管理要优于编程式事务管理:它将事务管理代码从业务方法中分离出来,这正是Spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受干扰;一个普通的对象,只要加上注解就可以获得完全的事务支持。
声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
#### 1.2.3 Spring事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring所有的事务管理策略类都继承自`org.springframework.transaction.PlatformTransactionManager`接口。
它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
* 事务管理器主要的实现:
+ `DataSourceTransactionManager`:使用Spring JDBC 或 Mybatis 等基于DataSource数据源的持久化技术时,使用 该事务管理器
+ `JpaTransactionManager`:使用JPA时采用的事务管理器
+ `JtaTransactionManager`:具有多个数据源的全局事务使用该事务管理器
+ `JdoTransactionManager`:使用JDO进行持久化时 ,使用该事务管理器
+ `HibernateTransactionManager`:使用Hibernate进行持久化时,使用该事务管理器
#### 1.2.4 事务传播行为
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为,在`org.springframework.transaction.TransactionDefinition`类中被定义。
| 静态常量 | 说明 |
| --- | --- |
| PROPAGATION\_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是Spring的**默认值**。 |
| PROPAGATION\_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
| PROPAGATION\_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
| PROPAGATION\_REQUIRES\_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
| PROPAGATION\_NOT\_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
| PROPAGATION\_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
| PROPAGATION\_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION\_REQUIRED类似的操作。 |
`TransactionDefinition`类中不仅定义了事务的传播行为,也定义了很多事务的隔离级别:
| 静态常量 | 说明 |
| --- | --- |
| ISOLATION\_DEFAULT | 使用数据库默认的隔离级别,Spring默认值 |
| ISOLATION\_READ\_UNCOMMITTED | 读未提交 |
| ISOLATION\_READ\_COMMITTED | 读已提交 |
| ISOLATION\_REPEATABLE\_READ | 可重复读 |
| ISOLATION\_SERIALIZABLE | 串行化 |
### 1.3 Spring实现声明式事务
#### 1.3.1 案例准备
##### 1)SQL脚本:
drop database if exists spring;
create database spring;
use spring;
– 创建数据表
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10),
money DOUBLE
);
– 添加数据
INSERT INTO account (name, money) VALUES (‘a’, 1000), (‘b’, 1000);
##### 2)Maven依赖:
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>com.dfbz</groupId>
<artifactId>01_Spring</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!--spring基本环境依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--spring对jdbc的支持(DataSourceTransaction)-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--spring对事物的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--spring对切面AspectsJ的支持-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--spring对junit的整合-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.18</version>
</dependency>
</dependencies>
##### 3)jdbc.properties:
jdbc.username=root
jdbc.password=admin
jdbc.url=jdbc:mysql:///spring
jdbc.driverClassName=com.mysql.jdbc.Driver
##### 4)实体类:
package com.dfbz.entity;
import lombok.Data;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Data
public class Account {
private Integer id;
private String name;
private Double money;
}
#### 1.3.2 事务通知XML配置
##### 1)Spring配置:
<?xml version="1.0" encoding="UTF-8"?>
<!--组件扫描-->
<context:component-scan base-package="com.dfbz" />
<!--加载配置文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--druid数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="driverClassName" value="${jdbc.driverClassName}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--引用数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
isolation:事务隔离级别
DEFAULT:使用数据库默认的隔离级别(默认值)
READ_UNCOMMITTED:读未提交
READ_COMMITTED:读已提交
REPEATABLE_READ:可重复读
SERIALIZABLE:串行化
propagation: 事务传播行为
REQUIRED:必须要有事务,如果已经存在事务,则加入到这个事务中,如果没有事务则创建一个新的事务
SUPPORTS:支持当前事务,如果调用这个方法之前没有事务则不会开启新的事务
MANDATORY:必须存在事务,如果调用此方法之前没有事务,就抛出异常
REQUIRES_NEW:新建事务,如果调用此方法之前就已经开启过事务,则将之前的事务挂起
NOT_SUPPORTED:非事务方式,如果调用此方法之前就存在事务,则挂起事务
NEVER:非事务方式运行,如果调用此方法之前存在事务,则抛出异常
NESTED:如果存在事务,则嵌套在事务内执行,如果没有事务则与REQUIRED保存一致
read-only: 是否只读
true: 只读
false:非只读(默认值)
timeout: 方法执行超时时间,默认值为-1,代表永不超时,单位为秒
rollback-for: 指定特定的异常才回滚(默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚),该配置针对于编译时异常;可以写多个,以逗号隔开
no-rollback-for: 指定一个特定的异常事务不回滚,可以写多个,以逗号隔开
–>
<!--出现ArithmeticException异常不回滚-->
<tx:method name="transfer" no-rollback-for="java.lang.ArithmeticException"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="myPoint" expression="execution(\* com.dfbz.service.AccountService.\*(..))"/>
<!--将通知应用到切入点上(织入)-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPoint"></aop:advisor>
</aop:config>
\*\*默认情况下,任何的运行时异常事务都会回滚,但编译时异常都不会进行回滚;\*\*我们可以通过一些配置来调整
* `no-rollback-for`:指定什么异常不回滚,可以写多个,以逗号隔开;
* `rollback-for`:该配置针对于编译时异常;可以写多个,以逗号隔开;
**上述两个参数在指定异常时,如果指定某个异常的父类,包括这个异常的所有子类异常都会回滚;**
**如:`no-rollback-for`指定的参数是Exception则代表Java中的任何异常都不回滚,**
**`rollback-for`指定的参数是`Exception`则Java中任何的异常都回滚;**
##### 2)业务类:
package com.dfbz.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author lscl
* @version 1.0
* @intro:
*/
@Service
public class AccountService {
@Autowired
private JdbcTemplate jdbcTemplate;
/\*\*
* @param str : 谁要转账
* @param target: 转账给谁
* @param flag: 模拟异常 true:出现异常 false:不出现异常
*/
public void transfer(String str, String target, Boolean flag) {
jdbcTemplate.update("update account set money=money-500 where name=?", str);
if (flag) {
// 模拟异常
int i = 1 / 0;
}
jdbcTemplate.update("update account set money=money+500 where name=?", target);
}
/\*\*
*
* @param id
*/
public void findById(Integer id) {
Account account = jdbcTemplate.queryForObject(
"select \* from account where id=?",
new BeanPropertyRowMapper<>(Account.class),
id
);
}
}
##### 3)测试类
package com.dfbz.test;
import com.dfbz.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;