JFinal支持事务的操作,这里引用JFinal手册中的一段代码:
boolean succeed = Db.tx(new IAtom(){
public boolean run() throws SQLException {
int count = Db.update("update account set cash = cash - ? where id = ?", 100, 123);
int count2 = Db.update("update account set cash = cash + ? where id = ?", 100, 456);
return count == 1 && count2 == 1;
}
});
我们可以通过如上代码中的方法,执行两个更新操作,其中只要一个操作出现异常,就会回滚。那么,要做到如上的需求,代码中是如何保证的呢?
首先,我们看 IAtom这个接口,它里面定义了一个run() 方法,我们对数据库操作需要事务支持的话,就可以代码写在这里面。写在这里后,又是如何做到事务的呢?接着往下看。
Db.tx(IAtom atom) 方法调用了DbPro类的tx(IAtom atom) 方法,这个方法,就是对数据库的操作,以及事务的封装。
boolean tx(Config config, int transactionLevel, IAtom atom) {
Connection conn = config.getThreadLocalConnection();
if (conn != null) { // Nested transaction support
try {
if (conn.getTransactionIsolation() < transactionLevel)
conn.setTransactionIsolation(transactionLevel);
boolean result = atom.run();
if (result)
return true;
throw new NestedTransactionHelpException("Notice the outer transaction that the nested transaction return false"); // important:can not return false
}
catch (SQLException e) {
throw new ActiveRecordException(e);
}
}
Boolean autoCommit = null;
try {
conn = config.getConnection();
autoCommit = conn.getAutoCommit();
config.setThreadLocalConnection(conn);
conn.setTransactionIsolation(transactionLevel);
conn.setAutoCommit(false);
boolean result = atom.run();
if (result)
conn.commit();
else
conn.rollback();
return result;
} catch (NestedTransactionHelpException e) {
if (conn != null) try {conn.rollback();} catch (Exception e1) {LogKit.error(e1.getMessage(), e1);}
LogKit.logNothing(e);
return false;
} catch (Throwable t) {
if (conn != null) try {conn.rollback();} catch (Exception e1) {LogKit.error(e1.getMessage(), e1);}
throw t instanceof RuntimeException ? (RuntimeException)t : new ActiveRecordException(t);
} finally {
try {
if (conn != null) {
if (autoCommit != null)
conn.setAutoCommit(autoCommit);
conn.close();
}
} catch (Throwable t) {
LogKit.error(t.getMessage(), t); // can not throw exception here, otherwise the more important exception in previous catch block can not be thrown
} finally {
config.removeThreadLocalConnection(); // prevent memory leak
}
}
}
对上面的代码进行分析:
1.这里会先通过
Connection conn = config.getThreadLocalConnection();
获取存储在ThreadLocal中的连接,如果conn不为空,我们则认为当前的数据库操作处于事务中,直接执行数据库操作,即atom.run();
这里有两点需要指出:
★在一个事务开始的时候,会将当前数据库连接存储到 com.jfinal.plugin.activerecord.Config 类的 threadLocal属性中,ThreadLocal这个类的作用是为每个线程维护自己的变量,在这里不同的线程之间会创建各自的连接,互不影响。
★atom.run() 方法,会依次执行数据库的操作,在具体的数据库操作中,如update:
public boolean update(String tableName, String primaryKey, Record record) {
Connection conn = null;
try {
conn = config.getConnection();
return update(config, conn, tableName, primaryKey, record);
} catch (Exception e) {
throw new ActiveRecordException(e);
} finally {
config.close(conn);
}
}
这里会执行 config.close(conn) 关闭连接的操作,进去close方法中看:
public final void close(Connection conn) {
if (threadLocal.get() == null) // in transaction if conn in threadlocal
if (conn != null)
try {conn.close();}
catch (SQLException e) {
throw new ActiveRecordException(e);、
}
}
上面方法中,会判断ThreadLocal中是否存在连接,如果不存在的话,则关闭当前连接–相当于当前数据库操作不是在事务中的话,就关闭连接。
2.接上面的1,如果从ThreadLocal中没有获取到连接,则将当前连接存入ThreadLocal中,且开启事务。执行boolean result = atom.run();,如果返回值result 为false 则回滚,否则提交事务。最后操作完成后,关闭连接,移除ThreadLocal中的连接。