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