我所理解的spring 事务

spring的事务管理是基于数据库事务实现的,首先得看下数据库事务是如何实现的(以mysql为例),首先建一张表user,其数据如下:

 id		|  name(varchar) |
--------|----------------|
 1		|	A1	 		 |
--------|----------------|
 2		|	A2	 		 |
--------|----------------|


在一个事务内执行以下sql语句:

insert into user(id,name)values(3,'A3');
update user set name = 'B1' where id=1;
delete from user where id=2;


下面大致根据我的理解分析下事务执行过程:

A,开启事务

B,insert的反操作信息进入undo_buf, //可以理解为相当于记录一条sql: delete from user where id=3;

C,insert 信息记录到redo_buf; //insert时会加锁,如果另一个事务中有相同的数据insert,另一事务会阻塞在此

D,根据主键id找到id=1的记录,记录信息到undo_buf; //可以理解为相当于一条sql: update user set name='A1' where id=1;

E,update信息记录到redo_buf; //update时会加锁,如果where条件是唯一键(uk非pk),会先加锁在uk上,然后加锁在pk上

F,根据id=2找到记录,记录信息到undo_buf, //可以理解为相当于一条sql:insert into user(id,name)values(2,'A2');

G,delete 信息进入redo_buf; //delete时也会加锁,如果where条件是唯一键(uk非pk),会先加锁在uk上,然后加锁在pk上

H,undo_buf写入磁盘文件undo_log

I,redo_buf写入磁盘文件redo_log

J,commit

然后再写入到数据文件,事务一旦提交后,就可以清除undo_log了

考虑几个异常场景:

1,H以及H之前的步骤如果发生异常,这时数据没有被写入文件.所以是干净的无需做任何处理

2,H在写入过程中宕机了,数据依然没有写入到文件,此时undo_buf写入的相当于是脏log,

3,I在写入过程中宕机了,可以通过undo_log进行恢复

4,J执行时宕机,通过redo_log可以保证数据的持久性

ok,以上是数据库事务简单的实现过程,回到我们的主题,首先想下,如果没有spring事务管理之前,程序员是如何处理事务的,如下的一个例子,在某个java类中:

Connection connection = null;
        Statement statement = null;
        try{
            connection = DriverManager.getConnection("xxxx");
            boolean autoCommit = connection.getAutoCommit();
            statement = connection.createStatement();
            doSomething();
            connection.commit();
        }catch (Exception e){
            connection.rollback();
        }finally {
            if(null != statement){
                statement.close();
            }
            if(null != connection){
                connection.close();
            }
        }
考虑几个问题:

1,若果有多个java类中多个业务场景都有事务操作,按照上面的方式都去写一次?一是频繁的创建以及关闭连接造成了大量的资源开销,二是这样写会造成大量重复的代码,维护不便,看上去也显得low。

2,针对上面的问题解决办法有,一是引入连接池,做到连接可以复用,可以大大节约资源,提升性能,二是抽取出他们的共性,放在一个类中进行统一管理.

3,doSomething()是我们的业务处理逻辑,却和一堆的数据库操作揉在一起,严重耦合了!

4,如何解决3的问题?这个问题与之前一篇文章"我所理解的spring aop"处理的问题类似,利用aop的思路就可以解决这个问题!

下面结合前面的几篇文章,一步步实现我们自己的事务管理(数据库连接池这里先不考虑),首先定义一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
    /**
     * int TRANSACTION_NONE             = 0;
     int TRANSACTION_READ_UNCOMMITTED = 1;
     int TRANSACTION_READ_COMMITTED   = 2;
     int TRANSACTION_REPEATABLE_READ  = 4;
     int TRANSACTION_SERIALIZABLE     = 8;
     * @return
     */
    int isolation() default 2;//READ_COMMITED
    String propagation() default "REQUIRED";
}


既然涉及到事务,不可避免的需要定义隔离级别以及传播属性,这里默认我们都采用比较常用的READ_COMMITED和REQUIRED,本文只考虑REQUIRED和REQUIRED_NEW两种传播属性.有了上面的注解,就需要对注解进行解析,执行必要的操作,我们重新添加一个BeanPostProcessor,用于处理bean中被注解Transactional标注的方法,创建bean对应的代理类对象:

@MyService
public class TransactionBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object processBeanAfterInstance(Object bean) {
        Set<Method> methodList = new HashSet<Method>();
        Class cls = bean.getClass();
        Class[] interfaces = cls.getInterfaces();
        if(null != interfaces){//jdk动态代理一定是实现了接口的
            for(Class inter: interfaces){
                Method[] methods = inter.getDeclaredMethods();
                for(Method method : methods){
                    try {
                        Method m = cls.getDeclaredMethod(method.getName());
                        if(m.isAnnotationPresent(Transactional.class)){
                            methodList.add(m);
                        }
                        //others
                    } catch (NoSuchMethodException e) {
                        //ignore
                    }
                }
            }
        }
        if(!methodList.isEmpty()){
            return createProxy(bean,methodList);
        }
        return bean;
    }
}
在createProxy中我们采用创建jdk动态代理的方式,在InvocationHandler中实现增强的方法.在实现创建代理类对象前,我们先思考几个问题:

1,存在多个被Transactional注解标注的方法之间存在调用关系时,事务何时被提交?

2,怎样判断本次事务已经结束(即何时释放资源)?

3,REQUIRED_NEW的传播属性该如何处理?

4,基于上面的问题,以下几种场景该如何处理才是正确的?



围绕这个上面几个问题,我们的处理方式如下:

1,建立一个ConnectionHolder,里面记录了connection被请求的的次数,初始值为0,当进入A.method1时这个方法时次数加1,此时值为1,调用B.method2时这个次数再加1,此时值为2。调用方法返回时再依次减1.当从ConnectionHolder中获取到的当前次数为1时,事务提交(spring中的实现是判断当前事务是否为新事务,如果为新事务就提交。针对上图中,进入A.method1,新开启了一个事务,进入B.method2时加入当前事务,此时不被认为是一个新事务,进入C.method3时又开启了一个新事务)。即A调用B已经完成,A.method1即将返回时事务提交。当前获取到的次数为0时,关闭连接释放资源.

2,如果是REQUIRED_NEW,此时需要新创建一个连接,事务独立于当前的事务。如上图C.method3,当C.method3返回时,要能够找到之前的连接以及事务上下文。为此我们建立一个TransactionManager,专门用于管理事务上下文TransactionContext,TransactionContext是一个链表,每当有被Transactional注解标注的方法加入当前事务或者新起一个事务时,加到这个上下文中,这个上下文包含了当前的连接,事务隔离级别以及传播属性是否自动提交等,对于上图,其事务上下文大致如下:


进入A.method1时创建新的上下文TransactionContext-1,进入B.method2时创建上下文TransactionContext-2,它的前一个结点指向TransactionContext-1,这个两个上下文中的connection是一样的,进入C.method3时,创建上下为TransactionContext-3,它的前一个结点指向TransactionContext-2,这样当方法调用返回时,依次将上下文的前一个结点设置为当前上下文.上面的核心代码如下:

Transactional transactional = m.getAnnotation(Transactional.class);
int currentIsolation = transactional.isolation();
String currentProgation = transactional.propagation();

TransactionManager.TransactionContext currentTransactionContext = getTransactionContext(currentIsolation,currentProgation);//获取一个上下文
Connection currentConnection = currentTransactionContext.getCurrentConnection();
if(null != ConnectionHolder.getConnectedCount(currentConnection)){//连接与connetioHolder绑定,用于计算次数
	ConnectionHolder.setConnection(currentConnection,ConnectionHolder.getConnectedCount(currentConnection));
}else{
	ConnectionHolder.setConnection(currentConnection);
}

try{
	ConnectionHolder.incRequest();//次数加1
	Object object = method.invoke(o,args);
	if(1 == ConnectionHolder.getConnectedCount(currentConnection))
		currentConnection.commit();
	return object;
}catch (Exception e){
	currentConnection.rollback();
	throw e;
}finally {
	cleanUpTransactionContext(currentTransactionContext);//清理事务上下文
}

private TransactionManager.TransactionContext getTransactionContext(int currentIsolation, String currentProgation) throws Exception {
	TransactionManager.TransactionContext transactionContext = TransactionManager.getTransactionContext();
	if(null == transactionContext){//当前不存在事务上下文
		Connection connection = ConnectionProvider.getConnection();
		connection.setTransactionIsolation(currentIsolation);
		connection.setAutoCommit(false);
		transactionContext = new TransactionManager.TransactionContext(connection,connection.getTransactionIsolation(),
				currentProgation,false);
		TransactionManager.setTransactionContext(transactionContext);
	}else{
		if(TransactionManager.PROAGTION_REQUIRED_NEW.equalsIgnoreCase(currentProgation)){
			//开辟一个新的连接
			Connection newConnnection = ConnectionProvider.getConnection();
			newConnnection.setTransactionIsolation(currentIsolation);
			newConnnection.setAutoCommit(false);
			TransactionManager.TransactionContext newTransactionContext = new TransactionManager.TransactionContext(newConnnection,
					newConnnection.getTransactionIsolation(),currentProgation,false);
			newTransactionContext.setPrevTransactionContext(transactionContext);//指向前一个上下文
			TransactionManager.setTransactionContext(newTransactionContext);
			transactionContext = newTransactionContext;
		}else{
			TransactionManager.TransactionContext context = new TransactionManager.TransactionContext(transactionContext.getCurrentConnection(),
					currentIsolation,currentProgation,false);
			context.setPrevTransactionContext(transactionContext);//指向前一个上下文
			context.getCurrentConnection().setAutoCommit(false);
			transactionContext = context;
		}
	}
	return transactionContext;
	}
	
private void cleanUpTransactionContext(TransactionManager.TransactionContext currentTransactionContext)throws Exception {
	ConnectionHolder.releaseRequest();//次数减1
	Connection currentConnection = currentTransactionContext.getCurrentConnection();
	if(TransactionManager.PROAGTION_REQUIRED_NEW.equalsIgnoreCase(currentTransactionContext.getPropagation())){//REQUIRED_NEW一定需要关闭当前连接
		currentConnection.close();
		ConnectionHolder.clear();
	}
	if(null != ConnectionHolder.getConnectedCount(currentConnection) && 0 == ConnectionHolder.getConnectedCount(currentConnection)){//如果连接次数为0,需要关闭
		if(!currentConnection.isClosed())
			currentConnection.close();
		ConnectionHolder.clear();
	}
	if(null != currentTransactionContext.getPrevTransactionContext()){//如果存在前一个结点,将前一个结点设置为当前事务上下文
		currentTransactionContext = currentTransactionContext.getPrevTransactionContext();
		currentConnection = currentTransactionContext.getCurrentConnection();
		currentConnection.setAutoCommit(currentTransactionContext.isAutoCommit());
		int count = ConnectionHolder.getConnectedCount(currentConnection);
		ConnectionHolder.setConnection(currentConnection,count);
		TransactionManager.setTransactionContext(currentTransactionContext);
	}else{
		//must be clear
		Connection conn = ConnectionHolder.getCurrentConnection();
		if(null != conn && !conn.isClosed()){
			conn.close();
		}
		ConnectionHolder.clear();
		TransactionManager.clear();
	}
}

以上的代码均已上传:https://github.com/reverence/mytransaction


参考文章:

http://blog.csdn.net/tangkund3218/article/details/47704527

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值