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

git-svn-id: https://svn.apache.org/repos/asf/openjpa/branches/2.1.x@1153990 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Heath Thomann 2011-08-04 20:01:34 +00:00
parent dddb5a0f67
commit 1dbc7edccb
10 changed files with 142 additions and 42 deletions

View File

@ -55,6 +55,8 @@ import serp.util.Strings;
/** /**
* {@link JDBCSeq} implementation that uses a database sequences * {@link JDBCSeq} implementation that uses a database sequences
* to generate numbers. * 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 JDBCSeq
* @see AbstractJDBCSeq * @see AbstractJDBCSeq
@ -73,9 +75,11 @@ public class NativeJDBCSeq
private DBIdentifier _seqName = DBIdentifier.newSequence("OPENJPA_SEQUENCE"); private DBIdentifier _seqName = DBIdentifier.newSequence("OPENJPA_SEQUENCE");
private int _increment = 1; private int _increment = 1;
private int _initial = 1; private int _initial = 1;
private int _allocate = 0; private int _allocate = 50;
private Sequence _seq = null; private Sequence _seq = null;
private String _select = null; private String _select = null;
private long _nextValue = 0;
private long _maxValue = -1;
// for deprecated auto-configuration support // for deprecated auto-configuration support
private String _format = null; private String _format = null;
@ -203,29 +207,76 @@ public class NativeJDBCSeq
public void endConfiguration() { public void endConfiguration() {
buildSequence(); buildSequence();
if (DBIdentifier.isNull(_tableName))
_tableName = DBIdentifier.newTable("DUAL");
DBDictionary dict = _conf.getDBDictionaryInstance(); DBDictionary dict = _conf.getDBDictionaryInstance();
String name = dict.getFullName(_seq);
if (dict.useNativeSequenceCache){
if (_format == null) { if (_format == null) {
_format = dict.nextSequenceQuery; _format = dict.nextSequenceQuery;
if (_format == null) if (_format == null)
throw new MetaDataException(_loc.get("no-seq-sql", _seqName)); throw new MetaDataException(_loc.get("no-seq-sql", _seqName));
} }
if (DBIdentifier.isNull(_tableName))
_tableName = DBIdentifier.newTable("DUAL");
String name = dict.getFullName(_seq);
Object[] subs = (_subTable) ? new Object[]{ name, _tableName } Object[] subs = (_subTable) ? new Object[]{ name, _tableName }
: new Object[]{ name }; : 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; type = dict.nativeSequenceType;
} }
@Override @Override
protected Object nextInternal(JDBCStore store, ClassMapping mapping) protected synchronized Object nextInternal(JDBCStore store, ClassMapping mapping)
throws SQLException { 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); Connection conn = getConnection(store);
try { try {
return getSequence(conn); _nextValue = getSequence(conn);
_maxValue = _nextValue + _allocate * _increment;
} finally { } finally {
closeConnection(conn); closeConnection(conn);
} }
@ -300,9 +351,7 @@ public class NativeJDBCSeq
try { try {
stmnt = conn.prepareStatement(_select); stmnt = conn.prepareStatement(_select);
dict.setTimeouts(stmnt, _conf, false); dict.setTimeouts(stmnt, _conf, false);
synchronized(this) {
rs = stmnt.executeQuery(); rs = stmnt.executeQuery();
}
if (rs.next()) if (rs.next())
return rs.getLong(1); return rs.getLong(1);
@ -327,13 +376,12 @@ public class NativeJDBCSeq
* Where the following options are recognized. * Where the following options are recognized.
* <ul> * <ul>
* <li><i>-properties/-p &lt;properties file or resource&gt;</i>: The * <li><i>-properties/-p &lt;properties file or resource&gt;</i>: The
* path or resource name of a OpenJPA properties file containing * path or resource name of an OpenJPA properties file containing
* information such as the license key and connection data as * information such as connection data as
* outlined in {@link JDBCConfiguration}. Optional.</li> * outlined in {@link JDBCConfiguration}. Optional.</li>
* <li><i>-&lt;property name&gt; &lt;property value&gt;</i>: All bean * <li><i>-&lt;property name&gt; &lt;property value&gt;</i>: All bean
* properties of the OpenJPA {@link JDBCConfiguration} can be set by * properties of the OpenJPA {@link JDBCConfiguration} can be set by
* using their names and supplying a value. For example: * using their names and supplying a value.</li>
* <code>-licenseKey adslfja83r3lkadf</code></li>
* </ul> * </ul>
* The various actions are as follows. * The various actions are as follows.
* <ul> * <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, public static boolean run(JDBCConfiguration conf, String[] args,
String action) String action)

View File

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

View File

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

View File

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

View File

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

View File

@ -62,4 +62,34 @@ public class TestNativeSeqGenerator extends SQLListenerTestCase {
assertTrue("Next value should depend on previous genid", nextId >= genId + 1); assertTrue("Next value should depend on previous genid", nextId >= genId + 1);
em.close(); 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 allocationSize property
</secondary> </secondary>
</indexterm> </indexterm>
<literal>int allocationSize</literal>: Some databases can pre-allocate groups <literal>int allocationSize</literal>: The number of values to allocate in
of sequence values. This allows the database to service sequence requests from memory for each trip to the database. Allocating values in memory allows the JPA
cache, rather than physically incrementing the sequence with every request. This runtime to avoid accessing the database for every sequence request.
allocation size defaults to 50. This number also specifies the amount that the sequence value is incremented
each time the sequence is accessed. Defaults to 50.
</para> </para>
</listitem> </listitem>
<listitem> <listitem>

View File

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

View File

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

View File

@ -1661,8 +1661,11 @@ properties:
</listitem> </listitem>
<listitem> <listitem>
<para> <para>
<literal>Allocate</literal>: Some database can allocate values in-memory to <literal>Allocate</literal>: The number of values to allocate on each database
service subsequent sequence requests faster. 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> </para>
</listitem> </listitem>
</itemizedlist> </itemizedlist>