The caching issue is because Hibernate defaults to an Oracle dialect which does two things. It creates a sequence shared across all tables for primary key generation and the sequence caches 20 numbers at a time, which if they aren't used within a specific timeframe, Oracle will discard the remainder.
The following Oracle solution comes from the a post by Burt Beckwith, with a tweek to prevent the Oracle sequence from caching numbers. Thus, this dialect will do two things for you:
- It will create a single sequence for each table, so the sequence isn't shared and primary key numbers aren't split across tables.
- It will disable the caching of numbers in Oracle, so you won't lose any sequence numbers in an expired cache. This is set up in the
PARAMETERS
property with the NOCACHE
command.
Since you are defining your table's sequence in the mapping you could probably strip out the sequence per table logic and leave in the NOCACHE
sequence definition to achieve your desired results.
Also, you'll need to drop the sequence from your existing tablespace, as Grails will not re-create it, except in create
and create-drop
scenarios. When doing this you may also want to bump up the start value of the new sequence to prevent conflicting primary key issues with keys already in use by the database.
To use the dialect add dialect = SequencePerTableOracleDialect
to your DataSource.groovy
file in the dataSource definition closure.
import java.util.Properties;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.Oracle10gDialect;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.SequenceGenerator;
import org.hibernate.type.Type;
public class SequencePerTableOracleDialect extends Oracle10gDialect {
public static final String PARAMETERS = "MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 NOCACHE NOCYCLE";
/**
* Get the native identifier generator class.
*
* @return TableNameSequenceGenerator.
*/
@Override
public Class<?> getNativeIdentifierGeneratorClass() {
return TableNameSequenceGenerator.class;
}
/**
* Creates a sequence per table instead of the default behavior of one
* sequence.
*/
public static class TableNameSequenceGenerator extends SequenceGenerator {
/**
* {@inheritDoc} If the parameters do not contain a
* {@link SequenceGenerator#SEQUENCE} name, we assign one based on the
* table name.
*/
@Override
public void configure(final Type type, final Properties params,
final Dialect dialect) {
if (params.getProperty(SEQUENCE) == null
|| params.getProperty(SEQUENCE).length() == 0) {
/* Sequence per table */
String tableName = params
.getProperty(PersistentIdentifierGenerator.TABLE);
if (tableName != null) {
params.setProperty(SEQUENCE, createSequenceName(tableName));
}
/* Non-Caching Sequence */
params.setProperty(PARAMETERS, SequencePerTableOracleDialect.PARAMETERS);
}
super.configure(type, params, dialect);
}
/**
* Construct a sequence name from a table name.
*
* @param tableName
* the table name
* @return the sequence name
*/
String createSequenceName(final String tableName) {
return "seq_" + tableName;
}
}
}
This link has some history on this question, with a link to Burt's original code, and a response for PostGreSql: Hibernate & postgreSQL with Grails