How to implement a custom String sequence identifier generator with Hibernate

25,078

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.

Share:
25,078
Takmashido
Author by

Takmashido

Updated on January 10, 2021

Comments

  • Takmashido
    Takmashido over 3 years

    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 !