OPENJPA-182. Moved to API-based model. Query.setHint() can still be used via the query hint => fetch plan binding.

Removed the logic to override the forUpdate value, since the calling code already incorporates fetch configuration data into its decision about how to invoke toSelect(). Added a test case to assert this behavior.

Also cleaned up some minor whitespace issues, and reduced code duplication by moving a couple of concepts up into DBDictionary. Removed some seemingly-unnecessary overrides from H2Dictionary.

Added a test case for isolation level configuration. For non-DB2 dictionaries, it asserts that an exception is thrown during execution. Someone with DB2 knowledge / access should fill in the test case for the DB2 cases. As we add support for per-query isolation level configuration for other databases, we should change this test case.

git-svn-id: https://svn.apache.org/repos/asf/incubator/openjpa/trunk@525997 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Patrick Linskey 2007-04-05 22:55:52 +00:00
parent e541936463
commit 814637ca6c
13 changed files with 365 additions and 180 deletions

View File

@ -240,4 +240,21 @@ public class DelegatingJDBCFetchConfiguration
throw translate(re);
}
}
public int getIsolationLevel() {
try {
return getJDBCDelegate().getIsolationLevel();
} catch (RuntimeException re) {
throw translate(re);
}
}
public JDBCFetchConfiguration setIsolationLevel(int level) {
try {
getJDBCDelegate().setIsolationLevel(level);
return this;
} catch (RuntimeException re) {
throw translate(re);
}
}
}

View File

@ -16,6 +16,7 @@
package org.apache.openjpa.jdbc.kernel;
import java.sql.ResultSet;
import java.sql.Connection;
import java.util.Collection;
import java.util.Set;
@ -169,4 +170,38 @@ public interface JDBCFetchConfiguration
* Convenience method to cast traversal to store-specific type.
*/
public JDBCFetchConfiguration traverseJDBC(FieldMetaData fm);
/**
* <p>The isolation level for queries issued to the database. This overrides
* the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code>
* value.</p>
*
* <p>Must be one of {@link Connection#TRANSACTION_NONE},
* {@link Connection#TRANSACTION_READ_UNCOMMITTED},
* {@link Connection#TRANSACTION_READ_COMMITTED},
* {@link Connection#TRANSACTION_REPEATABLE_READ},
* {@link Connection#TRANSACTION_SERIALIZABLE},
* or -1 for the default connection level specified by the context in
* which this fetch configuration is being used.</p>
*
* @since 0.9.7
*/
public int getIsolationLevel();
/**
* <p>The isolation level for queries issued to the database. This overrides
* the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code>
* value.</p>
*
* <p>Must be one of {@link Connection#TRANSACTION_NONE},
* {@link Connection#TRANSACTION_READ_UNCOMMITTED},
* {@link Connection#TRANSACTION_READ_COMMITTED},
* {@link Connection#TRANSACTION_REPEATABLE_READ},
* {@link Connection#TRANSACTION_SERIALIZABLE},
* or -1 for the default connection level specified by the context in
* which this fetch configuration is being used.</p>
*
* @since 0.9.7
*/
public JDBCFetchConfiguration setIsolationLevel(int level);
}

View File

@ -17,6 +17,7 @@ package org.apache.openjpa.jdbc.kernel;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.Connection;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
@ -65,6 +66,7 @@ public class JDBCFetchConfigurationImpl
public int size = 0;
public int syntax = 0;
public Set joins = null;
public int isolationLevel = -1;
}
private final JDBCConfigurationState _state;
@ -319,4 +321,22 @@ public class JDBCFetchConfigurationImpl
return null;
return (JDBCConfiguration) conf;
}
public int getIsolationLevel() {
return _state.isolationLevel;
}
public JDBCFetchConfiguration setIsolationLevel(int level) {
if (level != -1
&& level != Connection.TRANSACTION_NONE
&& level != Connection.TRANSACTION_READ_UNCOMMITTED
&& level != Connection.TRANSACTION_READ_COMMITTED
&& level != Connection.TRANSACTION_REPEATABLE_READ
&& level != Connection.TRANSACTION_SERIALIZABLE)
throw new IllegalArgumentException(
_loc.get("bad-level", Integer.valueOf(level)).getMessage());
_state.isolationLevel = level;
return this;
}
}

View File

@ -23,8 +23,8 @@ import java.util.Arrays;
import java.util.StringTokenizer;
import org.apache.openjpa.jdbc.kernel.JDBCFetchConfiguration;
import org.apache.openjpa.jdbc.schema.Sequence;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.util.OpenJPAException;
import org.apache.openjpa.kernel.LockLevels;
/**
* Dictionary for IBM DB2 database.
@ -43,11 +43,12 @@ public class DB2Dictionary
private static final String forUpdateOfClause = "FOR UPDATE OF";
private static final String withRSClause = "WITH RS";
private static final String withRRClause = "WITH RR";
private static final String useKeepUpdateLockClause= "USE AND KEEP UPDATE LOCKS";
private static final String useKeepExclusiveLockClause="USE AND KEEP EXCLUSIVE LOCKS";
private static final String useKeepUpdateLockClause
= "USE AND KEEP UPDATE LOCKS";
private static final String useKeepExclusiveLockClause
= "USE AND KEEP EXCLUSIVE LOCKS";
private static final String forReadOnlyClause = "FOR READ ONLY";
public static final String UPDATE_HINT = "openjpa.hint.updateClause";
public static final String ISOLATION_HINT = "openjpa.hint.isolationLevel";
public DB2Dictionary() {
platform = "DB2";
validationSQL = "SELECT DISTINCT(CURRENT TIMESTAMP) FROM "
@ -226,66 +227,64 @@ public class DB2Dictionary
}
}
/** Get the update clause for the query based on the
/**
* Get the update clause for the query based on the
* updateClause and isolationLevel hints
*/
public String getForUpdateClause(JDBCFetchConfiguration fetch, boolean forUpdate) {
String isolationLevel = null;
boolean updateClause;
DatabaseMetaData metaData = null;
protected String getForUpdateClause(JDBCFetchConfiguration fetch,
boolean forUpdate) {
int isolationLevel;
StringBuffer forUpdateString = new StringBuffer();
try {
// Determine the update clause/isolationLevel the hint
// overrides the persistence.xml value
if (fetch != null && fetch.getHint(UPDATE_HINT)
!=null )
updateClause = ((Boolean)fetch.
getHint(UPDATE_HINT)).booleanValue();
// Determine the isolationLevel; the fetch
// configuration data overrides the persistence.xml value
if (fetch != null && fetch.getIsolationLevel() != -1)
isolationLevel = fetch.getIsolationLevel();
else
updateClause = forUpdate;
if (fetch != null &&fetch.getHint(ISOLATION_HINT)
!=null )
isolationLevel = (String)fetch.
getHint(ISOLATION_HINT);
else
isolationLevel = conf.getTransactionIsolation();
if (updateClause == false)
isolationLevel = conf.getTransactionIsolationConstant();
if (!forUpdate) {
// This sql is not for update so add FOR Read Only clause
forUpdateString.append(" ").append(forReadOnlyClause)
.append(" ");
else if (updateClause == true){
} else {
switch(db2ServerType) {
case db2ISeriesV5R3AndEarlier:
case db2UDBV81OrEarlier:
if (isolationLevel.equals("read-uncommitted"))
if (isolationLevel ==
Connection.TRANSACTION_READ_UNCOMMITTED) {
forUpdateString.append(" ").append(withRSClause)
.append(" ").append(forUpdateOfClause).append(" ");
else
} else {
forUpdateString.append(" ").append(forUpdateOfClause)
.append(" ");
}
break;
case db2ZOSV8x:
case db2UDBV82AndLater:
if (isolationLevel.equals("serializable"))
if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE) {
forUpdateString.append(" ").append(withRRClause)
.append(" ").append(useKeepUpdateLockClause)
.append(" ");
else
} else {
forUpdateString.append(" ").append(withRSClause)
.append(" ").append(useKeepUpdateLockClause)
.append(" ");
}
break;
case db2ISeriesV5R4AndLater:
if (isolationLevel.equals("serializable"))
if (isolationLevel == Connection.TRANSACTION_SERIALIZABLE) {
forUpdateString.append(" ").append(withRRClause)
.append(" ").append(useKeepExclusiveLockClause)
.append(" ");
else
} else {
forUpdateString.append(" ").append(withRSClause)
.append(" ").append(useKeepExclusiveLockClause)
.append(" ");
}
break;
}
}
}
catch (Exception e) {
@ -359,71 +358,16 @@ public class DB2Dictionary
return i;
}
/**
* Override the toOperationMethod of DBDictionary to pass the
* forUpdateString.
*/
protected SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, boolean forUpdate, long start,
long end,String forUpdateString) {
SQLBuffer buf = new SQLBuffer(this);
buf.append(op);
boolean range = start != 0 || end != Long.MAX_VALUE;
if (range && rangePosition == RANGE_PRE_DISTINCT)
appendSelectRange(buf, start, end);
if (distinct)
buf.append(" DISTINCT");
if (range && rangePosition == RANGE_POST_DISTINCT)
appendSelectRange(buf, start, end);
buf.append(" ").append(selects).append(" FROM ").append(from);
if (where != null && !where.isEmpty())
buf.append(" WHERE ").append(where);
if (group != null && !group.isEmpty())
buf.append(" GROUP BY ").append(group);
if (having != null && !having.isEmpty()) {
assertSupport(supportsHaving, "SupportsHaving");
buf.append(" HAVING ").append(having);
}
if (order != null && !order.isEmpty())
buf.append(" ORDER BY ").append(order);
if (range && rangePosition == RANGE_POST_SELECT)
appendSelectRange(buf, start, end);
if (!simulateLocking ) {
assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate");
buf.append(" ").append(forUpdateString);
}
if (range && rangePosition == RANGE_POST_LOCK)
appendSelectRange(buf, start, end);
return buf;
}
public SQLBuffer toSelect(Select sel, boolean forUpdate,
JDBCFetchConfiguration fetch) {
sel.addJoinClassConditions();
boolean update = forUpdate && sel.getFromSelect() == null;
SQLBuffer select = getSelects(sel, false, update);
SQLBuffer ordering = null;
if (!sel.isAggregate() || sel.getGrouping() != null)
ordering = sel.getOrdering();
SQLBuffer from;
if (sel.getFromSelect() != null)
from = getFromSelect(sel, forUpdate);
else
from = getFrom(sel, update);
SQLBuffer where = getWhere(sel, update);
String forUpdateString = getForUpdateClause(fetch,forUpdate);
SQLBuffer buf = toOperation(getSelectOperation(fetch), select,
from, where,sel.getGrouping(), sel.getHaving(), ordering,
sel.isDistinct(), forUpdate, sel.getStartIndex(),
sel.getEndIndex(),forUpdateString);
if (sel.getExpectedResultCount() > 0)
buf.append(" ").append(optimizeClause).append(" ").
append(String.valueOf(sel.getExpectedResultCount())).
append(" ").append(rowClause);
SQLBuffer buf = super.toSelect(sel, forUpdate, fetch);
if (sel.getExpectedResultCount() > 0) {
buf.append(" ").append(optimizeClause).append(" ")
.append(String.valueOf(sel.getExpectedResultCount()))
.append(" ").append(rowClause);
}
return buf;
}

View File

@ -2145,7 +2145,22 @@ public class DBDictionary
SQLBuffer having, SQLBuffer order,
boolean distinct, boolean forUpdate, long start, long end) {
return toOperation(getSelectOperation(fetch), selects, from, where,
group, having, order, distinct, forUpdate, start, end);
group, having, order, distinct, forUpdate, start, end,
getForUpdateClause(fetch, forUpdate));
}
/**
* Get the update clause for the query based on the
* updateClause and isolationLevel hints
*/
protected String getForUpdateClause(JDBCFetchConfiguration fetch,
boolean forUpdate) {
if (fetch.getIsolationLevel() != -1)
throw new IllegalStateException(_loc.get(
"isolation-level-config-not-supported", getClass().getName())
.getMessage());
else
return forUpdateClause;
}
/**
@ -2161,7 +2176,7 @@ public class DBDictionary
protected SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, boolean forUpdate, long start,
long end) {
long end, String forUpdateClause) {
SQLBuffer buf = new SQLBuffer(this);
buf.append(op);
@ -2190,8 +2205,8 @@ public class DBDictionary
if (forUpdate && !simulateLocking) {
assertSupport(supportsSelectForUpdate, "SupportsSelectForUpdate");
if (forUpdateClause != null)
buf.append(" ").append(forUpdateClause);
if (this.forUpdateClause != null)
buf.append(" ").append(this.forUpdateClause);
}
if (range && rangePosition == RANGE_POST_LOCK)
appendSelectRange(buf, start, end);

View File

@ -159,14 +159,6 @@ public class H2Dictionary extends DBDictionary {
return buf.toString();
}
protected SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, boolean forUpdate, long start,
long end) {
return super.toOperation(op, selects, from, where, group, having,
order, distinct, forUpdate, start, end);
}
public Column[] getColumns(DatabaseMetaData meta, String catalog,
String schemaName, String tableName, String columnName, Connection conn)
throws SQLException {
@ -175,18 +167,6 @@ public class H2Dictionary extends DBDictionary {
return cols;
}
public void setDouble(PreparedStatement stmnt, int idx, double val,
Column col)
throws SQLException {
super.setDouble(stmnt, idx, val, col);
}
public void setBigDecimal(PreparedStatement stmnt, int idx, BigDecimal val,
Column col)
throws SQLException {
super.setBigDecimal(stmnt, idx, val, col);
}
protected void appendSelectRange(SQLBuffer buf, long start, long end) {
if (end != Long.MAX_VALUE)
buf.append(" LIMIT ").appendValue(end - start);

View File

@ -193,13 +193,13 @@ public class HSQLDictionary
protected SQLBuffer toOperation(String op, SQLBuffer selects,
SQLBuffer from, SQLBuffer where, SQLBuffer group, SQLBuffer having,
SQLBuffer order, boolean distinct, boolean forUpdate, long start,
long end) {
long end, String forUpdateClause) {
// hsql requires ordering when limit is used
if ((start != 0 || end != Long.MAX_VALUE)
&& (order == null || order.isEmpty()))
order = _oneBuffer;
return super.toOperation(op, selects, from, where, group, having,
order, distinct, forUpdate, start, end);
order, distinct, forUpdate, start, end, forUpdateClause);
}
public Column[] getColumns(DatabaseMetaData meta, String catalog,

View File

@ -97,3 +97,8 @@ clstable-seq-usage: Usage: \
native-seq-usage: Usage: java org.apache.openjpa.jdbc.kernel.NativeJDBCSeq\n\
\t[-properties/-p <properties file or resource>]\n\
\t[-<property name> <property value>]*
bad-level: Invalid isolation level. Valid levels are -1, \
Connection.TRANSACTION_NONE, Connection.TRANSACTION_READ_UNCOMMITTED, \
Connection.TRANSACTION_READ_COMMITTED, \
Connection.TRANSACTION_REPEATABLE_READ, or \
Connection.TRANSACTION_SERIALIZABLE. Specified value: {0}.

View File

@ -160,3 +160,5 @@ oracle-timestamp-bug: An ArrayIndexOutOfBoundsException has occured when \
less than 9.2. Downgrading the driver will solve this, or it can be \
worked around by setting the "SupportsTimestampNanos" DBDictionary \
property to "true".
isolation-level-config-not-supported: This DBDictionary does not support \
customization of isolation levels on a per-query basis. DBDictionary: {0}.

View File

@ -15,6 +15,8 @@
*/
package org.apache.openjpa.persistence.jdbc;
import java.sql.Connection;
import org.apache.openjpa.jdbc.kernel.EagerFetchModes;
import org.apache.openjpa.jdbc.kernel.LRSSizes;
import org.apache.openjpa.jdbc.sql.JoinSyntaxes;
@ -114,4 +116,38 @@ public interface JDBCFetchPlan
* @see JoinSyntaxes
*/
public JDBCFetchPlan setJoinSyntax(int syntax);
/**
* <p>The isolation level for queries issued to the database. This overrides
* the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code>
* value.</p>
*
* <p>Must be one of {@link Connection#TRANSACTION_NONE},
* {@link Connection#TRANSACTION_READ_UNCOMMITTED},
* {@link Connection#TRANSACTION_READ_COMMITTED},
* {@link Connection#TRANSACTION_REPEATABLE_READ},
* {@link Connection#TRANSACTION_SERIALIZABLE},
* or -1 for the default connection level specified by the context in
* which this fetch plan is being used.</p>
*
* @since 0.9.7
*/
public int getIsolationLevel();
/**
* <p>The isolation level for queries issued to the database. This overrides
* the persistence-unit-wide <code>openjpa.jdbc.TransactionIsolation</code>
* value.</p>
*
* <p>Must be one of {@link Connection#TRANSACTION_NONE},
* {@link Connection#TRANSACTION_READ_UNCOMMITTED},
* {@link Connection#TRANSACTION_READ_COMMITTED},
* {@link Connection#TRANSACTION_REPEATABLE_READ},
* {@link Connection#TRANSACTION_SERIALIZABLE},
* or -1 for the default connection level specified by the context in
* which this fetch plan is being used.</p>
*
* @since 0.9.7
*/
public JDBCFetchPlan setIsolationLevel(int level);
}

View File

@ -102,4 +102,13 @@ public class JDBCFetchPlanImpl
_fetch.setJoinSyntax(syntax);
return this;
}
public int getIsolationLevel() {
return _fetch.getIsolationLevel();
}
public JDBCFetchPlan setIsolationLevel(int level) {
_fetch.setIsolationLevel(level);
return this;
}
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.persistence.jdbc;
import java.sql.Connection;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceException;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
import org.apache.openjpa.persistence.simple.AllFieldTypes;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.FetchPlan;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.jdbc.sql.DBDictionary;
import org.apache.openjpa.jdbc.sql.DB2Dictionary;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
public class TestIsolationLevelOverride
extends SQLListenerTestCase {
public void setUp() {
setUp(AllFieldTypes.class,
"openjpa.Optimistic", "false",
"openjpa.LockManager", "pessimistic");
}
public void testIsolationLevelOverride() {
OpenJPAEntityManager em =
OpenJPAPersistence.cast(emf.createEntityManager());
DBDictionary dict = ((JDBCConfiguration) em.getConfiguration())
.getDBDictionaryInstance();
sql.clear();
try {
em.getTransaction().begin();
((JDBCFetchPlan) em.getFetchPlan())
.setIsolationLevel(Connection.TRANSACTION_SERIALIZABLE);
em.find(AllFieldTypes.class, 0);
if (dict instanceof DB2Dictionary) {
assertEquals(1, sql.size());
assertSQL(".*DB2-specific SQL to test for goes here.*");
} else {
fail("OpenJPA currently only supports per-query isolation " +
"level configuration on the following databases: DB2");
}
} catch (PersistenceException pe) {
// if we're not using DB2, we expect an IllegalStateException.
if (dict instanceof DB2Dictionary
|| !(pe.getCause() instanceof IllegalStateException))
throw pe;
} finally {
em.getTransaction().rollback();
em.close();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2006 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openjpa.persistence.jdbc;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import org.apache.openjpa.persistence.test.SQLListenerTestCase;
import org.apache.openjpa.persistence.simple.AllFieldTypes;
import org.apache.openjpa.persistence.OpenJPAPersistence;
import org.apache.openjpa.persistence.FetchPlan;
public class TestSelectForUpdateOverride
extends SQLListenerTestCase {
public void setUp() {
setUp(AllFieldTypes.class,
"openjpa.Optimistic", "false",
"openjpa.LockManager", "pessimistic",
"openjpa.ReadLockLevel", "none");
}
public void testSelectForUpdateOverride() {
EntityManager em = emf.createEntityManager();
sql.clear();
try {
em.getTransaction().begin();
OpenJPAPersistence.cast(em).getFetchPlan()
.setReadLockMode(LockModeType.WRITE);
em.find(AllFieldTypes.class, 0);
assertEquals(1, sql.size());
assertSQL(".*FOR UPDATE.*");
} finally {
em.getTransaction().rollback();
em.close();
}
}
}