军装下的绕指柔19楼,暴风城长者,788111
面的几个章节已经分析了spring基于@aspectj
的源码,那么接下来我们分析一下aop的另一个重要功能,事物管理。
当数据库并发操作时,可能会引起脏读、不可重复读、幻读、第一类丢失更新、第二类更新丢失等现象。
对于以上问题,可以有多个解决方案,设置数据库事物隔离级别就是其中的一种,数据库事物隔离级别分为四个等级,通过一个表格描述其作用。
隔离级别 | 脏读 | 不可重复读 | 幻象读 |
---|---|---|---|
read uncommitted | 允许 | 允许 | 允许 |
read committed | 脏读 | 允许 | 允许 |
repeatable read | 不允许 | 不允许 | 允许 |
serializable | 不允许 | 不允许 | 不允许 |
public interface transactiondefinition { // 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中。 int propagation_required = 0; // 支持当前事物,如果当前没有事物,则以非事物方式执行。 int propagation_supports = 1; // 使用当前事物,如果当前没有事物,则抛出异常。 int propagation_mandatory = 2; // 新建事物,如果当前已经存在事物,则挂起当前事物。 int propagation_requires_new = 3; // 以非事物方式执行,如果当前存在事物,则挂起当前事物。 int propagation_not_supported = 4; // 以非事物方式执行,如果当前存在事物,则抛出异常。 int propagation_never = 5; // 如果当前存在事物,则在嵌套事物内执行;如果当前没有事物,则与propagation_required传播特性相同 int propagation_nested = 6; // 使用后端数据库默认的隔离级别。 int isolation_default = -1; // read_uncommitted 隔离级别 int isolation_read_uncommitted = connection.transaction_read_uncommitted; // read_committed 隔离级别 int isolation_read_committed = connection.transaction_read_committed; // repeatable_read 隔离级别 int isolation_repeatable_read = connection.transaction_repeatable_read; // serializable 隔离级别 int isolation_serializable = connection.transaction_serializable; // 默认超时时间 int timeout_default = -1; // 获取事物传播特性 int getpropagationbehavior(); // 获取事物隔离级别 int getisolationlevel(); // 获取事物超时时间 int gettimeout(); // 判断事物是否可读 boolean isreadonly(); // 获取事物名称 @nullable string getname(); }
传播特性名称 | 说明 |
---|---|
propagation_required | 如果当前没有事物,则新建一个事物;如果已经存在一个事物,则加入到这个事物中 |
propagation_supports | 支持当前事物,如果当前没有事物,则以非事物方式执行 |
propagation_mandatory | 使用当前事物,如果当前没有事物,则抛出异常 |
propagation_requires_new | 新建事物,如果当前已经存在事物,则挂起当前事物 |
propagation_not_supported | 以非事物方式执行,如果当前存在事物,则挂起当前事物 |
propagation_never | 以非事物方式执行,如果当前存在事物,则抛出异常 |
propagation_nested |
如果当前存在事物,则在嵌套事物内执行; 如果当前没有事物,则与propagation_required传播特性相同 |
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
mysql默认的事务隔离级别为 可重复读repeatable-read
3.platformtransactionmanager-->spring事务基础结构中的中心接口
public interface platformtransactionmanager { // 根据指定的传播行为,返回当前活动的事务或创建新事务。 transactionstatus gettransaction(@nullable transactiondefinition definition) throws transactionexception; // 就给定事务的状态提交给定事务。 void commit(transactionstatus status) throws transactionexception; // 执行给定事务的回滚。 void rollback(transactionstatus status) throws transactionexception; }
spring将事物管理委托给底层的持久化框架来完成,因此,spring为不同的持久化框架提供了不同的platformtransactionmanager接口实现。列举几个spring自带的事物管理器:
事物管理器 | 说明 |
---|---|
org.springframework.jdbc.datasource.datasourcetransactionmanager | 提供对单个javax.sql.datasource事务管理,用于spring jdbc抽象框架、ibatis或mybatis框架的事务管理 |
org.springframework.orm.jpa.jpatransactionmanager | 提供对单个javax.persistence.entitymanagerfactory事务支持,用于集成jpa实现框架时的事务管理 |
org.springframework.transaction.jta.jtatransactionmanager | 提供对分布式事务管理的支持,并将事务管理委托给java ee应用服务器事务管理器 |
public interface transactionstatus extends savepointmanager, flushable { // 返回当前事务是否为新事务(否则将参与到现有事务中,或者可能一开始就不在实际事务中运行) boolean isnewtransaction(); // 返回该事务是否在内部携带保存点,也就是说,已经创建为基于保存点的嵌套事务。 boolean hassavepoint(); // 设置事务仅回滚。 void setrollbackonly(); // 返回事务是否已标记为仅回滚 boolean isrollbackonly(); // 将会话刷新到数据存储区 @override void flush(); // 返回事物是否已经完成,无论提交或者回滚。 boolean iscompleted(); }
public interface savepointmanager { // 创建一个新的保存点。 object createsavepoint() throws transactionexception; // 回滚到给定的保存点。 // 注意:调用此方法回滚到给定的保存点之后,不会自动释放保存点, // 可以通过调用releasesavepoint方法释放保存点。 void rollbacktosavepoint(object savepoint) throws transactionexception; // 显式释放给定的保存点。(大多数事务管理器将在事务完成时自动释放保存点) void releasesavepoint(object savepoint) throws transactionexception; }
create table `account` ( `id` int(11) not null auto_increment comment '自增主键', `balance` int(11) default null comment '账户余额', primary key (`id`) ) engine=innodb auto_increment=5 default charset=utf8 comment='--账户表'
1 import org.apache.commons.dbcp.basicdatasource; 2 import org.springframework.dao.dataaccessexception; 3 import org.springframework.jdbc.core.jdbctemplate; 4 import org.springframework.jdbc.datasource.datasourcetransactionmanager; 5 import org.springframework.transaction.transactiondefinition; 6 import org.springframework.transaction.transactionstatus; 7 import org.springframework.transaction.support.defaulttransactiondefinition; 8 9 import javax.sql.datasource; 10 11 /** 12 * spring编程式事物 13 * @author: chenhao 14 * @create: 2019-10-08 11:41 15 */ 16 public class mytransaction { 17 18 private jdbctemplate jdbctemplate; 19 private datasourcetransactionmanager txmanager; 20 private defaulttransactiondefinition txdefinition; 21 private string insert_sql = "insert into account (balance) values ('100')"; 22 23 public void save() { 24 25 // 1、初始化jdbctemplate 26 datasource datasource = getdatasource(); 27 jdbctemplate = new jdbctemplate(datasource); 28 29 // 2、创建物管理器 30 txmanager = new datasourcetransactionmanager(); 31 txmanager.setdatasource(datasource); 32 33 // 3、定义事物属性 34 txdefinition = new defaulttransactiondefinition(); 35 txdefinition.setpropagationbehavior(transactiondefinition.propagation_required); 36 37 // 3、开启事物 38 transactionstatus txstatus = txmanager.gettransaction(txdefinition); 39 40 // 4、执行业务逻辑 41 try { 42 jdbctemplate.execute(insert_sql); 43 //int i = 1/0; 44 jdbctemplate.execute(insert_sql); 45 txmanager.commit(txstatus); 46 } catch (dataaccessexception e) { 47 txmanager.rollback(txstatus); 48 e.printstacktrace(); 49 } 50 51 } 52 53 public datasource getdatasource() { 54 basicdatasource datasource = new basicdatasource(); 55 datasource.setdriverclassname("com.mysql.jdbc.driver"); 56 datasource.seturl("jdbc:mysql://localhost:3306/my_test?usessl=false&useunicode=true&characterencoding=utf-8"); 57 datasource.setusername("root"); 58 datasource.setpassword("chenhao1991@"); 59 return datasource; 60 } 61 62 }
public class mytest { @test public void test1() { mytransaction mytransaction = new mytransaction(); mytransaction.save(); } }
运行测试类,在抛出异常之后手动回滚事物,所以数据库表中不会增加记录。
其底层建立在 aop 的基础之上,对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。通过声明式事物,无需在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。
import org.springframework.transaction.annotation.propagation; import org.springframework.transaction.annotation.transactional; /** * 账户接口 * @author: chenhao * @create: 2019-10-08 18:38 */ @transactional(propagation = propagation.required) public interface accountserviceimp { void save() throws runtimeexception; }
import org.springframework.jdbc.core.jdbctemplate; /** * 账户接口实现 * @author: chenhao * @create: 2019-10-08 18:39 */ public class accountserviceimpl implements accountserviceimp { private jdbctemplate jdbctemplate; private static string insert_sql = "insert into account(balance) values (100)"; @override public void save() throws runtimeexception { system.out.println("==开始执行sql"); jdbctemplate.update(insert_sql); system.out.println("==结束执行sql"); system.out.println("==准备抛出异常"); throw new runtimeexception("==手动抛出一个异常"); } public void setjdbctemplate(jdbctemplate jdbctemplate) { this.jdbctemplate = jdbctemplate; } }
<?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.xsd"> <!--开启tx注解--> <tx:annotation-driven transaction-manager="transactionmanager"/> <!--事物管理器--> <bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager"> <property name="datasource" ref="datasource"/> </bean> <!--数据源--> <bean id="datasource" class="org.apache.commons.dbcp.basicdatasource"> <property name="driverclassname" value="com.mysql.jdbc.driver"/> <property name="url" value="jdbc:mysql://localhost:3306/my_test?usessl=false&useunicode=true&characterencoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="chenhao1991@"/> </bean> <!--jdbctemplate--> <bean id="jdbctemplate" class="org.springframework.jdbc.core.jdbctemplate"> <property name="datasource" ref="datasource"/> </bean> <!--业务bean--> <bean id="accountservice" class="com.chenhao.aop.accountserviceimpl"> <property name="jdbctemplate" ref="jdbctemplate"/> </bean> </beans>
import org.junit.test; import org.springframework.context.applicationcontext; import org.springframework.context.support.classpathxmlapplicationcontext; /** * @author: chenhao * @create: 2019-10-08 18:45 */ public class mytest { @test public void test1() { // 基于tx标签的声明式事物 applicationcontext ctx = new classpathxmlapplicationcontext("aop.xml"); accountserviceimp studentservice = ctx.getbean("accountservice", accountserviceimp.class); studentservice.save(); } }
==开始执行sql ==结束执行sql ==准备抛出异常 java.lang.runtimeexception: ==手动抛出一个异常 at com.lyc.cn.v2.day09.accountserviceimpl.save(accountserviceimpl.java:24) at sun.reflect.nativemethodaccessorimpl.invoke0(native method) at sun.reflect.nativemethodaccessorimpl.invoke(nativemethodaccessorimpl.java:62) at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:43) at java.lang.reflect.method.invoke(method.java:498)
测试方法中手动抛出了一个异常,spring会自动回滚事物,查看数据库可以看到并没有新增记录。
注意:默认情况下spring中的事务处理只对runtimeexception方法进行回滚,所以,如果此处将runtimeexception替换成普通的exception不会产生回滚效果。
接下来我们就分析基于@transactional注解的声明式事物的的源码实现。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
浅析我对 String、StringBuilder、StringBuffer 的理解
使用IDEA搭建SSM框架的详细教程(spring + springMVC +MyBatis)
Springboot整合freemarker 404问题解决方案
引入mybatis-plus报 Invalid bound statement错误问题的解决方法
网友评论