关于weblogic中使用prepared statement cache后操作DDL的问题

关于weblogic中使用prepared statement cache后操作DDL的问题

        前几天有客户问我这么个问题,他们在weblogic中配置了prepared statement cache, 而他们应用中有操作DDL的地方,比如alter table什么的,这时候如果使用cached prepared statement的话,Oracle端会抛出SQLException: 违反协议。其实这个问题,weblogic 文档中已经有描述,如下:
        http://e-docs.bea.com/wls/docs81/ConsoleHelp/jdbc_connection_pools.html#1107805

        大概意思是:这个依赖数据库,需要看数据库端怎么处理这样的prepared statement. 最初我认为只要在weblogic 端手工清理掉整个cache就可以了(weblogic在prepared statement 出现异常的时候,会主动将wrapper connection上对应的prepared statement cache清掉,下次调用的时候会重建prepared statement,所以手工清理cache是完全多余的),但实际结果并不如想象的那样。即使我们clear掉prepared statement cache, 重新创建一个prepared statement的话,问题同样得不到解决。 为什么? 怎么办?作了几个相关的测试后, 结论是:这个行为依赖于DB的physical connection, 而不是单个的prepared statement,出现这样的问题后,能做的有如下2种方式:
         1:客户端处理prepared statement抛出的异常, catch到异常后,需要将physical connection拿出来close掉。之所以建议这样,客户从data source中拿出的是个logical connection,而physical connection一直在connection pool。如果简单的close掉logical connection, 重新去拿一个logical connection的话,weblogic无法保证返回的connection用了不同的physical connection。后面会有详细的解决办法。
         2:等待,大约一分钟左右,可以正常操作。

        首先看看为什么?
         好了,我们可以用用下面的代码测试一下:在测试程序run起来以后, 通过sql plus去改变后端test table的结构,比如alter table test  add(key1 varchar(10))

 1  package  test.jdbc;
 2 
 3  import  oracle.jdbc.OracleDriver;
 4  import  java.sql.DriverManager;
 5  import  java.sql.Connection;
 6  import  java.sql.PreparedStatement;
 7  import  java.sql.ResultSet;
 8 
 9  public   class  OracleDriverTest {
10      
11       public   static   void  main(String args[])
12      {
13           try
14          {
15              OracleDriver driver  =  (OracleDriver)Class.
16                  forName( " oracle.jdbc.OracleDriver " ).newInstance();
17              DriverManager.registerDriver(driver);
18              String url = " jdbc:oracle:thin:@localhost:1521:coffeedb " ;
19              Connection conn  =  DriverManager.getConnection(url,  " system " " coffee " );
20              PreparedStatement pstmt  =  conn.prepareStatement( " select * from Test " );
21               for ( int  loop = 0 ; loop < 10 ; loop ++ )
22              {
23                   try
24                  {
25                      System.out.println(pstmt.toString());
26                      ResultSet rs  =  pstmt.executeQuery();
27                       while (rs.next())
28                      {
29                          String val  =  rs.getString( 1 );
30                          System.out.println(val);
31                      }
32                      rs.close();
33                      Thread.currentThread().sleep( 5000 );
34                  } catch (java.sql.SQLException se)
35                  {
36                       // Thread.currentThread().sleep(10000);
37                      se.printStackTrace();
38                      System.out.println( " get exception, remake prepared statement in loop:  "   +  loop);
39                       /*
40                       * if we just remake a prepared statement, SQLException will be thrown still, to
41                       * slove such issue, we have to remake a physical connection. To do the test, we
42                       * can comment the next line at first to see what will happen and then we activate
43                       * it, to see what will happen this time. 
44                        */  
45                       // conn = DriverManager.getConnection(url, "system", "coffee");
46                      pstmt  =  conn.prepareStatement( " select * from Test " );
47                       continue ;
48                  }
49              }
50              pstmt.close();
51              conn.close();
52              
53          } catch (Exception e)
54          {
55               try
56              {
57                   // Thread.currentThread().sleep(10000);
58                  System.out.println( " catch exception in main() " );
59                  e.printStackTrace();
60              } catch (Exception e1)
61              {
62                  e1.printStackTrace();
63              }
64          }    
65      }
66  }
67 


       如代码中的注释说的一样,单纯的重建prepared statement是没用的,需要重建physical connection. 这个代码中connection没有通过weblogic, 直接从driver manager拿connection, 问题一样能够复现, 跟weblogic没关系了吧。

         好了,知道为什么了,但怎么办呢? physical connection是weblogic在connection pool中维护的,我们怎么去控制它们啊?看文档, weblogic的jdbc programming提到了具体的操作方法,链接如下:
        http://e-docs.bea.com/wls/docs81/jdbc/thirdparty.html#1108224

1       java.sql.Connection vendorConn  =         ((WLConnection)conn).getVendorConnection();      
2  //  do not close vendorConn     
3  //  You could also cast the vendorConn object  // to a vendor interface, such as:      
4  //  oracle.jdbc.OracleConnection vendorConn = (OracleConnection) 

        
        文档中不建议我们自己去关闭vendor connection,而是由connection pool自己去管理,connection pool通过Remove Infected Connections Enabled来控制physical connection如何还池,

Applies only to physical database connections.

When set to true, the physical connection is not returned to the connection pool after the application closes the logical connection. Instead, the physical connection is closed and recreated.

Note: It is recommended that you set this flag to true as such connections are not managed by WebLogic Server.

When set to false, if you close the logical connection, the physical connection is returned to the connection pool. If you use this setting, ensure that the database connection is suitable for reuse by other applications.

This parameter is applicable only if the application gets a connection from the connection pool and then calls the getVendorConnection() method on that object. The getVendorConnection() method returns a vendor specific connection to the caller of the method which might leave the connection pool in an inconsistent state. As a result, WebLogic Server might remove it from the pool assuming it is an infected connection.

Enabling this attribute will have an impact on performance as it will essentially disable the pooling of connections. This is because connections will be removed from the pool and replaced with new connections.


        因为我们这个问题必须关闭physical connection, 所以采用默认配置就可以了。你也许还会有疑问,physical connection被关闭了,如果我反复搞这么几次,connection不就被关完了?其他应用怎么办?不用担心,有connection pool呢,在getVendorConnection()被调用的时候, connection会检查Remove Infected Connections Enabled,如果为true,即这个logical connection对应的physical connection不会被重用,它会schedule一个创建physical connection的动作,以补充那个抛弃我们的phisical connection。最后关闭连接的时候,logical connection会被废弃,physical connection被关闭。

        而我在测试中,尝试自己去关闭vendor connection,如下:

 1       private   void  initializeConnection()  throws  SQLException
 2      {
 3           // this test should be run in local jvm, as oracle.jdbc.driver.T4CConnection
 4           // is not a serializable object.
 5           // java.io.NotSerializableException: oracle.jdbc.driver.T4CConnection
 6           this .conn  =   this .retriver.getJBDCConnection(dsName);
 7           this .wlConn  =  (WLConnection) this .conn;
 8           this .oraConn  =  (OracleConnection) this .wlConn.getVendorConnection();
 9          System.out.println( this .conn.toString());
10          System.out.println( this .oraConn.toString());
11      }
12      
13       private   void  pstmtTest()
14      {
15           try
16          {
17              PreparedStatement pstmt  =   this .conn.prepareStatement( " select * from Test " );
18              System.out.println(pstmt.toString());
19              ResultSet rs  =  pstmt.executeQuery();
20               while (rs.next())
21              {
22                  String val  =  rs.getString( 1 );
23                  System.out.println(val);
24              }
25              rs.close();
26              pstmt.close();
27               this .oraConn.close();
28               this .conn.close();
29          } catch (Exception e)
30          {
31               try
32              {
33                   this .oraConn.close();
34                   this .conn.close();
35              } catch (Exception e1)
36              {
37                  e1.printStackTrace();
38              }
39              e.printStackTrace();
40          }
41      }


        测试也没什么问题,应该是oracle connection在关闭connection时先去检查connection 状态,如果已经关闭,则忽略这个动作,否则weblogic在关闭physical connection的时候应该收到SQLException。虽然这么做没什么问题,但还是建议大家按照文档上的说明,不要主动关闭phisical connection, 而让connection pool自己去管理。

        总结一下:要处理这个问题,在catch到preparedStatement.execute***()抛出的SQLException时候,将从data source中get出来的connection cast成WLConnection, 然后调用getVendorConnection()即可,不要做其他任何处理。但有一个限制,这个方法必须在server端执行, 因为Vendor Connection不是个serializable对象,不能被拿到remote JVM上去用。

        时间有限,只做了Oracle的测试,没有做其他DB的测试,比如DB2, SQLServer,毕竟这个问题依赖于数据库,不保证这一做法是用于其它所有数据库。

你可能感兴趣的:(关于weblogic中使用prepared statement cache后操作DDL的问题)