OPENJPA-1376: Backported trunk changes to 2.0.x where possible, made code gated by a system property.

git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/2.0.x@1153950 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Heath Thomann 2011-08-04 17:41:44 +00:00
parent 75a512da70
commit d12334e6f9
10 changed files with 144 additions and 43 deletions

View File

@ -55,6 +55,8 @@ import serp.util.Strings;
/**
* {@link JDBCSeq} implementation that uses a database sequences
* to generate numbers.
* Supports allocation (caching). In order for allocation to work properly, the database sequence must be defined
* with INCREMENT BY value equal to allocate * increment.
*
* @see JDBCSeq
* @see AbstractJDBCSeq
@ -73,9 +75,11 @@ public class NativeJDBCSeq
private DBIdentifier _seqName = DBIdentifier.newSequence("OPENJPA_SEQUENCE");
private int _increment = 1;
private int _initial = 1;
private int _allocate = 0;
private int _allocate = 50;
private Sequence _seq = null;
private String _select = null;
private long _nextValue = 0;
private long _maxValue = -1;
// for deprecated auto-configuration support
private String _format = null;
@ -203,29 +207,76 @@ public class NativeJDBCSeq
public void endConfiguration() {
buildSequence();
DBDictionary dict = _conf.getDBDictionaryInstance();
if (_format == null) {
_format = dict.nextSequenceQuery;
if (_format == null)
throw new MetaDataException(_loc.get("no-seq-sql", _seqName));
}
if (DBIdentifier.isNull(_tableName))
_tableName = DBIdentifier.newTable("DUAL");
DBDictionary dict = _conf.getDBDictionaryInstance();
String name = dict.getFullName(_seq);
Object[] subs = (_subTable) ? new Object[]{ name, _tableName }
if (dict.useNativeSequenceCache){
if (_format == null) {
_format = dict.nextSequenceQuery;
if (_format == null)
throw new MetaDataException(_loc.get("no-seq-sql", _seqName));
}
Object[] subs = (_subTable) ? new Object[]{ name, _tableName }
: new Object[]{ name };
_select = MessageFormat.format(_format, subs);
_select = MessageFormat.format(_format, subs);
}
else {
String format = dict.nextSequenceQuery;
if (format == null) {
throw new MetaDataException(_loc.get("no-seq-sql", _seqName));
}
// Increment step is needed for Firebird which uses non-standard sequence fetch syntax.
// Use String.valueOf to get rid of possible locale-specific number formatting.
_select = MessageFormat.format(format, new Object[]{name,
String.valueOf(_allocate * _increment)});
}
type = dict.nativeSequenceType;
}
@Override
protected Object nextInternal(JDBCStore store, ClassMapping mapping)
protected synchronized Object nextInternal(JDBCStore store, ClassMapping mapping)
throws SQLException {
DBDictionary dict = _conf.getDBDictionaryInstance();
//To maintain existing behavior call allocateInternal to get the next
//sequence value, which it stores in _nextValue, and simply return the value.
if (dict.useNativeSequenceCache){
allocateInternal(0, store, mapping);
return _nextValue;
}
if (_nextValue < _maxValue) {
long result = _nextValue;
_nextValue += _increment;
return result;
}
allocateInternal(0, store, mapping);
long result = _nextValue;
_nextValue += _increment;
return result;
}
/**
* Allocate additional sequence values.
* @param additional ignored - the allocation size is fixed and determined by allocate and increment properties.
* @param store used to obtain connection
* @param mapping ignored
*/
@Override
protected synchronized void allocateInternal(int additional, JDBCStore store, ClassMapping mapping)
throws SQLException {
Connection conn = getConnection(store);
try {
return getSequence(conn);
_nextValue = getSequence(conn);
_maxValue = _nextValue + _allocate * _increment;
} finally {
closeConnection(conn);
}
@ -300,9 +351,7 @@ public class NativeJDBCSeq
try {
stmnt = conn.prepareStatement(_select);
dict.setTimeouts(stmnt, _conf, false);
synchronized(this) {
rs = stmnt.executeQuery();
}
rs = stmnt.executeQuery();
if (rs.next())
return rs.getLong(1);
@ -327,13 +376,12 @@ public class NativeJDBCSeq
* Where the following options are recognized.
* <ul>
* <li><i>-properties/-p &lt;properties file or resource&gt;</i>: The
* path or resource name of a OpenJPA properties file containing
* information such as the license key and connection data as
* path or resource name of an OpenJPA properties file containing
* information such as connection data as
* outlined in {@link JDBCConfiguration}. Optional.</li>
* <li><i>-&lt;property name&gt; &lt;property value&gt;</i>: All bean
* properties of the OpenJPA {@link JDBCConfiguration} can be set by
* using their names and supplying a value. For example:
* <code>-licenseKey adslfja83r3lkadf</code></li>
* using their names and supplying a value.</li>
* </ul>
* The various actions are as follows.
* <ul>
@ -373,7 +421,7 @@ public class NativeJDBCSeq
}
/**
* Run the tool. Return false if an invalid option was given.
* Run the tool. Returns false if an invalid option was given.
*/
public static boolean run(JDBCConfiguration conf, String[] args,
String action)

View File

@ -227,12 +227,15 @@ public class DB2Dictionary
}
}
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1)
sql[0] += " CACHE " + seq.getAllocate();
return sql;
}
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1 && useNativeSequenceCache){
sql[0] += " CACHE " + seq.getAllocate();
}
return sql;
}
@Override
protected String getSequencesSQL(String schemaName, String sequenceName) {

View File

@ -350,6 +350,7 @@ public class DBDictionary
public String sequenceNameSQL = null;
// most native sequences can be run inside the business transaction
public int nativeSequenceType= Seq.TYPE_CONTIGUOUS;
public boolean useNativeSequenceCache = true;
protected JDBCConfiguration conf = null;
protected Log log = null;
@ -3385,6 +3386,14 @@ public class DBDictionary
buf.append(seqName);
if (seq.getInitialValue() != 0)
buf.append(" START WITH ").append(seq.getInitialValue());
if (seq.getIncrement() > 1 && useNativeSequenceCache){
buf.append(" INCREMENT BY ").append(seq.getIncrement());
}
else if ((seq.getIncrement() > 1) || (seq.getAllocate() > 1)){
buf.append(" INCREMENT BY ").append(seq.getIncrement() * seq.getAllocate());
}
if (seq.getIncrement() > 1)
buf.append(" INCREMENT BY ").append(seq.getIncrement());
return new String[]{ buf.toString() };

View File

@ -1036,12 +1036,15 @@ public class OracleDictionary
}
@Override
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1)
sql[0] += " CACHE " + seq.getAllocate();
return sql;
}
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1 && useNativeSequenceCache){
sql[0] += " CACHE " + seq.getAllocate();
}
return sql;
}
/**
* Return the preferred {@link Types} constant for the given

View File

@ -345,8 +345,11 @@ public class PostgresDictionary
@Override
public String[] getCreateSequenceSQL(Sequence seq) {
String[] sql = super.getCreateSequenceSQL(seq);
if (seq.getAllocate() > 1)
if (seq.getAllocate() > 1 && useNativeSequenceCache){
sql[0] += " CACHE " + seq.getAllocate();
}
return sql;
}

View File

@ -59,7 +59,38 @@ public class TestNativeSeqGenerator extends SQLListenerTestCase {
em.getTransaction().commit();
int genId = entityE2.getId();
int nextId = (int)((Long)em.getIdGenerator(EntityE2.class).next()).longValue();
assertTrue("Next value should depend on previous genid", nextId == genId + 1);
assertTrue("Next value should depend on previous genid", nextId >= genId + 1);
em.close();
}
/**
* Asserts native sequence generator allocates values in memory
* and requests sequence values from database only when necessary.
*/
public void testAllocationSize() {
//Run this test only if the user has elected to not use the Native Sequence Cache.
if (supportsNativeSequence && !dict.useNativeSequenceCache){
// Turn off statement batching for easier INSERT counting.
dict.setBatchLimit(0);
em.getTransaction().begin();
resetSQL();
for (int i = 0; i < 51; i++) {
createEntityE2();
em.persist(entityE2);
}
em.getTransaction().commit();
// Since allocationSize has a default of 50, we expect 2 sequence fetches and 51 INSERTs.
assertEquals("53 statements should be executed.", 53, getSQLCount());
String[] statements = new String[53];
statements[0] = ".*";
statements[1] = ".*";
for (int i = 2; i < 53; i++) {
statements[i] = "INSERT .*";
}
assertAllExactSQLInOrder(statements);
em.close();
}
}
}

View File

@ -967,10 +967,11 @@ default.
allocationSize property
</secondary>
</indexterm>
<literal>int allocationSize</literal>: Some databases can pre-allocate groups
of sequence values. This allows the database to service sequence requests from
cache, rather than physically incrementing the sequence with every request. This
allocation size defaults to 50.
<literal>int allocationSize</literal>: The number of values to allocate in
memory for each trip to the database. Allocating values in memory allows the JPA
runtime to avoid accessing the database for every sequence request.
This number also specifies the amount that the sequence value is incremented
each time the sequence is accessed. Defaults to 50.
</para>
</listitem>
<listitem>

View File

@ -1824,8 +1824,9 @@ create a generated name. The default value is the underscore <literal>"_"</liter
</indexterm>
<literal>NextSequenceQuery</literal>: A SQL string for obtaining a native
sequence value. May use a placeholder of <literal>{0}</literal> for the variable
sequence name. Defaults to a database-appropriate value. For example,
<literal>"SELECT {0}.NEXTVAL FROM DUAL"</literal> for Oracle.
sequence name and <literal>{1}</literal> for sequence increment.
Defaults to a database-appropriate value. For example,
<literal>"SELECT {0}.NEXTVAL FROM DUAL"</literal> for Oracle database.
</para>
</listitem>
<listitem id="DBDictionary.NullTypeName">

View File

@ -295,8 +295,7 @@ it can become a factor.
</entry>
<entry colname="desc">
For applications that perform large bulk inserts, the retrieval of sequence
numbers can be a bottleneck. Increasing sequence increments and using
table-based rather than native database sequences can reduce or eliminate
numbers can be a bottleneck. Increasing sequence allocation sizes can reduce or eliminate
this bottleneck. In some cases, implementing your own sequence factory can
further optimize sequence number retrieval.
</entry>

View File

@ -1661,8 +1661,11 @@ properties:
</listitem>
<listitem>
<para>
<literal>Allocate</literal>: Some database can allocate values in-memory to
service subsequent sequence requests faster.
<literal>Allocate</literal>: The number of values to allocate on each database
trip. Defaults to 50, meaning the class will set aside the next 50 numbers each
time it accesses the sequence, which in turn means it only has to make a
database trip to get new sequence numbers once every 50 sequence number
requests.
</para>
</listitem>
</itemizedlist>