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