前几天有客户问我这么个问题,他们在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