公司的persistence是一个很久之前写的的框架了。期间经过了各种修修补补。最近要做的一个东西,需要支持数据库事务。
看了下,居然不支持事务,这个猛。。。
下面是一个数据库操作的调用链
框架采用了命令模式,最后会调用到以下的代码里
UpdateDatabaseExecutor{
public void invokeExecutor(...) throws Exception {
...
java.sql.Connection con = null;
con = ConnectionManager.getConnection();
//取到connection 后,并没有begin,rollback之类的事务支持
stmt.beforeExecution();
ret = stmt.executeUpdate(con);
stmt.afterExecution(ret);
...
PersistenceUtils.closeStuff(con, stmt, null);
}
}
由于知道所有的Connection都使用ConnectionManager.getConnection(),最后会调用到一个统一的代码里,如下
javax.sql.DataSource ds = (javax.sql.DataSource) result;
final String username =getProperty("javax.sql.username", ctx_);
final String password =getProperty("javax.sql.password", ctx_);
if ((username != null) || (password != null)) {
result = ds.getConnection(username, password);
} else {
result = ds.getConnection();
}
return result;
根据spring template transaction 里的源码的原理,begin的方法 取connection的并将connection放到threadlocal里。后面取得时候先判断threadlocal里是否已经存在Connection,如果存在的话,就从threadlocal里去取了。则可以改造代码加入如下
//加入的支持transaction处理开始
transactionManager.setDataSource(ds);
Connection connection = transactionManager.getConnection();
if(connection!=null){
return connection;
}
//加入的支持transaction处理结束
javax.sql.DataSource ds = (javax.sql.DataSource) result;
final String username =getProperty("javax.sql.username", ctx_);
final String password =getProperty("javax.sql.password", ctx_);
if ((username != null) || (password != null)) {
result = ds.getConnection(username, password);
} else {
result = ds.getConnection();
}
return result;
那么关键就在于 transactionManager.getConnection();
transactionManager也是我新加的一个类
public class TransactionManager {
//在线程上下文里存入一个ConnectionHolder的类
private ThreadLocal<ConnectionHolder> ConnectionCache =new
ThreadLocal<ConnectionHolder>();
private TransactionManager(){};
//spring里dataSource可以有多个,而我的项目里只有一个
private DataSource dataSource;
private static TransactionManager instance = new TransactionManager();
//单例
public static TransactionManager getInstance(){
return instance;
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
private ConnectionHolder get(){
return ConnectionCache.get();
}
//getConnection的时候从上下文里先判断是否存在,如果有的就直接获取,没有就返回空
public Connection getConnection() throws SQLException{
Connection connection = null;
ConnectionHolder holder =get();
if(holder==null)
return null;
if(holder.getOwnerThread() == Thread.currentThread()){
connection = holder.getConn();
}
if(connection!=null && !connection.isClosed() && !connection.getAutoCommit()){
return connection;
}
return null;
}
public void setConnection(Connection conn) {
ConnectionCache.set(new ConnectionHolder(conn,Thread.currentThread()));
}
public static class ConnectionHolder{
private Connection conn;
private Thread ownerThread;
public ConnectionHolder(Connection conn, Thread ownerThread) {
this.conn = conn;
this.ownerThread = ownerThread;
}
public Connection getConn() {
return conn;
}
public Thread getOwnerThread() {
return ownerThread;
}
}
public boolean hasConnectionInThread() throws SQLException{
return getConnection()!=null;
}
public boolean hasConnectionInThread(Connection conn) throws SQLException{
Connection connInThreadContext = getConnection();
if(connInThreadContext==null)
return false;
if(connInThreadContext==conn)
return true;
return false;
}
//调用doBegin,会将一个Connection放入到threadlocal里
public Connection doBegin() throws ConnectionException, SQLException {
Connection conn = null;
conn = ConnectionManager.getConnection();
conn.setAutoCommit(false);
setConnection(conn);
return conn;
}
public void commit() throws SQLException {
getConnection().commit();
}
public void rollback() throws SQLException {
getConnection().rollback();
}
public void close() throws SQLException {
getConnection().close();
ConnectionCache.remove();
}
}
在调用的地方
public void processs(PendingUserValue pendingUserValue) {
try {
。。。
TransactionManager.getInstance().doBegin();
process(pendingUserValue);
TransactionManager.getInstance().commit();
。。。
}catch(Exception e){
try {
TransactionManager.getInstance().rollback();
}finally{
try {
TransactionManager.getInstance().close();
} catch (SQLException e) {
。。。
}
}
}
以为万事大吉了,在测试中发现,我们的数据库里的表的主键不是通过自增的方式来做的,而是通过一张主键表来维护的。
也就是说process(pendingUserValue)里如果要插入一条记录,例如User, 就要先到主键表里获取到User的key,然后更新主键表里的User主键最大值,这样就需要执行commit操作。
一旦commit的话,connection遇到错误就没法rollback了。这实际上是事务的传播机制里的 RequiresNew
当Transaction2执行的时候,transaction1需要挂起。
用我的项目里逻辑就是外面 process执行到需要获取主键时,获取的主键是一个单独的事务,这个单独事务不影响process的处理。因此看了下spring 对这部分的处理源码,然后在TransactionManager 里加入了如下代码
public class TransactionManager {
//挂起
public ConnectionHolder suspend() {
ConnectionHolder holder= get();
ConnectionCache.remove();
return holder;
}
//恢复
public void resume(ConnectionHolder holder) {
ConnectionCache.set(holder);
}
}
获取主键的调用的时候,先挂起,再在finally里恢复,如下
TransactionManager.ConnectionHolder holder = null;
try {
if(TransactionManager.getInstance().hasConnectionInThread()){
holder = TransactionManager.getInstance().suspend();
}
connection = ConnectionManager.getConnection();
connection.commit();
} finally {
PersistenceUtils.closeStuff(connection, statement, resultSet);
if(holder!=null)
TransactionManager.getInstance().resume(holder);
}