Как реализовать пользовательский генератор идентификаторов последовательности строк в Hibernate
Я использую hibernate с spring, h2 и liquibase, и я пытаюсь создать собственный генератор идентификаторов String для своих сущностей, взяв пример с этим сообщением в блоге, но получаю ошибку: Caused by: org.hibernate.id.IdentifierGenerationException: Unknown integral data type for ids : java.lang.String
Вот мой код SequenceStyleGenerator:
public class CTCIDGenerator extends SequenceStyleGenerator {
@Override
public Serializable generate(SessionImplementor session, Object obj) {
if (obj instanceof Identifiable) {
Identifiable identifiable = (Identifiable) obj;
Serializable id = identifiable.getId();
if (id != null) {
return id;
}
}
return "CTC"+super.generate(session, obj);
}
}
Код моей сущности:
@Entity
@Table(name = "contact")
public class Contact implements Serializable, Identifiable<String> {
private static final long serialVersionUID = 1L;
@Id
@GenericGenerator(
name = "assigned-sequence",
strategy = "net.atos.seirich.support.domain.idgenerator.CTCIDGenerator",
parameters = @org.hibernate.annotations.Parameter(
name = "sequence_name",
value = "hibernate_sequence"
)
)
@GeneratedValue(generator = "assigned-sequence", strategy = GenerationType.SEQUENCE)
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
И жидкая база XML:
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd">
<property name="autoIncrement" value="true" dbms="mysql,h2,postgresql,oracle"/>
<property name="floatType" value="float4" dbms="postgresql, h2"/>
<property name="floatType" value="float" dbms="mysql, oracle"/>
<changeSet id="20160513091901-1" author="jhipster">
<createTable tableName="contact">
<column name="id" type="longvarchar" autoIncrement="${autoIncrement}">
<constraints primaryKey="true" nullable="false"/>
</column>
</changeSet>
</databaseChangeLog>
Кстати, можно ли избежать параметра sequence_name, чтобы hibernate мог справиться с этим сам?
Если кто-нибудь может мне помочь, спасибо!
3 ответа
Проблема в том, что SequenceStyleGenerator
ожидает вернуть числовое значение, а не String
,
Я уже пытался решить эту проблему, и она работает как шарм. Поэтому вам нужно изменить ваш генератор следующим образом:
public class StringSequenceIdentifier implements IdentifierGenerator, Configurable {
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 String sequencePerEntitySuffix = ConfigurationHelper.getString(CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, params, DEF_SEQUENCE_SUFFIX);
final String defaultSequenceName = ConfigurationHelper.getBoolean(CONFIG_PREFER_SEQUENCE_PER_ENTITY, params, false)
? params.getProperty(JPA_ENTITY_NAME) + sequencePerEntitySuffix
: DEF_SEQUENCE_NAME;
sequenceCallSyntax = dialect.getSequenceNextValString(ConfigurationHelper.getString(SEQUENCE_PARAM, params, defaultSequenceName));
}
@Override
public Serializable generate(SessionImplementor 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)
.createSQLQuery(sequenceCallSyntax)
.uniqueResult()).longValue();
return "CTC" + seqValue;
}
}
Ваше отображение становится:
@Entity(name = "Post")
@Table(name = "post")
public static class Post implements Identifiable<String> {
@Id
@GenericGenerator(
name = "assigned-sequence",
strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.StringSequenceIdentifier",
parameters = @org.hibernate.annotations.Parameter(name = "sequence_name", value = "hibernate_sequence")
)
@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"));
});
Hibernate генерирует правильный идентификатор:
Query:["select nextval ('hibernate_sequence')"], Params:[()]
Query:["select nextval ('hibernate_sequence')"], Params:[()]
Query:["insert into post (version, id) values (?, ?)"], Params:[(0, CTC1)]
Query:["insert into post (version, id) values (?, ?)"], Params:[(0, ABC)]
Query:["insert into post (version, id) values (?, ?)"], Params:[(0, CTC2)]
Query:["insert into post (version, id) values (?, ?)"], Params:[(0, DEF)]
Код доступен на GitHub.
Да, в Hibernate есть встроенные генераторы String. Просто подставьте свой @GenericGenerator
определение другой стратегии.
@Entity
@Table(name = "contact")
public class Contact implements Serializable, Identifiable<String> {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(generator = "uuid")
@GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Для получения дополнительной информации о различных генераторах гибернации вы можете посмотреть документацию.
можно повторно использовать генератор стиля последовательности, следуя решению
public class AppIdGenerator extends SequenceStyleGenerator {
public static final String PREFIX_PARAM = "prefix";
private String prefix;
@Override
public void configure(Type type, Properties properties,
ServiceRegistry serviceRegistry) throws MappingException {
super.configure(new NamedBasicTypeImpl<>(new JavaTypeBasicAdaptor<>(Long.class),
NumericJdbcType.INSTANCE, "long"), properties, serviceRegistry);
prefix = StringUtils.capitalize(ConfigurationHelper.getString(PREFIX_PARAM, properties));
}
@Override
public Serializable generate(SharedSessionContractImplementor session, Object obj) throws HibernateException {
return String.format("%s%09d", prefix, ((Long) super.generate(session, obj)));
}
}
и сущность
@Data
@EqualsAndHashCode(callSuper = false)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = Subject.NAME, uniqueConstraints = {@UniqueConstraint(name = "UC_SUBJECT",
columnNames = {"STUDY_KEY", "SUBJECT_UNIQUEID", "SUBJECT_UNIQUEID_SOURCE"})})
public class Subject extends Auditing {
static final String NAME = "SUBJECT";
static final String PREF = "SUBJ";
@Id
@GeneratedValue(generator = "subject_gen", strategy = GenerationType.SEQUENCE)
@GenericGenerator(name = "subject_gen", strategy = "com.awesome.db.AppIdGenerator", parameters = {
@org.hibernate.annotations.Parameter(name = "prefix", value = Subject.PREF),
@org.hibernate.annotations.Parameter(name = "sequence_name", value = Subject.NAME + SequenceStyleGenerator.DEF_SEQUENCE_SUFFIX),
@org.hibernate.annotations.Parameter(name = "increment_size", value = "1")
//@org.hibernate.annotations.Parameter(name = "initial_value", value = "21"),
//@org.hibernate.annotations.Parameter(name = "optimizer", value = "pooled-lo"),
})
@Column(name = "SUBJECT_KEY", unique = true, nullable = false, updatable = false, length = 13)
private String subjectKey;
@ManyToOne(optional = false)
@JoinColumn(name = "STUDY_KEY", referencedColumnName = "STUDY_KEY", foreignKey = @ForeignKey(name = "FK_STUDY_KEY"),
nullable = false, updatable = false)
private Study study;
@NotNull
@Column(name = "SUBJECT_UNIQUEID", length = 120)
private String subjectId;
@NotNull
@Column(name = "SUBJECT_UNIQUEID_SOURCE", length = 120)
private String subjectIdSource;
}
В результате:
- использует числовой счетчик последовательности БД
- поддерживает структуру базы данных и встроенные возможности оптимизатора
Имейте в виду, что если вы используете какие-либо сценарии заполнения, такие как import.sql/data.sql , тогда параметр Initial_value должен быть синхронизирован с количеством строк в таблице вручную (жестко закодирован или предоставлен из внешнего приложения) или nextval адаптирован к формату ключа строки идентификатора.
--import.sql
INSERT INTO SUBJECT (subject_key, subject_uniqueid, subject_uniqueid_source, created_date, modified_date, study_key) VALUES('SUBJ'||to_char(nextval('subject_seq'), 'FM000000009'),'1234','Other.App','2023-05-17 14:05:46.521000','2023-05-17 14:05:46.521000','STUD000000001');