事务,一直是一个比较严肃的问题。我们都知道事务几个要素
一致性,原子性,隔离性,持久性。
也都知道事务几个隔离级别
Read uncommitted,Read committed,Repeatable read,Serializable
上面几点,相关的文章很多,这些也不是我在讨论的重点,本文主要描述一下个人在接触J2EE开发过程中,对事务管理认识的历程。 ###一、无事务意识### 在我刚接触代码开发的时候,只知道Connection
这个类,数据库操作的步骤如下:<br/>
-
获得一个
Connection
对象<br/> -
得到一个
Statement
对象<br/> -
设置执行SQL以及参数<br/>
-
执行设置的SQL语句<br/>
-
调用
Connection.close
方法<br/><!-- lang:java-->
public void origionTransaction() throws SQLException, ClassNotFoundException{ Class.forName("driveName"); Connection connection = DriverManager.getConnection("jdbcurl"); Statement st = connection.createStatement(); try{ st.execute("executesql1"); st.execute("executesql2"); }finally{ if(st!=null){ st.close(); } if(connection!=null){ connection.close(); } }
}
上面这段代码,在任何一个j2ee入门的视频或者书籍里面都会是这样来第一次讲解jdbc操作数据库。第一次看到这个,看不到任何事务的概念在里面,我们只是知道获取了连接,并且执行了一段SQL语句,然后关闭连接。这里面貌似所有都是连接在做事情,而看不到事务的踪影。我想大家现在应该知道,其实事务这个时候是一直存在的,它其实一直藏在Connection
后面。这里没执行一段SQL语句,都会执行一个事务的提交,而我们却感知不到,这就是所谓的无事务时代。 ###二、第一次知道事务### 随着,开发经验的增多,我们知道了Connection
有一个setAutoCommit
方法,在默认情况下autoCommit是true,及没执行一个sql都会提交一个事务,这就导致了上面“无事务”的状态。那么我们是不是调用setAutoCommit
为false的时候,是不是就感知到了事务的存在?为了达到感知事务的存在,我们需要进行下面几个步骤:
-
获得一个
Connection
对象<br/> -
调用
setAutoCommit(false)
,设置不自动提交事务<br/> -
得到一个
Statement
对象<br/> -
设置执行SQL以及参数<br/>
-
执行设置的SQL语句<br/>
-
调用
Connection
对象的commit()
方法进行提交事务<br/> -
调用
Connection.close
方法<br/><!--lang:java-->
public void knowTransaction() throws SQLException, ClassNotFoundException{ Class.forName("driveName"); Connection connection = DriverManager.getConnection("jdbcurl"); connection.setAutoCommit(false); Statement st = connection.createStatement(); try{ st.execute("executesql1"); st.execute("executesql2"); connection.commit(); }catch(Exception e){ connection.rollback(); }finally{ if(st!=null){ st.close(); } if(connection!=null){ connection.close(); } }
}
上面则可以使得执行的executesql1
和executesql2
在一个事务里面,并且同时提交到数据库。而且这里面第一次感觉到了事务提交的存在,因为是通过显示的commit
操作来进行提交。发展到这里,我们知道了一个方法里面的所有数据库操作可以在一个事务里面完成。但是,当有需求需要将不同方法都必须在一个事务里面完成,这个方法就可以能无法满足。于是就演变出了下一个时代。
###三、出现DAO层### 经过开发经验的累积,当将数据库访问的代码全部杂糅在业务处理层,或者更上的层次。会显得代码臃肿、灵活性差、重用性低,可能需要调整同一个业务的SQL,需要到各个类里面去调整执行的SQL,一条相同的SQL会出现在很多类中,那么这会导致后期对代码的维护非常艰难。通过分析,得知,数据库操作是否在一个事务里面,是业务层决定的,是某个业务场景决定的,并不是某条SQL语句,或者几条SQL语句决定的。于是干脆抽离出一层只能来访问数据库,将对某个实体表操作的SQL抽离待一个对这个表操作的DAO类中,只要对这个实体表操作,都通过对应的DAO类来进行。那么DAO类中的方法有可能是一个单独的事务,也有可能和其他的方法在一个事务里面。如何保证不同方法都在一个事务里面?需要进行如下步骤:
-
获得一个
Connection
对象<br/> -
调用
setAutoCommit(false)
,设置不自动提交事务<br/> -
调用DAO的方法(这里可以是一个DAO类的不同方法,也可以是不同DAO的不同方法),并且将Connection传递到Dao方法中执行数据库操作<br>
-
调用
Connection
对象的commit()
方法进行提交事务<br/> -
调用
Connection.close
方法<br/><!--lang:java-->
public void multMethodTransaction() throws ClassNotFoundException, SQLException{ Class.forName("driveName"); Connection connection = DriverManager.getConnection("jdbcurl"); connection.setAutoCommit(false); try{ Dao1 dao1 = new Dao1(); dao1.executeSQL(connection); Dao2 dao2 = new Dao2(); dao2.executeSQL(connection); connection.commit(); }catch(Exception e){ connection.rollback(); }finally{ connection.close(); } connection.close();
}
上面这段代码就抽离了DAO层和业务处理层。并且在业务处理层,控制哪些DAO操作在一个事务里面,哪些不在一个事务里面。<br/> 但是这种显示的传递connection来控制,在编码上面比较麻烦,需要通过编码来控制一个connection的事务包含几条SQL需要执行,并且感觉初始化连接这一块以及获取数据库连接重用的地方很多,在设计方面不够简洁。能不能再简化一点。下面将会说说当前比较常用的事务控制实现基本思想。 ###四、无感知的事务控制### 一般情况,一个业务处理,基本上都是在一个线程中。其实事务控制,就是在执行一系列数据库操作之前开启事务,执行完毕之后提交或者回滚事务,中间则是执行具体的SQL语句,我们是否可以将这个执行前后抽离出来,而只需要中间这一块具体的数据库操作留给开发人员。不需要在业务处理层显示的通过参数传递的方式将Connection
传递给Dao层,由于一个业务处理都是在一个线程中完成,是否可以在事务开启之前获取连接,并且将连接放到线程变量中,之后在该线程中的数据库操作都可以访问这个线程变量来获取数据库连接,这种方式在业务层基本无法感知数据库事务的存在。下面给出了这种模式的简单实现:
<!--lang:java-->
abstract class TransactionCallBack<T extends Object>{
abstract T transactionCallBack();
}
一个抽象类(或者接口),这个是留给开发人员实现并添加访问数据库具体操作代码。
<!--lang:java-->
class TransactionHandler{
//装载数据库连接的线程变量
private static final ThreadLocal<Connection> CONNECTION_HANDLER = new ThreadLocal<Connection>();
//执行数据库操作,将需要执行的数据库操作,通过Callback传递进来
public <T> T execute(TransactionCallBack<T> callBack) throws Exception {
Class.forName("driveName");
Connection connection = DriverManager.getConnection("jdbcurl");
connection.setAutoCommit(false);
//将连接存在在线程变量中
CONNECTION_HANDLER.set(connection);
try{
//执行开发人员实现的具体数据库访问代码
T ret = callBack.transactionCallBack();
//提交事务
CONNECTION_HANDLER.get().commit();
return ret;
}catch(Exception e){
//对事务进行回滚
Connection conn = CONNECTION_HANDLER.get();
if(conn!=null){
conn.rollback();
}
throw e;
}finally{
Connection conn = CONNECTION_HANDLER.get();
if(conn!=null){
conn.close();
CONNECTION_HANDLER.remove();
}
}
}
//提供对外获取数据库连接的静态方法
public static Connection getConnection(){
return CONNECTION_HANDLER.get();
}
}
上面是一个事务模板类的定义,完成事务开启,以及事务提交/回滚公共模块。
<!--lang:java-->
public void hideConnectionTransaction(){
//创建一个数据控制器,该属性可以放在方法级别
TransactionHandler handler = new TransactionHandler();
try {
//执行有事务的数据库访问
handler.execute(new TransactionCallBack<Object>(){
@Override
Object transactionCallBack() {
Dao1 dao1 = new Dao1();
Dao2 dao2 = new Dao2();
dao1.executeSQL();
dao2.executeSQL();
return null;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
class Dao1{
public void executeSQL(){
//在DAO层获取数据库连接
Connection connection =TransactionHandler.getConnection();
//TODO 数据库操作
}
}
class Dao2{
public void executeSQL(){
Connection connection =TransactionHandler.getConnection();
//TODO 数据库操作
}
}
如果理解AOP思想的应该很容易理解上面的这段代码的原理。可以理解成,显示的AOP实现。这种模式在Spring中也有类似的API,虽然Spring比较推崇声明式事务管理,但是底层原理和这里差不多,只是它里面使用了AOP方式。关于AOP怎么来实现我这里就不再赘述了,由于思想都差不多。
这里就简单的介绍了一下我个人经历J2EE开发过程中对事务控制的几个历程。也和大家分享一下,相互交流。也希望大家分享一下你们的学习经历。