1

Scenario: Domain class is created and inserted using the default Postgres sequence generator (with a custom dialect to create one sequence per table but I think this is irrelevant). Now, in some cases I'd like to choose my own ID for insertion.

I imagine two basic ways:

  • preferred: simply set the id in the domain prior saving. If it is not set, use a generated id. I naively tried this, but it didn't work, i.e. the instance is inserted with the auto generated id, even if another id is already set in the domain instance.
  • using a "generator: assigned" mapping and determine a default id (let the DB decide). How to do this best?
user462982
  • 1,635
  • 1
  • 16
  • 26
  • How is the integrity assured that you are not going to use an `id` which has already been used by the sequence? – dmahapatro Aug 09 '13 at 15:12
  • "If it is not set, use a generated id". This is how PostgreSQL does it at the SQL level, but it's typically not a great idea as inserting a row with an ID greater than the current sequence position will lead to a failure when that point is reached. – Craig Ringer Aug 09 '13 at 15:42
  • @dmahapatro In this case, the existing row should simply be overwritten – user462982 Aug 09 '13 at 15:46
  • @Craig Ringer The actual requirement is a bit more relaxed, as there will never be an id that initially is not generated by the DB. I.e. the case you described will never occur. – user462982 Aug 09 '13 at 15:47
  • I think it should be possible like described here: http://stackoverflow.com/questions/3194721/bypass-generatedvalue-in-hibernate-merge-data-not-in-db/8535006#8535006 but I am not getting the config right I guess... – user462982 Aug 09 '13 at 15:48
  • 1
    @user462982 A viable workaround would be soft-deleting rows, so you can just resurrect them later. – Craig Ringer Aug 09 '13 at 15:53

1 Answers1

1

OK, so finally I came up with this (basically a combination from diverse suggestions made by others at different points in time)

package org.hibernate.id;

import java.io.Serializable;
import java.util.Properties;

import org.hibernate.HibernateException;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.type.Type;

public class UseExistingOrGenerateTabelNameSequenceIdGenerator extends org.hibernate.id.enhanced.SequenceStyleGenerator {

/**
 * {@inheritDoc} If the instance to insert does contain an ID
 * we use that id instead of an auto generated one.
 */
@Override
public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
    Serializable id = session.getEntityPersister(null, object).getClassMetadata().getIdentifier(object, session);
    return id != null ? id : super.generate(session, object);
}

/**
 * {@inheritDoc} If the parameters do not contain a
 * {@link SequenceStyleGenerator#SEQUENCE_PARAM} 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_PARAM) == null  || params.getProperty(SEQUENCE_PARAM).length() == 0) {
        String tableName = params.getProperty(PersistentIdentifierGenerator.TABLE);
        if (tableName != null) {
            params.setProperty(SEQUENCE_PARAM, "seq_" + tableName);
        }
    }
    super.configure(type, params, dialect);
}

}

To use it specifically on a domain class, you would include something like

static mapping = {
    id generator:'org.hibernate.id.UseExistingOrGenerateTabelNameSequenceIdGenerator', params:[optimizer:'pooled', increment_size:10]
}

into your domain class definition, which also allows to specify an id pool and its size. Seems to run great so far :)

user462982
  • 1,635
  • 1
  • 16
  • 26