spring–基础–08–spring事务
代码地址
https://gitee.com/DanShenGuiZu/learnDemo.git
1、ACID
- 原子性:事务操作要么是成功,要么是失败的。
- 一致性:数据一致
- 隔离性:事务之间,数据是隔离的
- 持久性:保存到硬盘中
2、五大属性
- 隔离级别
- 传播行为
- 是否只读
- 事务超时
- 回滚规则
2.1、隔离级别
- 指若干个并发的事务之间的隔离程度。
- TransactionDefinition:定义了5个表示隔离级别:
- ISOLATION_DEFAULT:
- 这是默认值,表示使用底层数据库的默认隔离级别。
- 对大部分数据库而言,通常这值就是ISOLATION_READ_COMMITTED。
- ISOLATION_READ_UNCOMMITTED:
- 该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。
- 该级别不能防止脏读,不可重复读和幻读
- 很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
- ISOLATION_READ_COMMITTED:
- 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。
- 该级别可以防止脏读,这也是大多数情况下的推荐值。
- ISOLATION_REPEATABLE_READ:
- 该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。
- 该级别可以防止脏读和不可重复读。
- ISOLATION_SERIALIZABLE:
- 所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
- 该级别这将严重影响程序的性能。
- 通常情况下不会使用
- ISOLATION_DEFAULT:
2.2、事务传播行为
- 指如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
- 在TransactionDefinition:定义中5传播行为
- PROPAGATION_REQUIRED:
- 如果当前存在事务,则加入该事务;
- 如果当前没有事务,则创建一个新的事务。
- 这是默认值。
- PROPAGATION_REQUIRES_NEW:
- 创建一个新的事务,
- 如果当前存在事务,则把当前事务挂起。
- PROPAGATION_SUPPORTS:
- 如果当前存在事务,则加入该事务;
- 如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_NOT_SUPPORTED:
- 以非事务方式运行,
- 如果当前存在事务,则把当前事务挂起。
- PROPAGATION_NEVER:
- 以非事务方式运行,
- 如果当前存在事务,则抛出异常。
- PROPAGATION_MANDATORY:
- 如果当前存在事务,则加入该事务;
- 如果当前没有事务,则抛出异常。
- PROPAGATION_NESTED:
- 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
- 如果当前没有事务,则创建一个新的事务。
- PROPAGATION_REQUIRED:
2.3、事务超时
- 指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。其单位是秒。
- 默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
2.4、是否只读
- 只读事务并不是一个强制选项,它只是一个暗示,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
- 你非要在只读事务里面修改数据,也并非不可以,只不过对于数据一致性的保护不像读写事务那样保险而已。
- 只读事务仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可
2.5、回滚规则
- spring事务管理器回滚一个事务的推荐方法:
- 在当前事务的上下文内抛出异常。
- spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚。
- 默认配置下
- spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),
- 抛出checked异常则不会导致事务回滚。
- 可以明确的配置在抛出那些异常时回滚事务,包括checked异常。
- 可以明确定义那些异常抛出时不回滚事务。
- 可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
2.5.1、回滚规则测试
@Override
public void insert2(){
Integer age = 12;
String name = "xiao6";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
if(1 == 1){
// 抛异常,事务由回滚
int i = 4 / 0;
}
}
@Override
public void insert3()throws Exception {
try {
Integer age = 12;
String name = "xiao7";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
int i = 4 / 0;
} catch(Exception e){
// 抛出异常,事务没有回滚
throw new Exception("人为产生异常");// 受检异常(非运行异常)
}
}
@Override
public void insert4(){
try {
Integer age = 12;
String name = "xiao8";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
int i = 4 / 0;
} catch(Exception e){
// 异常抛出,事务回滚
throw new RuntimeException("人为产生异常");// 运行异常(非受检异常)可以不抛出
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void insert5()throws Exception {
try {
Integer age = 12;
String name = "xiao9";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
int i = 4 / 0;
} catch(Exception e){
// 抛出异常,事务回滚
throw new Exception("人为产生异常");// 受检异常(非运行异常)
}
}
3、@Transactional属性和用法
3.1、属性
- value:指定使用的事务管理器
- propagation:事务传播行为
- isolation:事务隔离级别
- readOnly:读写或只读事务,默认读写
- timeout:事务超时时间设置(秒)
- rollbackFor:class对象数组,必须继承自Throwable,导致事务回滚的异常类数组
- rollbackForClassName:类名数组,必须继承自Throwable,导致事务回滚的异常类名字数组
- noRollbackFor:Class对象数组,必须继承自Throwable,不会导致事务回滚的异常类数组
- noRollbackForClassName:类名数组,必须继承自Throwable,不会导致事务回滚的异常类名字数组
3.2、用法
-
可以作用于接口、接口方法、类以及类方法上。
-
当作用于类上时
- 该类的所有public方法将都具有该类型的事务属性。
- 我们也可以在方法级别使用该标注来覆盖类级别的定义。
-
建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
-
应该只被应用到public方法上
- 这是由SpringAOP的本质决定的。
- 如果你在protected、private或者默认可见性的方法上使用@Transactional注解,这将被忽略,也不会抛出任何异常。
-
默认情况下
- 只有来自外部的方法调用才会被AOP代理捕获。
- 类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
-
注解不能被继承
- 在业务接口中标注的@Transactional注解不会被业务实现类继承,会出现不启动事务的情况。
- Spring建议我们将@Transaction注解在实现类上。
- 方法上的@Transactional注解会覆盖掉类上的@Transactional。
4、编程式和声明式事务
- 编程式事务管理:
- 代码控制事务,极大的灵活性,但却很难维护。
- 使用TransactionTemplate接口(推荐)
- 使用底层的PlatformTransactionManager接口
- 声明式事务管理:
- 业务代码中分离事务管理。通过注解释或XML配置来管理事务。
- 建立在AOP之上的。
- 本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
- 优点
- 不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
- 非侵入式的开发方式。
- 缺点
- 最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
4.1、编程式和声明式事务测试
4.1.1、公共部分
CREATE TABLE Student(
ID int(11)NOT NULL AUTO_INCREMENT,
NAME varchar(20)DEFAULT NULL,
AGE int(11)DEFAULT NULL,
PRIMARY KEY(ID)
)ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8;
4.1.2、编程式
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
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-3.0.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://zhoufei.ali.db.com:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- Initialization for TransactionManager -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- Definition for studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate"
class="com.example.demolearn.other.spring.demo6.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource"/>
<property name="transactionManager" ref="transactionManager"/>
</bean>
</beans>
public interface StudentDAO {
void insert();
}
public class StudentJDBCTemplate implements StudentDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
private PlatformTransactionManager transactionManager;
// 设置数据源
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
public PlatformTransactionManager getTransactionManager(){
return transactionManager;
}
public void setTransactionManager(PlatformTransactionManager transactionManager){
this.transactionManager = transactionManager;
}
@Override
public void insert(){
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
Integer age = 12;
String name = "xiao4";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
String SQL2 = "update Student set age = ? where id = ?";
jdbcTemplateObject.update(SQL2, 21, 25);
transactionManager.commit(status);
} catch(DataAccessException e){
System.out.println("异常,事务回滚");
transactionManager.rollback(status);
throw e;
}
}
}
public class JDCB1 {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("./other/JDBC2.xml");
StudentDAO dao =(StudentJDBCTemplate)context.getBean("studentJDBCTemplate");
dao.insert();
}
}
4.1.3、声明式01
<?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-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!--数据源-->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://zhoufei.ali.db.com:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</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>
<tx:method name="insert"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="createOperation"
expression="execution(* com.example.demolearn.other.spring.demo7.StudentJDBCTemplate.insert(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
</aop:config>
<!-- Definition for studentJDBCTemplate bean -->
<bean id="studentJDBCTemplate"
class="com.example.demolearn.other.spring.demo7.StudentJDBCTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
public interface StudentDAO {
void insert();
}
public class StudentJDBCTemplate implements StudentDAO {
private DataSource dataSource;
private JdbcTemplate jdbcTemplateObject;
// 设置数据源
public void setDataSource(DataSource dataSource){
this.dataSource = dataSource;
this.jdbcTemplateObject = new JdbcTemplate(dataSource);
}
@Override
public void insert(){
Integer age = 12;
String name = "xiao5";
String SQL = "insert into Student(name, age)values(?, ?)";
jdbcTemplateObject.update(SQL, name, age);
if(1 == 1){
throw new RuntimeException("异常");
}
String SQL2 = "update Student set age = ? where id = ?";
jdbcTemplateObject.update(SQL2, 21, 25);
}
}
public class JDCB1 {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("./other/JDBC3.xml");
StudentDAO dao =(StudentDAO)context.getBean("studentJDBCTemplate");
dao.insert();
}
}
4.1.4、声明式2
5、接口
5.1、PlatformTransactionManager接口
5.2、TransactionDefinition 接口
public interface TransactionDefinition {
int getPropagationBehavior();
int getIsolationLevel();
String getName();
int getTimeout();
boolean isReadOnly();
}
5.3、TransactionStatus 接口
public interface TransactionStatus extends SavepointManager {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
}
6、获取当前Spring事务
6.1、步骤
- 获取当前线程绑定的事务上下文
- 判断是否存在事务上下文
- 获取当前事务
6.2、代码和测试
// 步骤1:获取当前线程绑定的事务上下文
String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
System.out.println("当前事务上下文名称:" + transactionName);
// 步骤2:判断是否存在事务上下文,如果当前线程没有绑定事务上下文,那么说明当前没有正在执行的事务。
boolean hasTransactionContext = TransactionSynchronizationManager.isActualTransactionActive();
System.out.println("是否存在事务上下文:" + hasTransactionContext);
// 步骤3:获取当前事务,TransactionStatus对象包含了事务的各种状态和属性。
TransactionStatus transactionStatus = TransactionAspectSupport.currentTransactionStatus();
System.out.println("当前事务状态:" + transactionStatus);