使用proxool连接sybase时处理事务的问题

来源:互联网 发布:传奇武器外观算法 编辑:程序博客网 时间:2024/05/16 18:53

问题描述

今天花了一天时间解决一个问题,好久没有这样钻研解决这样的问题了。

http://topic.csdn.net/t/20050608/16/4068615.html

问题跟这个帖子相同,就是使用proxool+sybase处理事务时,会报错。

先看看以下代码

  1. public static void test2() throws Exception {  
  2.     // Connection conn = getConnectionFromPool();  
  3.     Connection conn = getConnection();  
  4.     conn.setAutoCommit(false);  
  5.     Statement stmt = conn.createStatement();  
  6.     String sql = "select 1";  
  7.     stmt.executeQuery(sql);  
  8.     conn.commit();  
  9.     try {  
  10.         conn.setAutoCommit(true);  
  11.     } catch (Exception e) {  
  12.         e.printStackTrace();  
  13.     }  
  14.     stmt.close();  
  15.     conn.close();  
  16. }  
  17.   
  18. public static Connection getConnection() throws Exception {  
  19.     Class.forName("com.sybase.jdbc3.jdbc.SybDriver");  
  20.     String username = "sa";  
  21.     String pwd = "";  
  22.     String url = "jdbc:sybase:Tds:192.168.0.100:5000/test";  
  23.     return DriverManager.getConnection(url, username, pwd);  
  24. }  
  25.   
  26. static public Connection getConnectionFromPool() throws Exception {  
  27.     Connection connection = null;  
  28.     String alias = "test";  
  29.     Class.forName("org.logicalcobwebs.proxool.ProxoolDriver");  
  30.     if (connection == null) {  
  31.   
  32.         Properties info = new Properties();  
  33.         info.setProperty("proxool.maximum-connection-count""20");  
  34.         info.setProperty("user""sa");  
  35.         info.setProperty("password""");  
  36.         info.put("proxool.maximum-active-time""60000");  
  37.         String driverClass = "com.sybase.jdbc3.jdbc.SybDriver";  
  38.         String driverUrl = "jdbc:sybase:Tds:192.168.0.100:5000/test";  
  39.         String url = "proxool." + alias + ":" + driverClass + ":"  
  40.                 + driverUrl;  
  41.         connection = DriverManager.getConnection(url, info);  
  42.         System.out.println(connection);  
  43.   
  44.     }  
  45.     return connection;  

在运行test2()函数时没有出现异常正常,这里是没有使用proxool,用原始的数据库连接方式。

将test2()第一行的注释去掉,将第二行注释掉,运行时就会报错

错误信息代码
  1. Exception in thread "main" com.sybase.jdbc3.jdbc.SybSQLException: SET CHAINED command not allowed within multi-statement transaction.  
  2. at com.sybase.jdbc3.tds.Tds.processEed(Tds.java:2942)  
  3. at com.sybase.jdbc3.tds.Tds.nextResult(Tds.java:2246)  
  4. at com.sybase.jdbc3.jdbc.ResultGetter.nextResult(ResultGetter.java:69)  
  5. at com.sybase.jdbc3.jdbc.SybStatement.nextResult(SybStatement.java:220)  
  6. at com.sybase.jdbc3.jdbc.SybStatement.nextResult(SybStatement.java:203)  
  7. at com.sybase.jdbc3.jdbc.SybStatement.updateLoop(SybStatement.java:1804)  
  8. at com.sybase.jdbc3.jdbc.SybStatement.executeUpdate(SybStatement.java:1787)  
  9. at com.sybase.jdbc3.jdbc.SybPreparedStatement.executeUpdate(SybPreparedStatement.java:116)  
  10. at com.sybase.jdbc3.tds.Tds.setOption(Tds.java:1278)  
  11. at com.sybase.jdbc3.jdbc.SybConnection.setAutoCommit(SybConnection.java:1011)  
  12. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  
  13. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)  
  14. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)  
  15. at java.lang.reflect.Method.invoke(Method.java:585)  
  16. at org.logicalcobwebs.proxool.WrappedConnection.invoke(WrappedConnection.java:208)  
  17. at org.logicalcobwebs.proxool.WrappedConnection.intercept(WrappedConnection.java:98)  
  18. at com.sybase.jdbcx.SybConnection$$EnhancerByProxool$$8df5facc.setAutoCommit(<generated>)  
  19. at TestConnectionTran.test(TestConnectionTran.java:34)  
  20. at TestConnectionTran.main(TestConnectionTran.java:13)  

问题原因

s_phoenix大侠的解决办法是:

写道
自己解决了。通过拦截setAutoCommit和close方法,并适当做修改,解决了sybase的问题。
修改的是proxool,因为jconn没有源代码。修改hibernate又不成功,只好修改夹在其中的缓冲池了。

 虽然我最后也是这样解决的,但是s_phoenix大侠没有告诉我怎么修改,我还是花了几个小时来调试proxool源代码。

 

首先说明下,sybase为什么会报这个错误

错误信息代码
  1. SET CHAINED command not allowed within multi-statement transaction.  

到google,baidu里面随便搜下,就有一堆。简单的一句话:处于事务环境下,调用 set chained 是会发生异常的,这一点手册上也非常明确的指出了。使用sybase的jconn驱动,调用setAutoCommit(false)事务开始,也就是 set chained on,然后执行完相应的操作后commit或者rollback结束事务,这时需要setAutoCommit(true)“完壁归赵”,以便连接池回收。但是在proxool中后面setAutoCommit(true)会报错,以上的错误。这个错误明显是事务没有结束又调用setAutoCommit方法,但是明明事务已经结束了,原因何在?

    查看源代码发现了问题,先看源代码org.logicalcobwebs.proxool.WrappedConnection:

Proxool代码代码
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  2.         Object result = null;  
  3.         int argCount = args != null ? args.length : 0;  
  4.         Method concreteMethod = method;  
  5.         if (proxyConnection != null && proxyConnection.getConnection() != null) {  
  6.             concreteMethod = InvokerFacade.getConcreteMethod(proxyConnection.getConnection().getClass(), method);  
  7.         }  
  8.         try {  
  9.             if (proxyConnection != null && proxyConnection.isReallyClosed()) {  
  10.                 // The user is trying to do something to this connection and it's been closed.  
  11. ................  
  12.                 if (concreteMethod.getName().equals(IS_CLOSED_METHOD)) {  
  13.                 
  14.         return result;  
  15.     }  

 这块代码是动态代理执行Connection的所有方式时被调用的方法,可以看出,每次都会调用proxyConnection.isReallyClosed()方法,这个方法里面调用的是Connection.isClose()方法.使用连接池调用test2()函数,可以类比于以下方式:

非连接池代码
  1. public static void test1() throws Exception {  
  2.         Connection conn = getConnection();  
  3.         conn.isClosed();  
  4.         conn.setAutoCommit(false);  
  5.         conn.isClosed();  
  6.         conn.commit();  
  7.         conn.isClosed();  
  8.         conn.setAutoCommit(true);  
  9.     }  

 运行test1报错根test2一样.问题就出现在这里.可以猜到conn.isClosed();又开启了一个新的事务,该事务没有结束就conn.setAutoCommit(true);当然会报错.事实上,我通过抓报工具,发现conn.isClosed();会发送命令sp_mda到数据库中,这个过程就可以看作是在数据库中执行以下命令:

Sybase代码
  1. set chained on  
  2. go  
  3. select 1  
  4. go  
  5. commit   
  6. go  
  7. sp_mda 0,3,2  
  8. set chained off  
  9. go  

问题解决

问题原因找到了,怎么解决呢? s_phoenix大侠给了我提示,提前拦截setAutoCommit方法.

修改后的代码
  1. public Object invoke(Object proxy, Method method, Object[] args)  
  2.             throws Throwable {  
  3.         Object result = null;  
  4.         int argCount = args != null ? args.length : 0;  
  5.         Method concreteMethod = method;  
  6.         if (proxyConnection != null && proxyConnection.getConnection() != null) {  
  7.             concreteMethod = InvokerFacade.getConcreteMethod(proxyConnection  
  8.                     .getConnection().getClass(), method);  
  9.         }  
  10.         try {  
  11.             if (concreteMethod.getName().equals("setAutoCommit")) {  
  12.                 result = concreteMethod.invoke(proxyConnection.getConnection(),  
  13.                         args);  
  14.                 return result;  
  15.             }  
  16.         } catch (Exception e) {  
  17.             LOG.error("setAutoCommit error", e);  
  18.             throw e;  
  19.         }  
  20.         try {  
  21.             if (proxyConnection != null && proxyConnection.isReallyClosed()) {  
  22.                 // The user is trying to do something to this connection and  
  23.                 // it's been closed.  
  24.                 if (concreteMethod.getName().equals(IS_CLOSED_METHOD)) {  
  25.                     // That's cool. No problem checking as many times as you  
  26.                     // like.  

简单的测试了下,没有问题.会不会对其他地方产生影响呢?我不知道,可能会发生很奇妙的结果,哈哈...还是得严格测试下.

 

修改后,还要注意一点,在commit后面要紧跟setAutoCommit(true),如果中间又调用connection其他方法,还是会报错的.

 

原创粉丝点击