You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by mi...@apache.org on 2011/08/02 00:11:25 UTC
svn commit: r1152951 - in /openjpa/branches/1.2.x:
openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/
openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/
openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/
openjpa-project/src/doc/manual/
Author: mikedd
Date: Mon Aug 1 22:11:24 2011
New Revision: 1152951
URL: http://svn.apache.org/viewvc?rev=1152951&view=rev
Log:
OPENJPA-1691: Oracle XMLType column failed to insert/update when xml contains more than 4000 characters - applied Jeremy's 1.2.x patch and in addition I added documentation.
Modified:
openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java
openjpa/branches/1.2.x/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/localizer.properties
openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
openjpa/branches/1.2.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
Modified: openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java (original)
+++ openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java Mon Aug 1 22:11:24 2011
@@ -4568,4 +4568,16 @@ public class DBDictionary
public void deleteStream(JDBCStore store, Select sel) throws SQLException {
// Do nothing
}
+
+ /**
+ * Return parameter marker for INSERT and UPDATE statements.
+ * Usually it is <code>?</code> but some database-specific types might require customization.
+ *
+ * @param col column definition
+ * @param val value to be inserted/updated
+ * @return parameter marker
+ */
+ public String getMarkerForInsertUpdate(Column col, Object val) {
+ return "?";
+ }
}
Modified: openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java (original)
+++ openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/OracleDictionary.java Mon Aug 1 22:11:24 2011
@@ -18,6 +18,8 @@
*/
package org.apache.openjpa.jdbc.sql;
+import java.io.Reader;
+import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
@@ -46,8 +48,10 @@ import org.apache.openjpa.jdbc.schema.In
import org.apache.openjpa.jdbc.schema.PrimaryKey;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.jdbc.schema.Table;
+import org.apache.openjpa.lib.jdbc.DelegatingConnection;
import org.apache.openjpa.lib.jdbc.DelegatingDatabaseMetaData;
import org.apache.openjpa.lib.jdbc.DelegatingPreparedStatement;
+import org.apache.openjpa.lib.jdbc.LoggingConnectionDecorator;
import org.apache.openjpa.lib.util.J2DoPrivHelper;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.util.StoreException;
@@ -71,6 +75,14 @@ public class OracleDictionary
private static Blob EMPTY_BLOB = null;
private static Clob EMPTY_CLOB = null;
+ private Method _setClob = null;
+ private Boolean _isJDBC4 = null;
+
+ /**
+ * Type constructor for XML column, used in INSERT and UPDATE statements.
+ */
+ public String xmlTypeMarker = "XMLType(?)";
+
private static final Localizer _loc = Localizer.forPackage
(OracleDictionary.class);
@@ -99,6 +111,14 @@ public class OracleDictionary
*/
public boolean useSetFormOfUseForUnicode = true;
+ /**
+ * If true, OpenJPA will attempt to use a Reader-based JDBC 4.0 method to
+ * set Clob or XML data. This allows XMLType and Clob values larger than 4000 bytes
+ * to be used if OpenJPA is used with a Java 6.0 JRE along with a JDBC 4.0 Oracle
+ * driver (ojdbc6.jar).
+ */
+ public boolean supportsSetClob = false;
+
// some oracle drivers have problems with select for update; warn the
// first time locking is attempted
private boolean _checkedUpdateBug = false;
@@ -212,7 +232,11 @@ public class OracleDictionary
}
// select of an xml column requires ".getStringVal()"
// suffix. eg. t0.xmlcol.getStringVal()
+ if (!supportsSetClob) {
getStringVal = ".getStringVal()";
+ } else {
+ getStringVal = ".getClobVal()";
+ }
} else if (metadataClassName.startsWith("com.ddtek.")
|| url.indexOf("jdbc:datadirect:oracle:") != -1
|| "Oracle".equals(driverName)) {
@@ -525,6 +549,75 @@ public class OracleDictionary
super.setNull(stmnt, idx, colType, col);
}
+ @Override
+ public void setClobString(PreparedStatement stmnt, int idx, String val,
+ Column col)
+ throws SQLException {
+ if (!useSetStringForClobs && supportsSetClob) {
+ // If the JRE and underlying driver support JDBC, reflectively call
+ // the JDBC setClob method which supports streams larger than 4000 bytes.
+ if (val != null && supportsJDBC4(stmnt.getConnection())) {
+ if (setClob(stmnt, idx, val)) {
+ return;
+ }
+ }
+ }
+ super.setClobString(stmnt, idx, val, col);
+ }
+
+ /**
+ * This method reflectively calls the JDBC 4 setClob method which takes a
+ * reader as a parameter. This allows support for clobs and XML values larger than 4000 bytes.
+ * @param stmnt
+ * @param idx
+ * @param val
+ * @return
+ */
+ private boolean setClob(PreparedStatement stmnt, int idx, String val) {
+ if (_setClob == null) {
+ try {
+ _setClob = PreparedStatement.class.getMethod(
+ "setClob", int.class, Reader.class, long.class);
+ if (_setClob == null) {
+ return false;
+ }
+ } catch (Throwable t) {
+ log.warn(_loc.get("oracle-set-clob-unsupported"), t);
+ return false;
+ }
+ }
+ try {
+ PreparedStatement inner = stmnt;
+ PreparedStatement outer = stmnt;
+ if (stmnt instanceof DelegatingPreparedStatement) {
+ inner = (PreparedStatement) ((DelegatingPreparedStatement)stmnt).getInnermostDelegate();
+ outer = (PreparedStatement) ((DelegatingPreparedStatement)stmnt).getDelegate();
+ }
+ _setClob.invoke(inner, new Object[] { idx, new StringReader(val), (long)val.length() } );
+ // Direct invocation of setClob on the Oracle driver bypasses OpenJPA's built-in
+ // parameter logging via decorators. Log the parameter directly.
+ LoggingConnectionDecorator.logStatementParameter(outer, idx, "Clob", val);
+ } catch (Throwable t) {
+ log.warn(_loc.get("oracle-set-clob-failed"), t);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Oracle requires special handling of XML column.
+ * Unless the value length is less or equal to 4000 bytes,
+ * the parameter marker must be decorated with type constructor.
+ */
+ @Override
+ public String getMarkerForInsertUpdate(Column col, Object val) {
+ if (col.isXML() && val != RowImpl.NULL) {
+ return xmlTypeMarker;
+ }
+ return super.getMarkerForInsertUpdate(col, val);
+ }
+
public String getClobString(ResultSet rs, int column)
throws SQLException {
if (_driverBehavior != BEHAVE_ORACLE)
@@ -1125,4 +1218,37 @@ public class OracleDictionary
}
return updateSuccessCnt;
}
+
+ /**
+ * Attempts to reflectively call a local JDBC4 method to determine whether
+ * JDBC 4.0 is supported in the runtime environment. This requires JDK 1.6,
+ * DBCP 1.4, and an Oracle JDK 1.6/JDBC 4.0 driver (ojdbc6.jar).
+ */
+ protected boolean supportsJDBC4(Connection conn) {
+ if (_isJDBC4 != null) {
+ return _isJDBC4;
+ }
+ if (_driverBehavior != BEHAVE_ORACLE) {
+ _isJDBC4 = false;
+ return _isJDBC4;
+ }
+ try {
+ Connection inner = conn;
+ if (conn instanceof DelegatingConnection) {
+ inner = ((DelegatingConnection)conn).getInnermostDelegate();
+ }
+ Method getClientInfo = Connection.class.getMethod(
+ "getClientInfo", new Class[0]);
+ getClientInfo.invoke(inner, new Object[0]);
+ _isJDBC4 = true;
+ } catch (Throwable t) {
+ if (t instanceof SQLException) {
+ // OK, we are on JDBC 4.
+ _isJDBC4 = true;
+ } else {
+ _isJDBC4 = false;
+ }
+ }
+ return _isJDBC4;
+ }
}
Modified: openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java (original)
+++ openjpa/branches/1.2.x/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/RowImpl.java Mon Aug 1 22:11:24 2011
@@ -754,8 +754,10 @@ public class RowImpl
buf.append(_cols[i]);
if (_types[i] == RAW)
buf.append(" = ").append(_vals[i]);
- else
- buf.append(" = ?");
+ else {
+ buf.append(" = ");
+ buf.append(dict.getMarkerForInsertUpdate(_cols[i], _vals[i]));
+ }
hasVal = true;
}
@@ -785,7 +787,7 @@ public class RowImpl
if (_types[i] == RAW)
vals.append(_vals[i]);
else
- vals.append("?");
+ vals.append(dict.getMarkerForInsertUpdate(_cols[i], _vals[i]));
hasVal = true;
}
Modified: openjpa/branches/1.2.x/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/localizer.properties
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/localizer.properties?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/localizer.properties (original)
+++ openjpa/branches/1.2.x/openjpa-jdbc/src/main/resources/org/apache/openjpa/jdbc/sql/localizer.properties Mon Aug 1 22:11:24 2011
@@ -190,4 +190,9 @@ sequencesql-override: Going to override
the property. This will allow openJPA to detect a difference between the DB2 default \
string and the string set in the property and will further allow openJPA to use the \
string defined by the property rather than the default string for DB2.
-
+oracle-set-clob-unsupported: The JRE or JDBC level in use does not support the function \
+ necessary to support storing large Clob or XML values. An alternate method will be used \
+ to store the data, but this may result in failure if the data is larger than 4000 bytes.
+oracle-set-clob-failed: Invocation of methods to support storing large Clob or XML values \
+ resulted in an error. An alternate method will be used to store the data, but this may \
+ result in failure if the data is larger than 4000 bytes.
Modified: openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java (original)
+++ openjpa/branches/1.2.x/openjpa-lib/src/main/java/org/apache/openjpa/lib/jdbc/LoggingConnectionDecorator.java Mon Aug 1 22:11:24 2011
@@ -241,6 +241,16 @@ public class LoggingConnectionDecorator
}
+ /* Allows direct recording of a statement parameter for logging purposes,
+ * provided the prepared statement implements LoggingPreparedStatement.
+ */
+ public static void logStatementParameter(PreparedStatement stmnt, int index, String type, Object val) {
+ if (stmnt instanceof LoggingConnection.LoggingPreparedStatement) {
+ LoggingConnection.LoggingPreparedStatement lcstmnt = (LoggingConnection.LoggingPreparedStatement) stmnt;
+ lcstmnt.setLogParameter(index, type, val);
+ }
+ }
+
/**
* Interface that allows customization of what to do when
* {@link SQLWarning}s occur.
@@ -253,7 +263,7 @@ public class LoggingConnectionDecorator
/**
* Logging connection.
*/
- private class LoggingConnection extends DelegatingConnection {
+ protected class LoggingConnection extends DelegatingConnection {
public LoggingConnection(Connection conn) throws SQLException {
super(conn);
@@ -821,7 +831,7 @@ public class LoggingConnectionDecorator
}
}
- private class LoggingPreparedStatement
+ protected class LoggingPreparedStatement
extends DelegatingPreparedStatement {
private final String _sql;
Modified: openjpa/branches/1.2.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml
URL: http://svn.apache.org/viewvc/openjpa/branches/1.2.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml?rev=1152951&r1=1152950&r2=1152951&view=diff
==============================================================================
--- openjpa/branches/1.2.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml (original)
+++ openjpa/branches/1.2.x/openjpa-project/src/doc/manual/ref_guide_dbsetup.xml Mon Aug 1 22:11:24 2011
@@ -3373,6 +3373,35 @@ on database configuration, e.g. encoding
CLOB to persist with the embedded method. Defaults to 4000 characters.
</para>
</listitem>
+ <listitem id="OracleDictionary.SupportsSetClob">
+ <para>
+<literal>SupportsSetClob</literal>: If true, OpenJPA will attempt to use a
+Reader-based JDBC 4.0 method to set Clob or XML data. This allows XMLType
+and Clob values larger than 4000 bytes to be used if OpenJPA is used
+with a Java 6.0 JRE along with a JDBC 4.0 Oracle driver (e.g. ojdbc6.jar).
+It is expected that this property will be used in conjunction with
+<literal>XMLValueHandler</literal>, see <xref linkend="ref_guide_xmlmapping"/>.
+That is, it is expected that the XML field in question will be annotated with
+the <literal>XMLValueHandler</literal> annotation. This allows a user to
+indicate that OpenJPA should use JAXB class metadata to perform the to/from
+database value retrieval and storage. In addition, using this strategy
+indicates there is an XMLType used to store the value in the database. However,
+it is possible for a user to use the <literal>SupportsSetClob</literal> and
+managing their own XML string, mapped to a string field. In this case,
+additional actions need to be taken. First, the XML field will need to be
+annotated with the <literal>Lob</literal> annotation. Second, the
+<literal>MaxEmbeddedClobSize</literal> dictionary property will need to be
+set to -1. Finally, a user may need to set the
+<literal>openjpa.jdbc.SchemaFactory</literal> property to <literal>native</literal>,
+see <xref linkend="ref_guide_schema_info_factory"/>.
+This property is necessary to allow OpenJPA to detect the XML column type. While
+a user may have an XMLType specified in the column definition, OpenJPA cannot count
+on this data to detect whether the column is really an XML column in the
+database. (The table could have been created manually with separate DDL instead
+of using OpenJPA's mapping tool). By enabling OpenJPA's native schema factory
+the database column type can be detected as XMLType.
+ </para>
+ </listitem>
<listitem id="OracleDictionary.UseSetFormOfUseForUnicode">
<para>
<literal>UseSetFormOfUseForUnicode</literal>: Prior to Oracle 10i, statements