Grails sequence generation for Oracle 11g
Asked Answered
P

3

5

I realize this is more of a hibernate question than Grails. In a load balanced (2 nodes) environment I see that the ids of my objects are jumping around quite a bit. Even without restarting the app server I see that the numbers skip 10 sometimes 20 numbers. I suspect the hibernate session is caching a block of sequence values. Is there a way to control this behavior with grails 1.3.7 ? Essentially I am OK with server pulling nextval from DB every time it needs one.

My domain object sequence declaration (same for 2 objects):

static mapping = {
        id generator:'sequence', params:[sequence:'MY_SEQ']  
    } 
Pricket answered 30/11, 2011 at 22:37 Comment(0)
P
4

I have since went to the database and modified the Sequence with the following DDL:

ALTER SEQUENCE MY_SEQ NOCACHE;

I think this is the best solution for this issue. Does anyone see potential problems with this approach?

Thanks all!

Pricket answered 1/12, 2011 at 19:19 Comment(1)
This should work fine. I think Hibernate will always call MY_SEQ.NEXTVAL regardless of whether the sequence is cached or not; the caching happens internally in Oracle, not in the Hibernate or JDBC level. Note that if your application depends on not having gaps in a sequence of numbers, then using an Oracle sequence might not be the right approach. There is no guarantee that you won't have any gaps in sequences -- you might have different threads getting sequence values or you might have rollbacks.Whitman
Q
3

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

Quash answered 1/12, 2011 at 3:1 Comment(3)
Thanks for that! Are you saying there is no way to specify NOCACHE or allocationSize in params for a sequence?Pricket
Not as far as I know, as that would be database specific SQL, which would tie down your application to a specific brand of database.Quash
Sorry schmolly. That is not the solution that I was looking for. I will mark it up as it maybe useful to others. See my solution for alternative easy fix. ...at least I think so :)Pricket
A
0

If someone ends here searching, this solution not works with Oracle12cDialect and hibernate 5 to have a sequence per table.

In that case I was able to have a sequence per table adding in the constraints of each domain class this parameters:

id generator:'sequence', params:[sequence_name:'XXX_seq']
Alternately answered 5/7, 2022 at 15:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.