关于UserTransaction(有些迷惑)

假如我在session bean(<transaction-type>为“Bean”)中写了类似下面的代码:
try {
usrTran.begin();

dbConnection.setAutoCommit(true); //JDBC被设置为自动提交更改

dbConnection.insertIntoTableA(); //往A表里插入几条数据
dbConnection.updateTableA(); //修改前一步插入A表的那几条数据

//进行一些别的操作
...
...

dbConnection.deleteFromTableA(); //将刚才插入A表的几条数据删除

usrTran.end();
} catch (SQLException ex) {
usrTran.rollback();
}
如果最后进行删除时失败并抛出SQLException,那么执行完usrTran.rollback();语句后,A表还能不能恢复到插入数据前的状态呢?
如果A表能够恢复到插入数据前的状态的话,难道是JTA/JTS自己另外保存了被操作资源(A表)的副本(而不是直接利用JDBC提供的事务机制),然后在JDBC已经完成了commit的情况下,仍然可以将原先的数据从保存的副本中回写到A表?



public class UserTransaction {
private static Map threadDbconnectionMap = new HashMap();
public void begin() {
dataSource.getConnection().setAutoCommit(false);
threadDbConnectionMap.put(Thread.currentThread(), dataSource.getConnection());
dataSource.getConnection().startTransaction();
}

public void commit() {
dataSource.getConnection().commitTransaction();
threadDbConnectionMap.remove(Thread.currentThread());
}
}

public class DataSource {
private static Map threadDbConnectionMap = new HashMap();
public Connection getConnection() {
if(threadDbConnectionMap.get(Thread.currentThread()) == null) {
threadDbConnectionMap.put(Thread.currentThread(), DBCONNECTION_POOL.getConnection);
}
return (Connection)threadDbConnectionMap.get( Thread.currentThread());
}
}

UserTransaction的机制就是建立一个currentThread和一个DBconnection的map,使得在同一个thread下的所有db operation使用同一个connection,这样通过背后的同一个connection的commit或rollback来保证transaction的atomic

To jrog:
非常感谢你无私的回复。
不过从你提供的UserTransaction的演示代码来看,那就是一旦与当前thread关联的dbConnection的commit()方法被调用,那么再怎么调用usrTran.rollback()也无法恢复原来的数据了,对吗?(不过我猜测JTA/JTS的实现应该并不是这样的吧?)
此外,我写的那几行演示代码中之所以要调用dbConnection.setAutoCommit(true),是因为我现在参与的一个项目中间遇到着这样的问题,即对同一个表的多次更新都在同一个事务的范围之内,那么显然先执行的对该表的更新就必须马上提交,否则会引起死锁,从而导致后面执行的对该表的更新无法开始。
由于现在我们并没有使用Application Server,而是只使用了一个Web Server(不提供JTA/JTS支持),所以我们现在的做法是手动地为每一个更新都编写一个恢复原始数据的方法(hand recovery code),实在是非常吃力呀!:((

昨天晚上由于太晚的缘故,家人催促着早点休息(不是双休日,第二天还得上班),没有来得及详细回答你的问题。

很高兴在这个论坛中看到这样的问题。不客气的说,你所问的问题是我进入这个论坛以来所见到的有一定深度的问题(有点跑题了)。两年前我曾经为这个问题抓耳挠腮,百思不得其解。我仿佛从你身上看到了当初自己的影子,呵呵

1。由于你只是使用了web server,没有采用application server,你的application又需要提供Transaction的control,唯一的办法是提供你自己的Transaction Manager,而不是使用UserTransaction(正如你所说的,web server不提供UserTransaction)。UserTransaction也是一种TransactionManager,由J2EE Container提供。

2。你的“dbConnection.setAutoCommit(true)”这个调用是错误的。如果你调用该语句,dbConnection就会每次execute一句sql以后自动commit了,这样你的程序中本身需要放在同一个transaction中执行的代码分散在了好几个不同的transaction中执行了,就会导致partial update的问题,data consistency就没有办法保证。

3。“死锁”的问题应该是你程序中的问题,而不应该归咎于dbConnection是否是auto commit的。我在以前曾经写过一个自己的TransactionManager,就是通过建立一个Thread和dbConnection的map来实现的。刚开始测试这个TransactionManager的时候,也是经常出现“死锁”的问题,最后debug的结果发现并不是auto commit的问题,而是对于thread synchronization的问题处理不得当所导致的。

4。“手动地为每一个更新都编写一个恢复原始数据的方法”是错误的。试想想,如果在你手动恢复数据的过程中又出现了Exception,你又如何保证data consistency呢?

先写这么多,回头继续

你所问的问题是我进入这个论坛以来第一次见到的有一定深度的问题

5。自己编写TransactionManager是一件比较复杂的工作,不是我想建议的。这也是为什么banq强烈推荐EJB的原因。EJB提供了TransactionManager的功能,我们可以采用EJB的解决方案,just off-the-shelf,不是吗?或者用JTA和DataSource一道实现Transaction的control。

6。如果你必须要采用自己编写的TransactionManager,我可以讲解一下J2EE CONTAINER所提供的JTA/JTS的实现原理,或许对于你编写自己的TransactionManager有一定的帮助。

7。 我们知道在J2EE Spec中提供了UserTransaction和DataSource的interface的定义,具体是如何实现的留给那些J2EE Container的vendor来实现,对于application developer来说他所能见到的就是这两个interface。对于支撑这两个interface背后的东西,application developer是永远都不可能知道的。看看下面的architecture吧

User-defined Application API
----------------------------
UserTransaction DataSource
----------------------------
DBConnectionPool ThreadConnectionMap
(XAConnection, XAResource, XADataSource)

我们清楚的看到,对于UserTransaction,DataSource的支持离不开我们所看不到的J2EE spec中的一些定义,对于application developer来说,它隐藏了这些东西的复杂性。与application developer打交道的只是UserTransaction和DataSource。



public class DBConnectionPool {
// singleton Pattern
public Connection fetchConnection() ;
public void releaseConnection(Connection conn);
}

public class ThreadConnectionMap {
// singleton pattern.
private Map threadConnectionMap = new HashMap();

public Connection getConnection() {
if(threadConnectionMap.get(Thread.currentThread()) != null) {
return (Connection)threadConnectionMap.get(Thread.currentThread());
}
return DBConnectionPool.fetchConnection();
}

public Connection getConnectionInTx() {
return (Connection)threadConnectionMap.get(Thread.currentThread());
}

public void releaseConnectionInTx() {
Connection con = getConnectionInTx();
threadConnectionMap.remove(Thread.currentThread());
DBConnectionPool.releaseConnection(con);
}

public Connection newConnectionInTx() {
if(inTransaction()) {
throw new TransactionException(
"Transaction already started!");
}
Connection conn = DBConnectionPool.fetchConnection();
threadConnectionMap.put(Thread.currentThread(), conn);
return conn;
}

public boolean inTransaction() {
return threadConnectionMap.get(Thread.currentThread()) != null;
}

..............
}

public class VendorDataSource() implements javax.sql.DataSource {
public Connection getConnection() {
return ThreadConnectionMap.getConnection();
}

..........
}

public class VendorUserTransaction implements javax.transaction.UserTransaction {
public void begin() {
Connection con = ThreadConnectionMap.newConnectionInTx();
con.setAutoCommit(false);
}
public void commit() {
Connection con = ThreadConnectionMap.getConnectionInTx();
if(con == null) {
throw new TransactionException(
"cannot commit transaction, UserTransaction not started!");
}

con.commit();
ThreadConnectionMap.releaseConnectionInTx();
}
public void rollback(){
Connection con = ThreadConnectionMap.getConnectionInTx();
if(con == null) {
throw new TransactionException(
"cannot rollback transaction, UserTransaction not started!");
}

con.rollback();
ThreadConnectionMap.releaseConnectionInTx();
}

public boolean inTransaction() {
return ThreadConnectionMap.inTransaction();
}
....
}

以上是没有考虑XA(distributed transaction)和连接多个数据库情况的TransactionManager的实现,非常简陋,只是用于说明JTA的原理

To jrog:
十分感谢你详尽的解答,让我受益匪浅。

1. 不过在你提供的ThreadConnectionMap.java中的Thread.currentThread()究竟是由谁,何时,为什么而起动的呢?WebContainer吗?那么Thread.currentThread()就是指的HttpRequestHandler?如果不是,那么我又该如何理解这个Thread.currentThread()呢?仍然很迷惑 :(

2. 此外,我前面提到的“死锁”其实应该指的是“无限等待”,以下面的代码为例:


try {
usrTran.begin();

dbConnection.setAutoCommit(false);

insertIntoTableA(); //往A表中插入两条记录

//进行一些别的操作
//...
//...

updateTableA_1stTime();
//第一次更新往A表中插入的那两条记录

//进行一些别的操作
//...
//...

updateTableA_2ndTime();
//第二次更新往A表中插入的那两条记录

//进行一些别的操作
//...
//...

deleteFromTableA();
//删除往A表中插入的那两条记录

usrTran.end();
} catch (SQLException ex) {
usrTran.rollback();
}

那么因为一开始调用了“dbConnection.setAutoCommit(false);”,会不会导致在执行完“updateTableA_1stTime();”后没有提交更新,从而在接下来执行“updateTableA_2ndTime();”时进入无限等待状态呢?(也许是我Oracle学得太差,连这个也不十分清楚:$)。而且,从你前面的解释来看,“usrTran.rollback();”只能恢复dbConnection尚未commit的资源;一旦连“dbConnection.commit();”都被执行了,那么再怎么调用“usrTran.rollback();”也无济于事了,不知是否正确地理解了你的意思?

你需要重新回炉好好学学servlet了,呵呵

当servlet处理一个http request的时候,就会start一个新的thread来handle这个request,你所说webcontainer start thread也不算错吧

insertIntoTableA();
updateTableA_1stTime();
updateTableA_2ndTime(); 这些method的调用之间是不会产生“无限等待的”他们都属于同一个transaction context,Database会自动handle这些的,你替他担心这些东西,是否有点杞人忧天的味道了? :)

另外,既然你都决定要commit那些db change了,为什么又需要rollback呢?真是有点不明白了

通常情况下,要么commit(sql be executed successfully),要么rollback(sql be executed failed).哪有即commit又rollback的,不太明白,活活

你的dbConnection和usrTransaction是如何得到的?

> 另外,既然你都决定要commit那些db
> change了,为什么又需要rollback呢?真是有点不明白了
>
> 通常情况下,要么commit(sql be executed
> successfully),要么rollback(sql be executed
> failed).哪有即commit又rollback的,不太明白,活活

我之所以决定要在捕获到SQLException的时候需要rollback,其实还是因为


insertIntoTableA();
updateTableA_1stTime();
updateTableA_2ndTime();
deleteFromTableA();

这几个方法在业务上属于一个事务的范围之内,所以你现在不难理解我为什么要为每个更新都手动地写一个数据恢复方法了吧?:) 当然,进行手动数据恢复时的异常的确是个问题。
我现在碰到的这种问题难道连JTA/JTS也无法解决吗?

P.S. 我的dbConnection也是从ConnectionPool中取得的,这个倒是由Web Server(Enhydra 3.1,估计你没用过)提供的;至于usrTran则是为了演示才加上去的,目前是没有现成的TransationManager和UserTransaction的,如果程序迁移到Application Server上,那么就会有了,不过即使是这样,好像也仍然无法解决我现在碰到的这种问题 :'(


insertIntoTableA();
updateTableA_1stTime();
updateTableA_2ndTime();
deleteFromTableA();

如果以上几个方法中的sql(更新前不执行锁定)都在同一个Oracle的session中执行的话,只要没有别的session或是别的客户端来打扰,应该是可以正常执行完的;但是万一有别的session或是别的客户端来打扰,很有可能就会陷入“无限等待状态了”。

你真是够笨的!!! :(

insertIntoTableA();
updateTableA_1stTime();
updateTableA_2ndTime();
deleteFromTableA();

这些method中如果出现sqlException的话,你应该是throw这些exception出来,而不是在这些method中catch它,让这些method的调用者去根据这些exception来决定是否rollback

另外,如果你决定采用UserTransaction的话,你必须采用J2EE container所提供的DataSource来得到db connection,而不是从什么“Enhydra 3.1”的connectionpool中得到的。

再一次重申:UserTransaction必须和相应的DataSource一到配合使用,i.e.如果使用J2EE container提供的UserTransaction,你也必须使用该container提供的DataSource服务才可以