Автор оригинала: 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 .