OPENJPA-1691: Support XML strings longer than 4000 bytes on Oracle.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1004043 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Milosz Tylenda 2010-10-03 20:46:38 +00:00
parent 8fa232c933
commit 5a98879567
7 changed files with 97 additions and 21 deletions

View File

@ -355,6 +355,7 @@ public class DBDictionary
protected Log log = null; protected Log log = null;
protected boolean connected = false; protected boolean connected = false;
protected boolean isJDBC3 = false; protected boolean isJDBC3 = false;
protected boolean isJDBC4 = false;
protected final Set<String> reservedWordSet = new HashSet<String>(); protected final Set<String> reservedWordSet = new HashSet<String>();
// reservedWordSet subset that CANNOT be used as valid column names // reservedWordSet subset that CANNOT be used as valid column names
// (i.e., without surrounding them with double-quotes) // (i.e., without surrounding them with double-quotes)
@ -423,9 +424,11 @@ public class DBDictionary
try { try {
metaData = conn.getMetaData(); metaData = conn.getMetaData();
try { try {
// JDBC3-only method, so it might throw a // JDBC3-only method, so it might throw an
// AbstractMethodError // AbstractMethodError
isJDBC3 = metaData.getJDBCMajorVersion() >= 3; int JDBCMajorVersion = metaData.getJDBCMajorVersion();
isJDBC3 = JDBCMajorVersion >= 3;
isJDBC4 = JDBCMajorVersion >= 4;
} catch (Throwable t) { } catch (Throwable t) {
// ignore if not JDBC3 // ignore if not JDBC3
} }
@ -5437,4 +5440,16 @@ public class DBDictionary
} }
return conversionKey; return conversionKey;
} }
/**
* 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 "?";
}
} }

View File

@ -19,6 +19,7 @@
package org.apache.openjpa.jdbc.sql; package org.apache.openjpa.jdbc.sql;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.security.AccessController; import java.security.AccessController;
@ -105,6 +106,11 @@ public class OracleDictionary
*/ */
public boolean useSetFormOfUseForUnicode = true; public boolean useSetFormOfUseForUnicode = true;
/**
* Type constructor for XML column, used in INSERT and UPDATE statements.
*/
public String xmlTypeMarker = "XMLType(?)";
// some oracle drivers have problems with select for update; warn the // some oracle drivers have problems with select for update; warn the
// first time locking is attempted // first time locking is attempted
private boolean _checkedUpdateBug = false; private boolean _checkedUpdateBug = false;
@ -237,9 +243,9 @@ public class OracleDictionary
timestampTypeName = "DATE"; // added oracle 9 timestampTypeName = "DATE"; // added oracle 9
supportsXMLColumn = false; supportsXMLColumn = false;
} }
// select of an xml column requires ".getStringVal()" // select of an xml column requires ".getStringVal()" (for values <= 4000 bytes only)
// suffix. eg. t0.xmlcol.getStringVal() // or ".getClobVal()" suffix. eg. t0.xmlcol.getClobVal()
getStringVal = ".getStringVal()"; getStringVal = ".getClobVal()";
} else if (metadataClassName.startsWith("com.ddtek.") } else if (metadataClassName.startsWith("com.ddtek.")
|| url.indexOf("jdbc:datadirect:oracle:") != -1 || url.indexOf("jdbc:datadirect:oracle:") != -1
|| "Oracle".equals(driverName)) { || "Oracle".equals(driverName)) {
@ -249,6 +255,7 @@ public class OracleDictionary
driverVendor = VENDOR_OTHER; driverVendor = VENDOR_OTHER;
} }
cacheDriverBehavior(driverVendor); cacheDriverBehavior(driverVendor);
guessJDBCVersion(conn);
} }
/** /**
@ -560,6 +567,16 @@ public class OracleDictionary
public void setClobString(PreparedStatement stmnt, int idx, String val, public void setClobString(PreparedStatement stmnt, int idx, String val,
Column col) Column col)
throws SQLException { throws SQLException {
if (col.isXML()) {
if (isJDBC4) {
// This JDBC 4 method handles values longer than 4000 bytes.
stmnt.setClob(idx, new StringReader(val), val.length());
} else {
// This method is limited to 4000 bytes.
setCharacterStream(stmnt, idx, new StringReader(val), val.length(), col);
}
return;
}
if (!useSetStringForClobs && val.length() == 0) if (!useSetStringForClobs && val.length() == 0)
stmnt.setClob(idx, getEmptyClob()); stmnt.setClob(idx, getEmptyClob());
else { else {
@ -1310,4 +1327,36 @@ public class OracleDictionary
row.setBlob(col, getEmptyBlob()); row.setBlob(col, getEmptyBlob());
} }
} }
/**
* 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);
}
/**
* Oracle drivers, at least in versions 10.2.0.4 and 11.2.0.1, incorrectly return a driver major version from
* {@link DatabaseMetaData#getJDBCMajorVersion()}.
*/
protected void guessJDBCVersion(Connection conn) {
if (_driverBehavior != BEHAVE_ORACLE) {
return;
}
isJDBC4 = true;
try {
conn.getClientInfo(); // Try to call a JDBC 4 method.
} catch (SQLException e) {
// OK, we are on JDBC 4.
} catch (Throwable t) {
// Most likely an AbstractMethodError from JDBC 3 driver.
isJDBC4 = false;
}
}
} }

View File

@ -758,8 +758,10 @@ public class RowImpl
buf.append(dict.getColumnDBName(_cols[i])); buf.append(dict.getColumnDBName(_cols[i]));
if (_types[i] == RAW) if (_types[i] == RAW)
buf.append(" = ").append(_vals[i]); buf.append(" = ").append(_vals[i]);
else else {
buf.append(" = ?"); buf.append(" = ");
buf.append(dict.getMarkerForInsertUpdate(_cols[i], _vals[i]));
}
hasVal = true; hasVal = true;
} }
@ -789,7 +791,7 @@ public class RowImpl
if (_types[i] == RAW) if (_types[i] == RAW)
vals.append(_vals[i]); vals.append(_vals[i]);
else else
vals.append("?"); vals.append(dict.getMarkerForInsertUpdate(_cols[i], _vals[i]));
hasVal = true; hasVal = true;
} }

View File

@ -26,6 +26,7 @@ import javax.persistence.Query;
import junit.textui.TestRunner; import junit.textui.TestRunner;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration; import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.jdbc.sql.DBDictionary; import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI; import org.apache.openjpa.persistence.OpenJPAEntityManagerFactorySPI;
@ -64,9 +65,13 @@ public class TestXMLCustomerOrder
private static final double ORDER_2_AMOUNT = 1000; private static final double ORDER_2_AMOUNT = 1000;
private static final boolean ORDER_2_DELIVERED = false; private static final boolean ORDER_2_DELIVERED = false;
private static boolean firstTestExecuted;
public void setUp() { public void setUp() {
Object clearOrDropTables = (firstTestExecuted) ? CLEAR_TABLES : DROP_TABLES;
firstTestExecuted = true;
setUp(Customer.class, Customer.CustomerKey.class, Order.class, setUp(Customer.class, Customer.CustomerKey.class, Order.class,
EAddress.class, DROP_TABLES); // test create table DDL for XML column EAddress.class, clearOrDropTables); // test create table DDL for XML column but only once to save time.
// skip test if dictionary has no support for XML column type // skip test if dictionary has no support for XML column type
setTestsDisabled(!dictionarySupportsXMLColumn()); setTestsDisabled(!dictionarySupportsXMLColumn());
@ -347,10 +352,11 @@ public class TestXMLCustomerOrder
private USAAddress createUSAAddress(String name) { private USAAddress createUSAAddress(String name) {
USAAddress address = new ObjectFactory().createUSAAddress(); USAAddress address = new ObjectFactory().createUSAAddress();
address.setName(name); address.setName(name);
address.getStreet().add("12500 Monterey"); // Use a 4000-byte value so the entire XML string is longer than 4000 bytes - ensure Oracle handles this.
address.getStreet().add(StringUtils.repeat("12500 Mont", 400));
address.setCity("San Jose"); address.setCity("San Jose");
address.setState("CA"); address.setState("CA");
address.setZIP(new Integer("95141")); address.setZIP(95141);
return address; return address;
} }

View File

@ -1490,8 +1490,8 @@ to <literal>"FOR UPDATE"</literal>.
<literal>GetStringVal</literal>: <literal>GetStringVal</literal>:
A special function to return the value of an XML A special function to return the value of an XML
column in a select statement. For example, Oracle uses column in a select statement. For example, Oracle uses
<literal>".getStringVal()"</literal>, as in, <literal>".getClobVal()"</literal>, as in
<literal>"select t0.xmlcol.getStringVal() from xmltab t0"</literal>. <literal>"SELECT t0.xmlcol.getClobVal() FROM xmltab t0"</literal>.
Defaults to the empty string. Defaults to the empty string.
</para> </para>
</listitem> </listitem>
@ -1505,14 +1505,6 @@ Defaults to the empty string.
InClauseLimit InClauseLimit
</secondary> </secondary>
</indexterm> </indexterm>
<indexterm>
<primary>
JDBC
</primary>
<secondary>
GetStringVal
</secondary>
</indexterm>
<literal>InClauseLimit</literal>: <literal>InClauseLimit</literal>:
The maximum number of elements in an <literal>IN</literal> clause. OpenJPA The maximum number of elements in an <literal>IN</literal> clause. OpenJPA
works around cases where the limit is exceeded. Defaults to -1 meaning works around cases where the limit is exceeded. Defaults to -1 meaning

View File

@ -2633,6 +2633,10 @@ SQL Server 2005
</listitem> </listitem>
</itemizedlist> </itemizedlist>
<para> <para>
See <xref linkend="supported_databases"/> for possible database-specific
restrictions.
</para>
<para>
Annotate the entity property using the XMLValueHandler strategy: Annotate the entity property using the XMLValueHandler strategy:
</para> </para>
<programlisting> <programlisting>

View File

@ -1206,6 +1206,14 @@ openjpa.ConnectionRetainMode: always
</example> </example>
</para> </para>
</listitem> </listitem>
<listitem>
<para>
Mapping persistent attributes to <link linkend="ref_guide_xmlmapping">XML columns</link> requires
a JDBC 4 compliant driver if XML strings are longer than 4000 bytes, as counted in database.
Otherwise an <literal>ORA-01461: can bind a LONG value only for insert into a LONG column</literal>
error may result.
</para>
</listitem>
</itemizedlist> </itemizedlist>
</section> </section>
</section> </section>