Spring事务说明与自实现
要使用Springboot的事务其实非常简单,在启动类上添加@EnableTransactionManagement,在Service的类或者方法上使用@Transactional就可以了。
事务本身的4大特性
原子性(Atomicity) 指事务必须是一个不可分割的整体
一致性(Consistency) 指执行完数据库操作后,数据不会被破坏
隔离性(Isolation) 保证数据库操作之间,彼此没有任何干扰
持久性(Durability) 保证永久的存放在磁盘中
其中隔离性又分四个级别,它们依次向下,级别越来越高,并发性越来越差,安全性越来越高
READ_UNCOMMITTED 允许存在脏读(事务A读取了事务B未提交的数据,并在这个基础上又做了其他操作)
READ_COMMITTED 不允许脏读,允许不可重复读(事务A读取了事务B已提交的更改数据)
REPEATABLE_READ 不允许脏读,不可重复读,允许幻读(事务A读取了事务B已提交的新增数据)
SERIALIZABLE 全部不允许,做到完全隔离
而Spring是以7种事务传播行为来区别的,假设事务从方法A传播到方法B,用户需要面对方法B,需要知道方法A有事务吗?
PROPAGATION_REQUIRED 如果没有,就新建一个事务;如果有,就加入当前事务。是Spring默认的事务传播行为,适合绝大多数情况。
PROPAGATION_REQUIRES_NEW 如果没有,就新建一个事务;如果有,就将当前事务挂起,意思就是创建了一个新事务,它和原来的事务没有任何关系。
PROPAGATION_NESTED 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务,也就是“嵌套事务”,所嵌套的子事务与主事务之间是有关联关系的(当主事务提交或回滚,子事务也会提交或回滚)。
PROPAGATION_SUPPORTS 如果没有,就以非事务方式执行;如果有,就使用当前事务。这种方式非常随意,没有就没有,有就有,有点无所谓的态度,反正是支持的。
PROPAGATION_NOT_SUPPORTED 如果没有,就以非事务方式执行;如果有,就将当前事务挂起,这种方式非常强硬,没有就没有,有也不支持,挂起,不管。
PROPAGATION_NEVER 如果没有,就以非事务方式执行;如果有,就抛出异常。这种方式更强硬,没有就没有,有了反而报错,从不支持事务。
PROPAGATION_MANDATORY 如果没有,就抛出异常;如果有,就使用当前事务。这种方式可以说是最强硬的,没有事务就直接报错,必须要有事务。
具体配置为
@Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED)
其中isolation和propagation取值都是枚举。
现在我们来自己实现一个事务管理特性,代码承接于 AOP原理与自实现
首先在pom中增加JDBC的引用,根据你数据库版本的不同而不同,我这里是针对mysql 8的。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
定义事务注解
package com.guanjian.annotion;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义需要事务控制的方法
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transacion {
}
实现一个线程隔离类
package com.guanjian.proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 线程隔离
*/
public class ThreadLocal<T> {
private Map<Thread,T> container = new ConcurrentHashMap<>();
public void set(T value) {
container.put(Thread.currentThread(),value);
}
public T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if (value == null && !container.containsKey(thread)) {
value = initialValue();
container.put(thread,value);
}
return value;
}
public void remove() {
container.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
数据库操作助手类
package com.guanjian.util;
import com.guanjian.proxy.ThreadLocal;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 数据库操作助手类
*/
public class DatabaseHelper {
private static final String driver = "com.mysql.cj.jdbc.Driver";
private static final String url = "jdbc:mysql://192.168.1.102:3306/cloud_user?useSSL=FALSE&serverTimezone=GMT%2B8";
private static final String username = "root";
private static final String password = "root";
//用于放置数据库连接的局部线程变量(使每个线程都拥有自己的连接),用于线程隔离,生成环境请使用数据库连接池
private static ThreadLocal<Connection> CONNECTION_HOLDER = new ThreadLocal<>();
/**
* 开启事务
*/
public static void beginTransaction() {
Connection conn = getConnection();
if (conn != null) {
try {
conn.setAutoCommit(false);
} catch (SQLException e) {
System.out.println("Begin transaction failure " + e);
throw new RuntimeException(e);
}finally {
CONNECTION_HOLDER.set(conn);
}
}
}
/**
* 提交事务
*/
public static void commitTransaction() {
Connection conn = getConnection();
if (conn != null) {
try {
conn.commit();
conn.close();
} catch (SQLException e) {
System.out.println("commit transaction failure " + e);
throw new RuntimeException(e)
} finally {
CONNECTION_HOLDER.remove();
}
}
}
/**
* 回滚事务
*/
public static void rollbackTransaction() {
Connection conn = getConnection();
if (conn != null) {
try {
conn.rollback();
conn.close();
} catch (SQLException e) {
System.out.println("rollback transaction failure" + e);
throw new RuntimeException(e);
} finally {
CONNECTION_HOLDER.remove();
}
}
}
private static Connection getConnection(){
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url,username,password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
}
使用事务代理类对标记有@Transaction的方法拦截,进行代理增强
package com.guanjian.proxy;
import com.guanjian.annotion.Transacion;
import com.guanjian.util.DatabaseHelper;
import java.lang.reflect.Method;
/**
* 事务代理
*/
public class TransactionProxy implements Proxy {
//线程事务控制标志,保证同一个线程中事务控制逻辑只会执行一次
private static final ThreadLocal<Boolean> FLAG_HOLDER = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return false;
}
};
@Override
public Object doProxy(ProxyChain proxyChain) throws Throwable {
Object result;
boolean flag = FLAG_HOLDER.get(); //默认false
Method method = proxyChain.getTargetMethod();
//检查方法是否带有@Transaction注解且是同一个线程执行的,进行代理增强
if (!flag && method.isAnnotationPresent(Transacion.class)) {
FLAG_HOLDER.set(true);
try {
DatabaseHelper.beginTransaction();
System.out.println("begin transaction");
//跟代理链双向递归
result = proxyChain.doProxyChain();
DatabaseHelper.commitTransaction();
System.out.println("commit transaction");
}catch (Exception e) {
DatabaseHelper.rollbackTransaction(www.hengtongyoule.com);
System.out.println("rollback transaction");
throw e;
}finally {
//移除该线程
FLAG_HOLDER.remove();
}
}else { //如果没有注解,则只执行被代理类实例本方法
result = proxyChain.doProxyChain();
}
return result;
}
}
在AOPHelper中进行修改
/**
* 创建所有的AOP类,事务类与与之对应的目标类集合的映射
* @return
* @throws Exception
*/
private static Map<Class<?>,Set<Class<?>>> createProxyMap() throws Exception {
Map<Class<?>,Set<Class<?>>> proxyMap = new HashMap<>();
addAspectProxy(proxyMap);
addTransactionProxy(proxyMap);
return proxyMap;
}
/**
* 增加切面代理
* @param proxyMap
* @throws Exception
*http://dasheng178.com/#portal/list.html
private static void addAspectProxy(Map<Class<?>,Set<Class<?>>> proxyMap) throws Exception {
//获取切面代理类(抽象类)的所有实现类(子类)
Set<Class<?>> proxyClassSet = ClassHelper.getClassSetBySuper(AspectProxy.class);
for (Class<?> proxyClass:proxyClassSet) {
//实现类是否有@Aspect标签
if (proxyClass.isAnnotationPresent(Aspect.class)) {
//获取该标签
Aspect aspect = proxyClass.getAnnotation(Aspect.class);
//获取所有目标类集合
Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
//将代理类实例与该集合添加map映射
proxyMap.put(proxyClass,targetClassSet);
}
}
}
/**
* 增加事务代理
* @param proxyMap
*/
private static void addTransactionProxy(www.meiwanyule.cn Map<www.yongshi123.cn Class<?>,Set<Class<?>>> proxyMap) {
//以@Componet标签为开启事务代理的类标签
Set<Class<?>> componentClassSet www.xintiandiyule1.com/= ClassHelper.www.yongshiyule178.comgetClassSetByAnnotation(Component.class);
proxyMap.put(TransactionProxy.www.sanxinyuLevip.com class,componentClassSet);
}
测试
@Component
public class Test4 {
@Transacion
public void show(www.michenggw.com) {
System.out.println("aaa");
}
}
public class Test {
public static void main(String[www.baishenyvip.com] args) {
//扫描包
Manager.scanAndImp("com.guanjian.test");
//初始化AopHelper
ClassUtil.loadClass(AopHelper.class.getName(),true);
//这里其实拿到的是代理类的实例,代理类是目标类的子类
Test4 test4 = (www.sanxingyLzc.com Test4)Manager.getBean(www.suoLaieyuLe.com Test4.class);
test4.show();
}
}
运行结果
aop Class loaded
begin transaction
aaa
commit transaction
Spring事务说明与自实现的更多相关文章
- spring事务概念理解
1.数据并发问题 脏读 A事务读取B事务尚未提交的更新数据,并在此数据的基础上操作.如果B事务回滚,则A事务读取的数据就是错误的.即读取了脏数据或者错误数据. 不可重复组 A事务先后读取了B事务提交[ ...
- 【Java EE 学习 52】【Spring学习第四天】【Spring与JDBC】【JdbcTemplate创建的三种方式】【Spring事务管理】【事务中使用dbutils则回滚失败!!!??】
一.JDBC编程特点 静态代码+动态变量=JDBC编程. 静态代码:比如所有的数据库连接池 都实现了DataSource接口,都实现了Connection接口. 动态变量:用户名.密码.连接的数据库. ...
- Spring事务
1.@Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.@Transactional 的 ...
- spring事务管理器设计思想(二)
上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...
- spring事务管理器设计思想(一)
在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...
- Spring事务管理的三种方式
一 .第一种:全注解声明式事务 Xml代码 复制代码 收藏代码 .<?xml version="1.0" encoding="UTF-8"?> .& ...
- spring 事务传播特性 和隔离级别
事务的几种传播特性1. PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务.如果没有事务则开启2. PROPAGATION_SUPPORTS: 如果存在一个事务,支持当前事务 ...
- Spring事务管理
Spring是SSH中的管理员,负责管理其它框架,协调各个部分的工作.今天一起学习一下Spring的事务管理.Spring的事务管理分为声明式跟编程式.声明式就是在Spring的配置文件中进行相关配置 ...
- Spring事务传播属性
Spring 对事务控制的支持统一在 TransactionDefinition 类中描述,该类有以下几个重要的接口方法: int getPropagationBehavior():事务的传播行为 i ...
- Spring事务属性的介绍
Spring声明式事务让我们从复杂的事务处理中得到解脱.使得我们再也无需要去处理获得连接.关闭连接.事务提交和回滚等这些操作.再也无需要我们在与事务相关的方法中处理大量的try-catch-final ...
随机推荐
- ASP.NET Core读取AppSettings (转载)
今天在把之前一个ASP.NET MVC5的Demo项目重写成ASP.NET Core,发现原先我们一直用的ConfigurationManager.AppSettings[]读取Web.config中 ...
- 七年一冠、IG牛13的背后是什么!
最近忙着看S8世界总决赛,博客荒废了近一个月,后续步入正轨. 2018年11月3日.S8世界总决赛.中国终于夺得了S系列赛的总冠军. “IG牛逼”也开始刷爆社交圈,对于在S3入坑的我来说,也弥补上 ...
- item 7:当创建对象的时候,区分()和{}的使用
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误.谢谢! 博客已经迁移到这里啦 从不同的角度来看,在C++11中,对象初始化拥有多种语法选择,这体 ...
- Centos下Nodejs+npm环境-部署记录
公司的一个项目上线,需要用到Nodejs和npm环境,这里记录下安装过程,方便回看同时供大家参考. 1)yum安装方式(版本比较老点,v5.12.0 + 3.8.6) 需要在centos中添加epel ...
- Haproxy和Nginx负载均衡测试效果对比记录
为了对比Hproxy和Nginx负载均衡的效果,分别在测试机上(以下实验都是在单机上测试的,即负载机器和后端机器都在一台机器上)做了这两个负载均衡环境,并各自抓包分析.下面说下这两种负载均衡环境下抓包 ...
- 软件工程M1/M2总结
也不分M1/M2了,就从头到尾的梳理一下这学期的软工课吧. 第一节课,老师就稀里哗啦说了一下这学期要怎么搞,什么个人项目啦,结对项目啦,团队项目一二啦,还要组队啊什么的,然后风风火火的组队. 个人项目 ...
- spring中通过JNDI、DBCP、C3P0配置数据源
JNDI配置数据源 1.首先在tomcat的server.xml中配置数据源信息,找到Context,然后在里边加入如下代码 <Context docBase="SpringDemo& ...
- 初学Java必写的小程序。
1.矩形面积,周长封装测试. /** * @author Administrator *封装好的矩形类 *自己私有的长宽属性 *开放 求面积求周长的方法 和设置长宽的方法 */ public clas ...
- Neo4j学习案例【转】
转自 打怪的蚂蚁 CSDN: https://blog.csdn.net/xgjianstart/article/details/77285334 neo4j有社区版本和企业版.社区版本是免费的,只支 ...
- Activiti For Eclipse(Mars)插件配置
Activiti BPMN 2.0 designer : http://www.activiti.org/designer/update/