15

I am working on a Java application, that should do CRUD operations (using Hibernate 4.3.8) on two different databases with the same database schema. There is a MySQL (version 5.1.73) and an Oracle (11g Express Edition Release 11.2.0.2.0 - 64bit) database.

Java classes with JPA annotations were generated from the database tables with Hibernate Code Generation.

The problem is that we now have the need to use auto primary key generation and MySQL uses GenerationType.IDENTITY and Oracle uses GenerationType.SEQUENCE. Furthermore, we need the ability to manually set primary key by ourself in some rare cases.

The followig code in the annotated class works with auto key generation for both databases but fails, if a primary key is self set.

@GeneratedValue(strategy=GenerationType.AUTO, generator="sequence_generator")
@SequenceGenerator(name="sequence_generator", sequenceName="SEQUENCE1")
@Column(name = "id", unique = true, nullable = false)
public Integer getId() {
    return this.id;
}

Without the @GeneratedValue and @SequenceGenerator annotations it is possible to set the primary key manually, but auto generation doesn't work.

Simon Schürg
  • 2,134
  • 2
  • 20
  • 31
  • Try this [manually-specify-the-value-of-a-primary-key-in-jpa-generatedvalue-column][1]. There there are many solutions. [1]: http://stackoverflow.com/questions/12002260/manually-specify-the-value-of-a-primary-key-in-jpa-generatedvalue-column – Volatile Jun 09 '15 at 13:15
  • The database-centric solution in this thread does not work for me because the database schema should not be changed. There are other people than me who rely that the database schema does not change. And I don't know whether it is possible and how to write an own Custom Id Generator which supports both databases MySQL AND Oracle (Identity and Sequence). In all Examples they either use IdentifierGenerator OR Sequencegenerator. – Simon Schürg Jun 09 '15 at 18:07
  • 1
    This question isn't clear. Are you saying that you have some classes for which you want Hibernate to auto-generate identifiers and manually assign identifiers to other classes; or are you saying that you want Hibernate to auto-assign identifiers to instances of a class most of the times but in some cases you want to manually assign identifiers to some instances of that same class? – manish Jun 24 '15 at 14:11
  • You should use trigger to have a homogeneous cross database solution. – falsarella Jun 26 '15 at 19:20
  • My old Spring 4 project used to work fine both on MySQL and on Oracle 11g just with the `@SequenceGenerator(name = "id_generator", sequenceName = "sq_id_user") ` annotation, but now that I'm on Spring Boot 2 and Hibernate 5 it complains of a non existing sequence in 10.1.24-MariaDB. – Stephane Jun 26 '18 at 14:21

2 Answers2

14

Even if you used GenerationType.AUTO without any SEQUENCE specific parameter, you wouldn't be able to save assigned identifiers.

There are some workarounds if you are willing to make some compromises:

  1. One way would be to switch to the assigned identifiers. You can use UUID identifiers, which work for both MySQL and Oracle and you can also assign the values manually.

  2. Another way is to use a custom table generator.

First you define an Identifiable interface:

    public interface Identifiable<T extends Serializable> {
        T getId();
    }

Then you extend the table generator:

    public class AssignedTableGenerator extends TableGenerator {

        @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 super.generate(session, obj);
        }
    }

This generator is able to mix assigned identifiers with synthetic generated ones:

    doInTransaction(session -> {
        for (int i = 0; i < 5; i++) {
            session.persist(new AssignTableSequenceIdentifier());
        }
        AssignTableSequenceIdentifier tableSequenceIdentifier = new AssignTableSequenceIdentifier();
        tableSequenceIdentifier.id = -1L;
        session.merge(tableSequenceIdentifier);
        session.flush();
    });

generating the following statements:

    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    insert into sequence_table (sequence_name, next_val)  values (default,1)
    update sequence_table set next_val=2  where next_val=1 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=3  where next_val=2 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=4  where next_val=3 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=5  where next_val=4 and sequence_name=default
    select tbl.next_val from sequence_table tbl where tbl.sequence_name=default for update
    update sequence_table set next_val=6  where next_val=5 and sequence_name=default
    select identityvs0_.id as id1_0_0_ from assigneTableIdentifier identityvs0_ where identityvs0_.id=-1
    insert into assigneTableIdentifier (id) values (1, 2)
    insert into assigneTableIdentifier (id) values (2, 4)
    insert into assigneTableIdentifier (id) values (5, -1)

For Oracle, you can combine the SEQUENCE and the assigned generators. In short, considering the following generator:

public class AssignedSequenceStyleGenerator 
    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 super.generate(session, obj);
    }
}

You can map it to your entities as follows:

@Id
@GenericGenerator(
    name = "assigned-sequence",
    strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedSequenceStyleGenerator",
    parameters = @org.hibernate.annotations.Parameter(
        name = "sequence_name", 
        value = "post_sequence"
    )
)
@GeneratedValue(
    generator = "assigned-sequence", 
    strategy = GenerationType.SEQUENCE
)
private Long id;

All the code is available on GitHub and works like a charm.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • Does this approach work for mysql db? It seems that I am getting below exceptionCaused by: org.hibernate.MappingException: org.hibernate.dialect.MySQL5Dialect does not support sequences – simpleusr May 29 '18 at 11:39
  • Hi Vlad, are you saying that use TableGenerator instead of Sequence generator? I already have an entity class and using it for oracle db with sequence generator. But my purpose is to use the same class also for mysql db. As far as I understand there is no sequence equivalent in mysql database so I should change my Id generation strategy (i.e use another table instead of sequence) ? – simpleusr May 30 '18 at 05:02
  • Hi Vlad. Thanks for suggestion. I followed this link : https://vladmihalcea.com/how-to-replace-the-table-identifier-generator-with-either-sequence-or-identity-in-a-portable-way/ and could use the same entity with sequence for oracle db and auto increment for mfsql db. Thnx – simpleusr May 30 '18 at 12:49
  • If I insert the code (with all changes of names and imports) into application, the server refuses to work with it. IMHO, you have forgotten some significant details. – Gangnus May 30 '20 at 13:41
  • 1
    Pro tip 1: Copy code from Stack Overflow and randomly pasting it into your applications is not a smart thing to do. Pro tip 2: Why copy code from Stack Overflow when you can fork the [GitHub repository](https://github.com/vladmihalcea/high-performance-java-persistence) locate the [package](https://github.com/vladmihalcea/high-performance-java-persistence/blob/951db3fb0f6e9e699d4023044aa6baaeaec54696/core/src/test/java/com/vladmihalcea/book/hpjp/hibernate/identifier/batch/AssignedTableGenerator.java) and run the code to see it working like a charm. – Vlad Mihalcea May 30 '20 at 15:18
7

Try something like this:

@Id
@Column( name = "ID" )
@TableGenerator( 
        name = "AppSeqStore", 
        table = "APP_SEQ_STORE", 
        pkColumnName = "APP_SEQ_NAME", 
        pkColumnValue = "LISTENER_PK", 
        valueColumnName = "APP_SEQ_VALUE", 
        initialValue = 1, 
        allocationSize = 1 )
@GeneratedValue( strategy = GenerationType.TABLE, generator = "AppSeqStore" )

And this table in the database:

CREATE TABLE APP_SEQ_STORE (
    APP_SEQ_NAME VARCHAR(255) NOT NULL,
    APP_SEQ_VALUE NUMBER(10) NOT NULL,
    PRIMARY KEY(APP_SEQ_NAME)
)

INSERT INTO APP_SEQ_STORE VALUES ('LISTENER_PK', 0)

This all works in Oracle, and MS Sql Server, and MySql using JBoss as the App Server.

More information here: http://www.developerscrappad.com/408/java/java-ee/ejb3-jpa-3-ways-of-generating-primary-key-through-generatedvalue/

John
  • 3,458
  • 4
  • 33
  • 54