Custom primary key generator in JPA

7.8k Views Asked by At

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?

1

There are 1 best solutions below

2
On

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;
}