Генератор пользовательских первичных ключей в JPA

Я использую org.hibernate.id.IdentifierGenerator создать столбец первичного ключа следующим образом. В следующем примере, в настоящее время он просто увеличивает ключ типа INT(11) (MySQL) последовательно, т.е. auto_increment в MySQL, но затем его можно использовать для генерации значений любого пользовательского шаблона, например 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;
    }
}

Лицо Test который использует вышеупомянутый генератор идентификаторов:

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;

    //...
}

Это, однако, особенность Hibernate. Есть ли способ сделать его независимым от провайдера, т.е. есть ли такая функциональность в JPA (2.1)?

Я использую Hibernate 5.1.0 final с JPA 2.1.


Приводит ли этот подход к некоторым проблемам параллелизма при сохранении, объединении и удалении сущности и требует какой-то блокировки?

1 ответ

Вы не можете сделать это с аннотацией, как вы бы надеялись. Вы можете использовать @PrePersist крюк.

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

Тем не менее, вы не сможете получить доступ к БД из @PrePersist метод... спецификация JPA 2 (JSR 317) гласит следующее:

В общем случае метод жизненного цикла переносимого приложения не должен вызывать операции EntityManager или Query, обращаться к другим экземплярам сущности или изменять отношения в одном и том же контексте постоянства.

Что касается реализации, Hibernate запрещает это явно:

Метод обратного вызова не должен вызывать методы EntityManager или Query!

Моя рекомендация: используйте последовательность и выделите отдельный столбец, если вам нужно указать тип. Кроме того, вы всегда можете изменить SELECT чтобы вернуть шаблон по вашему выбору:

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

Или вы можете использовать @PostLoad/@PostPersist для возврата временного поля "formattedId":

@PostLoad
@PostPersist
private void setFormattedId() {
    this.formattedId = "E" + id;
}
Другие вопросы по тегам