前置阅读
https://blog.csdn.net/define_us/article/details/79738918
https://blog.csdn.net/define_us/article/details/80284576
java中的事务
因为现代架构由CS向BS架构演进,可信边界前移,所以,直接使用数据库锁机制的地方减少了。
当然,你仍然可以通过sql来使用数据库自己的锁机制
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
如果选择sesion参数,则本次会话所开启的事物全部使用该事务隔离级别。
如果使用global参数,则本次会话和所有新连接的会话都会使用该事务隔离级别
如果不使用任何参数,则只有本会话下一次开启的事物使用该隔离级别。
JAVA/Spring事务/ 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。
Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。
使用 JDBC 事务界定时,您可以将多个 SQL 语句结合到一个事务中。JDBC 事务的一个缺点是事务的范围局限于一个数据库连接。一个 JDBC 事务不能跨越多个数据库。
JTA是一种高层的,与实现无关的,与协议无关的API,应用程序和应用服务器可以使用JTA来访问事务。JTA允许应用程序执行分布式事务处理——在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。
Spring 同时支持编程式事务和声明式事务。编程式事务需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法。Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。事务增强也是AOP的一大用武之处。
JDBC则定义五种事物隔离级别:
- RANSACTION_NONE JDBC: 驱动不支持事务
- TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读,丢失更新。
- TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读,丢失更新。
- TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,但允许幻读,丢失更新。
- TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读,丢失更新
Spring事务的传播属性
- PROPAGATION_REQUIRED 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。
- PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作
- PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY 支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED 某一个事务存在,则运行在一个嵌套的事务中
下面解释一下事务挂起
方法A支持事务,方法B不支持事务。 即PROPAGATION_NOT_SUPPORTED
方法A调用方法B。
在方法A开始运行时,系统为它建立Transaction,方法A中对于数据库的处理操作,会在该Transaction的控制之下。
这时,方法A调用方法B,方法A打开的 Transaction将挂起,方法B中任何数据库操作,都不在该Transaction的管理之下。
当方法B返回,方法A继续运行,之前的Transaction回复,后面的数据库操作继续在该Transaction的控制之下提交或回滚。
Spring中的隔离级别
其实隔离级别是SQL标准规范中的语义
- ISOLATION_DEFAULT 这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应。
- ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
- ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
- ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
- ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
我们都知道,通过如下SQL可以确定数据库目前事务的隔离级别
show variables like 'tx_isolation'; # 查看隔离级别,MySQL8以前
show variables like 'transaction_isolation'; # 查看隔离级别,MySQL8
set global transaction_isolation='READ-COMMITTED'; // 设置隔离级别,阀域READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE
但是这个是数据层的。实际上,可以对不通session调整不同的级别
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;
所以,spring中的隔离级别本质上要依赖数据源的支持。比如说目前据说很厉害的OceanBase只支持READ-COMMITTED的隔离借呗。。
基于声明的事务管理
SPRING是基于声明的事务管理。Spring事务可以操作hibernate,也可以直接操作JDBC。事实上,我们一般就是再操作数据时才使用事务的概念。
Spring有专门的的事务管理器类。
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。但是推荐只用在public方法上。
该注解可以配置如下属性:
- value String 可选的限定描述符,指定使用的事务管理器
- propagation enum: Propagation 可选的事务传播行为设置
- isolation enum: Isolation 可选的事务隔离级别设置
- readOnly boolean 读写或只读事务,默认读写
- timeout int (in seconds granularity) 事务超时时间设置
- rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
- rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
- noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
- noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组
第一步,配置容器
<?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:context="http://www.springframework.org/schema/context"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<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/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="accountDao" class="com.ys.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountService" class="com.ys.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
<!-- 创建模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"></property>
</bean>
<!-- 配置事务管理器 ,管理器需要事务,事务从Connection获得,连接从连接池DataSource获得 -->
<!-- 如果要和mybaits整合,那么应该是一致的 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>