Рубрики
Без рубрики

Как реализовать пользовательский генератор идентификаторов последовательностей на основе строк с помощью Hibernate

Автор оригинала: Vlad Mihalcea.

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

Я принял вызов и ответил на его вопрос в StackOverflow . Однако в этом посте эта тема будет объяснена более подробно, так что мы продолжим.

Нам нужен генератор идентификаторов гибернации, который может принимать любое значение, которое мы назначаем вручную, и он также может автоматически генерировать уникальный идентификатор, когда идентификатор сущности равен нулю. Однако пользователь не хочет использовать случайный идентификатор, подобный UUID. Вместо этого пользователю необходимо сгенерировать значение String , которое сочетает в себе префикс и числовое значение, полученное из последовательности базы данных.

Наш генератор пользовательских идентификаторов выглядит следующим образом:

public class StringSequenceIdentifier implements 
        IdentifierGenerator, Configurable {

    public static final String SEQUENCE_PREFIX = "sequence_prefix";

    private String sequencePrefix;

    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 ConfigurationService configurationService = serviceRegistry
        .getService(
            ConfigurationService.class
        );

        String globalEntityIdentifierPrefix = configurationService
        .getSetting(
            "entity.identifier.prefix",
            String.class,
            "SEQ_"
        );

        sequencePrefix = ConfigurationHelper
        .getString(
            SEQUENCE_PREFIX,
            params,
            globalEntityIdentifierPrefix
        );

        final String sequencePerEntitySuffix = ConfigurationHelper
        .getString(
            SequenceStyleGenerator.CONFIG_SEQUENCE_PER_ENTITY_SUFFIX,
            params,
            SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX
        );

        boolean preferSequencePerEntity = ConfigurationHelper
        .getBoolean(
            SequenceStyleGenerator.CONFIG_PREFER_SEQUENCE_PER_ENTITY,
            params,
            false
        );

        final String defaultSequenceName = preferSequencePerEntity
            ? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
            : SequenceStyleGenerator.DEF_SEQUENCE_NAME;

        sequenceCallSyntax = dialect
        .getSequenceNextValString(
            ConfigurationHelper.getString(
                SequenceStyleGenerator.SEQUENCE_PARAM,
                params,
                defaultSequenceName
            )
        );
    }

    @Override
    public Serializable generate(
            SharedSessionContractImplementor 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)
            .createNativeQuery(sequenceCallSyntax)
            .uniqueResult()
        ).longValue();

        return sequencePrefix + String.format("%011d%s", 0 ,seqValue);
    }
}

Синтаксис вызова последовательности содержит базовый для конкретной базы данных способ вызова последовательности. При вызове метода generate мы генерируем идентификатор только в том случае, если пользователь не предоставил значение, не допускающее значения null. Чтобы построить идентификатор на основе строки, мы извлекаем новое значение последовательности из базы данных и объединяем его с заданным префиксом.

Как имя последовательности базы данных, так и префикс настраиваются, как показано в сопоставлении сущностей:

@Entity(name = "Post") 
@Table(name = "post")
public class Post implements Identifiable {

    @Id
    @GenericGenerator(
        name = "assigned-sequence",
        strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.StringSequenceIdentifier",
        parameters = {
            @org.hibernate.annotations.Parameter(
                name = "sequence_name", value = "hibernate_sequence"),
            @org.hibernate.annotations.Parameter(
                name = "sequence_prefix", value = "CTC_"),
        }
    )
    @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;
    }
}

С учетом этого сопоставления пришло время сохранить несколько сущностей и посмотреть, какие идентификаторы сохранены в базе данных:

doInJPA(entityManager -> {
    entityManager.persist(new Post());
    entityManager.persist(new Post("ABC"));
    entityManager.persist(new Post());
    entityManager.persist(new Post("DEF"));
    entityManager.persist(new Post());
    entityManager.persist(new Post());
});

При запуске приведенного выше тестового случая в PostgreSQL Hibernate генерирует следующие инструкции:

SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')

INSERT INTO post (version, id) VALUES (0, 'CTC_000000000001')
INSERT INTO post (version, id) VALUES (0, 'ABC')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000002')
INSERT INTO post (version, id) VALUES (0, 'DEF')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000003')
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000004')

При запуске одного и того же тестового набора в Oracle синтаксис последовательности изменяется соответствующим образом:

SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual
SELECT hibernate_sequence.nextval FROM dual

INSERT INTO post (version, id) VALUES (0, 'CTC_000000000001')
INSERT INTO post (version, id) VALUES (0, 'ABC')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000002')
INSERT INTO post (version, id) VALUES (0, 'DEF')             
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000003')
INSERT INTO post (version, id) VALUES (0, 'CTC_000000000004')

Режим гибернации легко расширяется, и генераторы идентификаторов ничем не отличаются. От пользовательских типов до генераторов идентификаторов, @Формула , @Где или @Любые сопоставления , Hibernate позволяет вам выполнять любые требования к привязке данных, которые у вас могут возникнуть.

Код доступен на GitHub .