javadatabasehibernateidentifiersequence-generators

How to implement a custom String sequence identifier generator with Hibernate


I'm using hibernate with spring, h2 and liquibase and I'm trying to make a custom String id generator for my entities by taking example with this blog post but I'm getting an error : Caused by: org.hibernate.id.IdentifierGenerationException: Unknown integral data type for ids : java.lang.String

Here my SequenceStyleGenerator code :

public class CTCIDGenerator extends SequenceStyleGenerator {

    @Override
    public Serializable generate(SessionImplementor session, Object obj) {
        if (obj instanceof Identifiable) {
            Identifiable identifiable = (Identifiable) obj;
            Serializable id = identifiable.getId();
            if (id != null) {
                return id;
            }
        }
        return "CTC"+super.generate(session, obj);
    }
}

My entity code :

@Entity
@Table(name = "contact")
public class Contact implements Serializable, Identifiable<String> {

    private static final long serialVersionUID = 1L;

    @Id
    @GenericGenerator(
        name = "assigned-sequence",
        strategy =     "net.atos.seirich.support.domain.idgenerator.CTCIDGenerator",
        parameters = @org.hibernate.annotations.Parameter(
            name = "sequence_name", 
            value = "hibernate_sequence"
        )
    )
    @GeneratedValue(generator = "assigned-sequence", strategy = GenerationType.SEQUENCE)
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

And the liquibase XML :

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">

    <property name="autoIncrement" value="true" dbms="mysql,h2,postgresql,oracle"/>

    <property name="floatType" value="float4" dbms="postgresql, h2"/>
    <property name="floatType" value="float" dbms="mysql, oracle"/>

    <changeSet id="20160513091901-1" author="jhipster">
        <createTable tableName="contact">
            <column name="id" type="longvarchar" autoIncrement="${autoIncrement}">
                <constraints primaryKey="true" nullable="false"/>
            </column>
    </changeSet>
</databaseChangeLog>

Btw is it possible to avoid the parameter sequence_name so hibernate can handle this by itself ?

If anyone can help me, Thanks !


Solution

  • The problem is that SequenceStyleGenerator expects to return a numerical value, not a String.

    I already tried a solution to this problem and it works like a charm. Therefore, you need to change your generator like this:

    public class StringSequenceIdentifier implements IdentifierGenerator, Configurable {
    
        private String sequenceCallSyntax;
    
        @Override
        public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
            final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService(JdbcEnvironment.class);
            final Dialect dialect = jdbcEnvironment.getDialect();
    
            final String sequencePerEntitySuffix = ConfigurationHelper.getString(CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, params, DEF_SEQUENCE_SUFFIX);
    
            final String defaultSequenceName = ConfigurationHelper.getBoolean(CONFIG_PREFER_SEQUENCE_PER_ENTITY, params, false)
                    ? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
                    : DEF_SEQUENCE_NAME;
    
            sequenceCallSyntax = dialect.getSequenceNextValString(ConfigurationHelper.getString(SEQUENCE_PARAM, params, defaultSequenceName));
        }
    
        @Override
        public Serializable generate(SessionImplementor session, Object obj) {
            if (obj instanceof Identifiable) {
                Identifiable identifiable = (Identifiable) obj;
                Serializable id = identifiable.getId();
                if (id != null) {
                    return id;
                }
            }
            long seqValue = ((Number) Session.class.cast(session)
                .createSQLQuery(sequenceCallSyntax)
                .uniqueResult()).longValue();
    
            return "CTC" + seqValue;
        }
    }
    

    Your mapping becomes:

    @Entity(name = "Post")
    @Table(name = "post")
    public static class Post implements Identifiable<String> {
    
        @Id
        @GenericGenerator(
            name = "assigned-sequence",
            strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.StringSequenceIdentifier",
            parameters = @org.hibernate.annotations.Parameter(name = "sequence_name", value = "hibernate_sequence")
        )
        @GeneratedValue(generator = "assigned-sequence", strategy = GenerationType.SEQUENCE)
        private String id;
    
        @Version
        private Integer version;
    
        public Post() {
        }
    
        public Post(String id) {
            this.id = id;
        }
    
        @Override
        public String getId() {
            return id;
        }
    }
    

    Now, when you insert the following entities:

    doInJPA(entityManager -> {
        entityManager.persist(new Post());
        entityManager.persist(new Post("ABC"));
        entityManager.persist(new Post());
        entityManager.persist(new Post("DEF"));
    });
    

    Hibernate generates the right identifier:

    Query:["select nextval ('hibernate_sequence')"], Params:[()]
    Query:["select nextval ('hibernate_sequence')"], Params:[()]
    Query:["insert into post (version, id) values (?, ?)"], Params:[(0, CTC1)]
    Query:["insert into post (version, id) values (?, ?)"], Params:[(0, ABC)]
    Query:["insert into post (version, id) values (?, ?)"], Params:[(0, CTC2)]
    Query:["insert into post (version, id) values (?, ?)"], Params:[(0, DEF)]
    

    Code available on GitHub.