今天,当数据库更新但Web应用程序未重新启动时,我们的Web应用程序出现问题 经过调查,我们发现在db update期间,一个用户定义的类型被删除并再次创建 类型没有改变 - 它只是被删除并使用与以前相同的sql语句再次创建。
以下代码可以显示此问题 执行第二个预处理语句时,存在java.sql.SQLSyntaxErrorException“ORA-00902:invalid datatype”。
String str = "select * from MYTESTTABLE where test in (select column_value from TABLE (?) tmp)";
OracleConnection conn = null;
Properties connectionProps = new Properties();
connectionProps.put("user", "user");
connectionProps.put("password", "pwd");
conn = (OracleConnection) DriverManager
.getConnection("conn", connectionProps);
Statement stmt = conn.createStatement();
stmt.execute("drop table mytesttable");
stmt.execute("create table MYTESTTABLE ( test NUMBER(22))");
System.out.println("Table MYTESTTABLE was created");
stmt.execute("drop type my_type");
System.out.println("Type my_type was dropped");
stmt.execute("create type my_type as table of NUMBER(22)");
System.out.println("Type my_type was created");
String[] northEastRegion = { "10022", "02110", "07399" };
oracle.sql.ARRAY arr1 = new oracle.sql.ARRAY(
oracle.sql.ArrayDescriptor.createDescriptor("MY_TYPE", conn),
conn, northEastRegion);
PreparedStatement ps = conn.prepareStatement(str);
ps.setArray(1, arr1);
ps.executeQuery();
stmt.execute("drop type my_type");
System.out.println("Type my_type was dropped");
stmt.execute("create type my_type as table of NUMBER(22)");
System.out.println("Type my_type was created");
oracle.sql.ARRAY arr2 = new oracle.sql.ARRAY(
oracle.sql.ArrayDescriptor.createDescriptor("MY_TYPE", conn),
conn, northEastRegion);
ps = conn.prepareStatement(str);
ps.setArray(1, arr2);
ps.executeQuery(); //java.sql.SQLSyntaxErrorException: ORA-00902: invalid datatype
为什么将arr1和arr2视为不同的类型?
据我所知,问题是这些类型有不同的OID
但是在java代码中,我只使用名称来处理这些类型。我不使用OID。
如何处理这个问题?
我们是否应该在更新DB后重新启动Web应用程序?即使DB的结构没有改变
或者我们应该在创建MY_TYPE时指定(硬编码)OID吗?
什么是正确的方法?
更新
在我看来,我在oracle.sql.ArrayDescriptor中找到了答案和一个错误
请参阅下面的答案。
答案 0 :(得分:1)
我认为这里的问题是准备好的语句与数据类型的组合用作行源。 Prepared语句有效地创建和优化执行计划,并在创建执行游标时存储游标的地址,因此下次使用不同的绑定变量执行相同的语句时,只需再次执行现有游标(以避免硬解析和软解析) 。删除对象时,Oracle会使依赖于此对象的所有内容失效,包括游标。如果你发出相同的语句,包括解析(未准备好),它应该产生涉及新数据类型的新执行计划,但是你想要重新执行不再有效的游标。
答案 1 :(得分:1)
oracle.sql.ArrayDescriptor 使用缓存 默认情况下,将从缓存加载相同ArrayDescriptor的第二个实例 它与数据库中的类型不同,因为重新创建了数据库中的类型。
ArrayDescriptor中有一个构造函数,您可以在其中指定是否替换任何缓存的描述符。
static ArrayDescriptor createDescriptor(java.lang.String name, java.sql.Connection conn, boolean recurse, boolean force)
static ArrayDescriptor createDescriptor(SQLName sqlName, java.sql.Connection conn, boolean recurse, boolean force)
但是构造函数中有一个错误,第一个参数是String - 在该构造函数的主体中没有使用force参数。
它仅在具有SQLName的构造函数体中使用
您可以看到代码here的代码。
因此,为了解决我们的问题,我们可以使用带有SQLName的构造函数并将force参数设置为true。在这种情况下,将不使用缓存。
String[] northEastRegion = { "10022", "02110", "07399" };
String str = "select * from MYTESTTABLE where test in (select column_value from TABLE (?) tmp)";
OracleConnection conn = null;
Properties connectionProps = new Properties();
connectionProps.put("user", "user");
connectionProps.put("password", "pwd");
conn = (OracleConnection) DriverManager
.getConnection("connstr", connectionProps);
Statement stmt = conn.createStatement();
stmt.execute("drop table mytesttable");
stmt.execute("create table MYTESTTABLE ( test NUMBER(22))");
stmt.execute("drop type my_type1");
stmt.execute("create type my_type1 as table of NUMBER(22)");
ArrayDescriptor desc1 = oracle.sql.ArrayDescriptor.createDescriptor("MY_TYPE1", conn);
stmt.execute("drop type my_type1");
stmt.execute("create type my_type1 as table of NUMBER(22)");
oracle.sql.SQLName sqlName = new oracle.sql.SQLName("MY_TYPE", conn);
ArrayDescriptor desc2 = oracle.sql.ArrayDescriptor.createDescriptor(sqlName, conn, true, true);
oracle.sql.ARRAY arr2 = new oracle.sql.ARRAY(desc2, conn, northEastRegion);
PreparedStatement ps = conn.prepareStatement(str);
ps.setArray(1, arr2);
ps.executeQuery();