You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by ht...@apache.org on 2011/07/08 22:37:11 UTC

svn commit: r1144498 - 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: hthomann
Date: Fri Jul  8 20:37:11 2011
New Revision: 1144498

URL: http://svn.apache.org/viewvc?rev=1144498&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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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=1144498&r1=1144497&r2=1144498&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 Fri Jul  8 20:37:11 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