3

I am using org.hibernate.id.IdentifierGenerator to generate a primary key column as follows. In the following given example, currently it just increments the key of type INT(11) (MySQL) sequentially i.e. it does like auto_increment in MySQL but it can then be used to generate values of any custom pattern like E0001, E0002, E0003 ... E0010 ... E0100 ... E1000 ... E12345 ...

import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.IdentifierGenerator;

public final class TestIdGenerator implements IdentifierGenerator {

    @Override
    public Serializable generate(SessionImplementor session, Object object) throws HibernateException {

        try {
            Connection connection = session.connection();
            PreparedStatement ps = connection.prepareStatement("SELECT MAX(id) AS id FROM test");
            ResultSet rs = ps.executeQuery();

            if (rs.next()) {
                int id = rs.getInt("id");
                return id <= 0 ? 1 : id + 1;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        return null;
    }
}

The entity Test which uses the above-mentioned id generator :

public class Test implements Serializable {

    @Id
    @GenericGenerator(name = "test_sequence", strategy = "com.example.TestIdGenerator")
    @GeneratedValue(generator = "test_sequence")
    @Basic(optional = false)
    @Column(name = "id", nullable = false)
    private Integer id;

    //...
}

This is however, a Hibernate specific feature. Is there a way to make it provider agnostic i.e is there such functionality available in JPA (2.1)?

I am using Hibernate 5.1.0 final having JPA 2.1.


Does this approach lead to some concurrency issues while persisting, merging and removing an entity and require some kind of locking?

Tiny
  • 27,221
  • 105
  • 339
  • 599
  • 1
    you could use an event listener and in the preStore callback set the PK field to this max(id) (and remove the GeneratedValue annotation). That is then applicable to all JPA providers. – Neil Stockton May 11 '16 at 15:55

1 Answers1

2

You can't do it with an annotation as you would've hoped. You can use the @PrePersist hook.

@PrePersist
public void ensureId() {
    id = ...
}

That said, you might not be able to access the DB from the @PrePersist method... the JPA 2 specification (JSR 317) states the following:

In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.

As far as implementations go, Hibernate forbids it explicitly:

A callback method must not invoke EntityManager or Query methods!

My recommendation: Use the sequence and have a separate column if you need to specify type. Also, you can always modify a SELECT to pull back the pattern of your choice:

select 'E' || id as id from schema.table;

Or you can use @PostLoad/@PostPersist to pull back a Transient field "formattedId":

@PostLoad
@PostPersist
private void setFormattedId() {
    this.formattedId = "E" + id;
}
Dean Clark
  • 3,770
  • 1
  • 11
  • 26
  • A sequence generated primary key or auto generated primary key is unfortunately disallowed. There are separate database maintainers who are not interested in auto generated or sequence generated primary keys. – Tiny May 21 '16 at 04:55
  • Well... that's wildly inefficient. Are you the only person who would insert to the table? You could perhaps cache the max value on startup and then increment in memory (but I'm not a fan of that). Can you move away from the idea of a sequence-based key and instead go with a composite business key along the lines of "Transaction Type, User, Timestamp" or something? – Dean Clark May 23 '16 at 13:23