Hibernate JPA Sequence (без идентификатора)
Можно ли использовать последовательность БД для некоторого столбца, который не является идентификатором / не является частью составного идентификатора?
Я использую hibernate в качестве провайдера jpa, и у меня есть таблица, в которой есть несколько столбцов, которые являются сгенерированными значениями (используя последовательность), хотя они не являются частью идентификатора.
Я хочу использовать последовательность для создания нового значения для сущности, где столбец для последовательности НЕ является (частью) первичного ключа:
@Entity
@Table(name = "MyTable")
public class MyEntity {
//...
@Id //... etc
public Long getId() {
return id;
}
//note NO @Id here! but this doesn't work...
@GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
@SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
@Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
public Long getMySequencedValue(){
return myVal;
}
}
Затем, когда я делаю это:
em.persist(new MyEntity());
идентификатор будет сгенерирован, но mySequenceVal
Свойство также будет сгенерировано моим JPA провайдером.
Просто чтобы прояснить ситуацию: я хочу, чтобы Hibernate генерировал значение для mySequencedValue
имущество. Я знаю, что Hibernate может обрабатывать значения, сгенерированные базой данных, но я не хочу использовать триггер или любую другую вещь, кроме самого Hibernate, для генерации значения для моего свойства. Если Hibernate может генерировать значения для первичных ключей, почему он не может генерировать для простого свойства?
23 ответа
Ища ответы на эту проблему, я наткнулся на эту ссылку
Похоже, что Hibernate/JPA не может автоматически создать значение для ваших не-id-свойств. @GeneratedValue
аннотация используется только в сочетании с @Id
создавать авто-номера.
@GeneratedValue
аннотация просто сообщает Hibernate, что база данных сама генерирует это значение.
Решение (или обходное решение), предложенное на этом форуме, заключается в создании отдельной сущности с сгенерированным идентификатором, что-то вроде этого:
@Сущность открытый класс GeneralSequenceNumber { @Я бы @GeneratedValue(...) приватный длинный номер; } @Сущность открытый класс MyEntity { @Я бы.. приватный длинный идентификатор; @Один к одному(...) private GeneralSequnceNumber myVal; }
Я нашел это @Column(columnDefinition="serial")
отлично работает, но только для PostgreSQL. Для меня это было идеальное решение, потому что вторая сущность - это "безобразный" вариант.
Я знаю, что это очень старый вопрос, но он показывается в первую очередь по результатам, и jpa сильно изменился со времени вопроса.
Правильный способ сделать это сейчас с @Generated
аннотаций. Вы можете определить последовательность, установить значение по умолчанию в столбце для этой последовательности, а затем сопоставить столбец следующим образом:
@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
Hibernate определенно поддерживает это. Из документов:
"Сгенерированные свойства - это свойства, значения которых создаются базой данных. Как правило, приложениям Hibernate необходимо обновлять объекты, которые содержат любые свойства, для которых база данных генерировала значения. Однако пометка свойств как сгенерированных позволяет приложению делегировать эту ответственность Hibernate. По сути, всякий раз, когда Hibernate выдает SQL INSERT или UPDATE для объекта, который определил сгенерированные свойства, он сразу же выдает выбор для извлечения сгенерированных значений ".
Для свойств, созданных только для вставки, ваше сопоставление свойств (.hbm.xml) будет выглядеть следующим образом:
<property name="foo" generated="insert"/>
Для свойств, созданных при вставке и обновлении, ваше сопоставление свойств (.hbm.xml) будет выглядеть следующим образом:
<property name="foo" generated="always"/>
К сожалению, я не знаю JPA, поэтому я не знаю, доступна ли эта функция через JPA (я подозреваю, что нет)
Кроме того, вы должны иметь возможность исключить свойство из вставок и обновлений, а затем "вручную" вызвать session.refresh( obj); после того, как вы вставили / обновили его, чтобы загрузить сгенерированное значение из базы данных.
Вот как вы бы исключили использование свойства в операторах вставки и обновления:
<property name="foo" update="false" insert="false"/>
Опять же, я не знаю, предоставляет ли JPA эти функции Hibernate, но Hibernate поддерживает их.
Я исправил генерацию UUID (или последовательности) в Hibernate, используя @PrePersist
аннотация:
@PrePersist
public void initializeUUID() {
if (uuid == null) {
uuid = UUID.randomUUID().toString();
}
}
Похоже, что поток старый, я просто хотел добавить свое решение здесь (Использование AspectJ - AOP весной).
Решение заключается в создании пользовательской аннотации @InjectSequenceValue
следующее.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
String sequencename();
}
Теперь вы можете аннотировать любое поле в сущности, так что значение базового поля (длинное / целое) будет введено во время выполнения с использованием следующего значения последовательности.
Аннотировать, как это.
//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
@InjectSequenceValue(sequencename = "serialnum_sequence")
Long serialNumber;
До сих пор мы пометили поле, в которое нам нужно ввести значение последовательности. Итак, мы посмотрим, как ввести значение последовательности в отмеченные поля, это делается путем создания среза точки в AspectJ.
Мы запустим инъекцию непосредственно перед save/persist
Метод выполняется. Это делается в следующем классе.
@Aspect
@Configuration
public class AspectDefinition {
@Autowired
JdbcTemplate jdbcTemplate;
//@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
@Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
public void generateSequence(JoinPoint joinPoint){
Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
for (Object arg :aragumentList ) {
if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class
Field[] fields = arg.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields
field.setAccessible(true);
try {
if (field.get(arg) == null){ // Setting the next value
String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
long nextval=getNextValue(sequenceName);
System.out.println("Next value :"+nextval); //TODO remove sout.
field.set(arg, nextval);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* This method fetches the next value from sequence
* @param sequence
* @return
*/
public long getNextValue(String sequence){
long sequenceNextVal=0L;
SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
while (sqlRowSet.next()){
sequenceNextVal=sqlRowSet.getLong("value");
}
return sequenceNextVal;
}
}
Теперь вы можете комментировать любую сущность, как показано ниже.
@Entity
@Table(name = "T_USER")
public class UserEntity {
@Id
@SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
Long id;
String userName;
String password;
@InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
Long serialNumber;
String name;
}
В качестве продолжения вот как я заставил это работать:
@Override public Long getNextExternalId() {
BigDecimal seq =
(BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
return seq.longValue();
}
Хотя это старая ветка, я хочу поделиться своим решением и, надеюсь, получить некоторую обратную связь по этому вопросу. Имейте в виду, что я тестировал это решение только с моей локальной базой данных в некотором тестовом примере JUnit. Так что пока это не продуктивная особенность.
Я решил эту проблему для себя, введя пользовательскую аннотацию под названием Sequence без свойства. Это просто маркер для полей, которым должно быть присвоено значение из увеличенной последовательности.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}
Используя эту аннотацию, я отметил свои объекты.
public class Area extends BaseEntity implements ClientAware, IssuerAware
{
@Column(name = "areaNumber", updatable = false)
@Sequence
private Integer areaNumber;
....
}
Чтобы сохранить независимость базы данных, я ввел сущность SequenceNumber, которая содержит текущее значение последовательности и размер приращения. Я выбрал className в качестве уникального ключа, чтобы каждый класс сущности получал свою собственную последовательность.
@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
@Id
@Column(name = "className", updatable = false)
private String className;
@Column(name = "nextValue")
private Integer nextValue = 1;
@Column(name = "incrementValue")
private Integer incrementValue = 10;
... some getters and setters ....
}
Последний и самый сложный шаг - PreInsertListener, который обрабатывает присвоение порядкового номера. Обратите внимание, что я использовал весну как контейнер для фасоли.
@Component
public class SequenceListener implements PreInsertEventListener
{
private static final long serialVersionUID = 7946581162328559098L;
private final static Logger log = Logger.getLogger(SequenceListener.class);
@Autowired
private SessionFactoryImplementor sessionFactoryImpl;
private final Map<String, CacheEntry> cache = new HashMap<>();
@PostConstruct
public void selfRegister()
{
// As you might expect, an EventListenerRegistry is the place with which event listeners are registered
// It is a service so we look it up using the service registry
final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);
// add the listener to the end of the listener chain
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
}
@Override
public boolean onPreInsert(PreInsertEvent p_event)
{
updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());
return false;
}
private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
{
try
{
List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);
if (!fields.isEmpty())
{
if (log.isDebugEnabled())
{
log.debug("Intercepted custom sequence entity.");
}
for (Field field : fields)
{
Integer value = getSequenceNumber(p_entity.getClass().getName());
field.setAccessible(true);
field.set(p_entity, value);
setPropertyState(p_state, p_propertyNames, field.getName(), value);
if (log.isDebugEnabled())
{
LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
}
}
}
}
catch (Exception e)
{
log.error("Failed to set sequence property.", e);
}
}
private Integer getSequenceNumber(String p_className)
{
synchronized (cache)
{
CacheEntry current = cache.get(p_className);
// not in cache yet => load from database
if ((current == null) || current.isEmpty())
{
boolean insert = false;
StatelessSession session = sessionFactoryImpl.openStatelessSession();
session.beginTransaction();
SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);
// not in database yet => create new sequence
if (sequenceNumber == null)
{
sequenceNumber = new SequenceNumber();
sequenceNumber.setClassName(p_className);
insert = true;
}
current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
cache.put(p_className, current);
sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());
if (insert)
{
session.insert(sequenceNumber);
}
else
{
session.update(sequenceNumber);
}
session.getTransaction().commit();
session.close();
}
return current.next();
}
}
private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
{
for (int i = 0; i < propertyNames.length; i++)
{
if (propertyName.equals(propertyNames[i]))
{
propertyStates[i] = propertyState;
return;
}
}
}
private static class CacheEntry
{
private int current;
private final int limit;
public CacheEntry(final int p_limit, final int p_current)
{
current = p_current;
limit = p_limit;
}
public Integer next()
{
return current++;
}
public boolean isEmpty()
{
return current >= limit;
}
}
}
Как видно из приведенного выше кода, слушатель использовал один экземпляр SequenceNumber для каждого класса сущности и резервирует пару порядковых номеров, определяемых incrementValue сущности SequenceNumber. Если у него заканчиваются порядковые номера, он загружает сущность SequenceNumber для целевого класса и резервирует значения incrementValue для следующих вызовов. Таким образом, мне не нужно запрашивать базу данных каждый раз, когда требуется значение последовательности. Обратите внимание на StatelessSession, который открывается для резервирования следующего набора порядковых номеров. Вы не можете использовать тот же сеанс, целевая сущность которого в настоящее время сохраняется, так как это приведет к исключению ConcurrentModificationException в EntityPersister.
Надеюсь, это кому-нибудь поможет.
Если вы используете postgresql
И я использую весной загрузки 1.5.6
@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;
Я работаю в той же ситуации, что и вы, и я также не нашел серьезных ответов, возможно ли сгенерировать свойства без идентификатора с помощью JPA или нет.
Мое решение состоит в том, чтобы вызвать последовательность с собственным запросом JPA, чтобы установить свойство вручную, прежде чем сохранить его.
Это не удовлетворяет, но это работает как обходной путь на данный момент.
Марио
Я создал отдельную таблицу сущностей для создания и использовал ее, чтобы установить этот неосновной ключ в службе, которая содержит этот файл .
Сущность:
import lombok.Data;
@Entity
@Data
public class GeneralSeqGenerator {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_gen")
@SequenceGenerator(name = "my_gen", sequenceName= "my_seq", allocationSize = 1, initialValue = 100000)
private long seqNumber;
}
Репозиторий:
public interface GeneralSeqGeneratorRepository extends JpaRepository<GeneralSeqGenerator, Long>{
}
Реализация сервиса, который держит не первичныеid
:
...
public void saveNewEntity(...) {
...
newEntity.setNonPrimaryId(generalSeqGeneratorRepository.save(new GeneralSeqGenerator()).getSeqNumber());
...
}
...
Вы можете сделать именно то, что вы просите.
Я обнаружил, что можно адаптировать реализации Hibernate IdentifierGenerator , зарегистрировав их с помощью Integrator . При этом вы должны иметь возможность использовать любой генератор последовательностей идентификаторов, предоставленный Hibernate, для создания последовательностей для полей, отличных от идентификаторов (предположительно, генераторы непоследовательных идентификаторов также будут работать).
Существует довольно много вариантов для создания идентификаторов таким образом. Ознакомьтесь с некоторыми реализациями IdentifierGenerator, в частности с SequenceStyleGenerator и TableGenerator . Если вы настроили генераторы с помощью аннотации @GenericGenerator , то параметры этих классов могут быть вам знакомы. Это также имело бы преимущество использования Hibernate для генерации SQL.
Вот как я заработал:
import org.hibernate.Session;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.enhanced.TableGenerator;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.internal.SessionImpl;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.hibernate.tuple.ValueGenerator;
import org.hibernate.type.LongType;
import java.util.Properties;
public class SequenceIntegrator implements Integrator, ValueGenerator<Long> {
public static final String TABLE_NAME = "SEQUENCE_TABLE";
public static final String VALUE_COLUMN_NAME = "NEXT_VAL";
public static final String SEGMENT_COLUMN_NAME = "SEQUENCE_NAME";
private static SessionFactoryServiceRegistry serviceRegistry;
private static Metadata metadata;
private static IdentifierGenerator defaultGenerator;
@Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
//assigning metadata and registry to fields for use in a below example
SequenceIntegrator.metadata = metadata;
SequenceIntegrator.serviceRegistry = sessionFactoryServiceRegistry;
SequenceIntegrator.defaultGenerator = getTableGenerator(metadata, sessionFactoryServiceRegistry, "DEFAULT");
}
private TableGenerator getTableGenerator(Metadata metadata, SessionFactoryServiceRegistry sessionFactoryServiceRegistry, String segmentValue) {
TableGenerator generator = new TableGenerator();
Properties properties = new Properties();
properties.setProperty("table_name", TABLE_NAME);
properties.setProperty("value_column_name", VALUE_COLUMN_NAME);
properties.setProperty("segment_column_name", SEGMENT_COLUMN_NAME);
properties.setProperty("segment_value", segmentValue);
//any type should work if the generator supports it
generator.configure(LongType.INSTANCE, properties, sessionFactoryServiceRegistry);
//this should create the table if ddl auto update is enabled and if this function is called inside of the integrate method
generator.registerExportables(metadata.getDatabase());
return generator;
}
@Override
public Long generateValue(Session session, Object o) {
// registering additional generators with getTableGenerator will work here. inserting new sequences can be done dynamically
// example:
// TableGenerator classSpecificGenerator = getTableGenerator(metadata, serviceRegistry, o.getClass().getName());
// return (Long) classSpecificGenerator.generate((SessionImpl)session, o);
return (Long) defaultGenerator.generate((SessionImpl)session, o);
}
@Override
public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
}
}
Вам нужно будет зарегистрировать этот класс в каталоге META-INF/services. Вот что говорится в документации Hibernate о регистрации интегратора:
Чтобы интегратор автоматически использовался при запуске Hibernate, вам необходимо добавить в jar-файл файл META-INF/services/org.hibernate.integrator.spi.Integrator. Файл должен содержать полное имя класса, реализующего интерфейс.
Поскольку этот класс реализует класс ValueGenerator , его можно использовать с аннотацией @GeneratorType для автоматического создания последовательных значений. Вот как может быть настроен ваш класс:
@Entity
@Table(name = "MyTable")
public class MyEntity {
//...
@Id //... etc
public Long getId() {
return id;
}
@GeneratorType(type = SequenceIntegrator.class, when = GenerationTime.INSERT)
@Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
public Long getMySequencedValue(){
return myVal;
}
}
Я нашел это конкретное примечание в сеансе 9.1.9 Аннотация GeneratedValue из спецификации JPA: "[43] Переносимые приложения не должны использовать аннотацию GeneratedValue для других постоянных полей или свойств". Итак, я предполагаю, что невозможно автоматически сгенерировать значение для значений не первичного ключа, по крайней мере, используя просто JPA.
Я хочу предоставить альтернативу принятому решению @Morten Berg, которое лучше сработало для меня.
Такой подход позволяет определить поле с действительно желаемым Number
тип - Long
в моем случае - вместо GeneralSequenceNumber
. Это может быть полезно, например, для (де-) сериализации JSON.
Обратной стороной является то, что это требует немного больше накладных расходов на базу данных.
Во-первых, нам нужен ActualEntity
в котором мы хотим автоматически увеличивать generated
типа Long
:
// ...
@Entity
public class ActualEntity {
@Id
// ...
Long id;
@Column(unique = true, updatable = false, nullable = false)
Long generated;
// ...
}
Далее нам понадобится вспомогательная сущность Generated
. Я поместил его рядом сActualEntity
, чтобы сохранить детали реализации пакета:
@Entity
class Generated {
@Id
@GeneratedValue(strategy = SEQUENCE, generator = "seq")
@SequenceGenerator(name = "seq", initialValue = 1, allocationSize = 1)
Long id;
}
Наконец, нам нужно место для подключения прямо перед сохранением ActualEntity
. Там мы создаем и сохраняемGenerated
пример. Затем это обеспечивает сгенерированную последовательность базы данныхid
типа Long
. Мы используем это значение, записывая его вActualEntity.generated
.
В моем случае я реализовал это с помощью Spring Data REST @RepositoryEventHandler
, который вызывается прямо перед ActualEntity
получить настойчиво. Он должен продемонстрировать принцип:
@Component
@RepositoryEventHandler
public class ActualEntityHandler {
@Autowired
EntityManager entityManager;
@Transactional
@HandleBeforeCreate
public void generate(ActualEntity entity) {
Generated generated = new Generated();
entityManager.persist(generated);
entity.setGlobalId(generated.getId());
entityManager.remove(generated);
}
}
Я не тестировал его в реальных приложениях, поэтому, пожалуйста, наслаждайтесь.
Я нашел обходной путь для этого в базах данных MySql, используя @PostConstruct и JdbcTemplate в приложении Spring. Это может быть выполнено с другими базами данных, но вариант использования, который я представлю, основан на моем опыте работы с MySql, поскольку он использует auto_increment.
Во-первых, я попытался определить столбец как auto_increment с помощью свойства ColumnDefinition аннотации @Column, но он не работал, поскольку столбец должен был быть ключом для автоматического увеличения, но, очевидно, столбец не был определен как индекс до тех пор, пока он не был определен, вызывая тупик.
Вот где я пришел к идее создать столбец без определения auto_increment и добавить его после создания базы данных. Это возможно с помощью аннотации @PostConstruct, которая вызывает вызов метода сразу после инициализации bean-компонентов приложением в сочетании с методом обновления JdbcTemplate.
Код выглядит следующим образом:
В моей сущности:
@Entity
@Table(name = "MyTable", indexes = { @Index(name = "my_index", columnList = "mySequencedValue") })
public class MyEntity {
//...
@Column(columnDefinition = "integer unsigned", nullable = false, updatable = false, insertable = false)
private Long mySequencedValue;
//...
}
В классе PostConstructComponent:
@Component
public class PostConstructComponent {
@Autowired
private JdbcTemplate jdbcTemplate;
@PostConstruct
public void makeMyEntityMySequencedValueAutoIncremental() {
jdbcTemplate.update("alter table MyTable modify mySequencedValue int unsigned auto_increment");
}
}
@Column(name = "<column name>", columnDefinition = "serial")
Работает для MySQL
Это не то же самое, что использование последовательности. При использовании последовательности вы ничего не вставляете и не обновляете. Вы просто получаете следующее значение последовательности. Похоже, Hibernate не поддерживает его.
ИДК, это решение правильное или неправильное, но я откопал некоторый код и реализовал это. Кажется, работает правильно. Выполнено на MySQL .
Требование:
Требуется отдельный столбец, кроме того, который имеет уникальное значение, автоматически вставляется и должен вставлять значение в INSERT (а не в UPDATE).
Контекст:
- Допустим, у меня есть имя сущности
Bug
с кодом следующим образом:
public class Bug extends AbstractAuditingMappedEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// want column as auto increment non id column
@Column(unique = true, nullable = false, updatable = false)
private Integer bugKey;
// ... other things
}
Для достижения результата я сделал это:
Некоторые наблюдения:
- Генерировать случайные уникальные значения автоматической вставки сложно.
- Таким образом, единственный последовательный способ генерации — использовать первичный ключ.
Id
Стратегия поколения. - И из значений первичного ключа мы можем использовать любые
Injective Function
f(x) = y
для создания уникальных значений.
Процедура:
- Создайте собственный генератор, который извлекает значение
Ids
Вставленоtill now
и при этом мы можем использовать любойf(x)
, Я использовалf(x) = x
;
public class BugKeyValueGenerator implements BeforeExecutionGenerator {
private IntegralDataTypeHolder previousValueHolder;
@Override
public synchronized Object generate(SharedSessionContractImplementor session, Object obj, Object idk, EventType eventType) {
String sql = String.format("select max( %s ) as id from %s",
session.getEntityPersister(obj.getClass().getName(), obj)
.getIdentifierPropertyName(),
obj.getClass().getSimpleName());
// according to your Id type.
previousValueHolder = new IdentifierGeneratorHelper.BasicHolder(Integer.class);
try {
PreparedStatement st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql);
try {
ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract(st);
try {
if (rs.next()) {
previousValueHolder.initialize(rs, 0L).increment();
} else {
previousValueHolder.initialize(1L);
}
sql = null;
} finally {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release(rs, st);
}
} finally {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release(st);
session.getJdbcCoordinator().afterStatementExecution();
}
} catch (SQLException sqle) {
throw session.getJdbcServices().getSqlExceptionHelper().convert(
sqle,
"could not fetch initial value for increment generator",
sql
);
}
// you can use any `Injective function` .
// eg
// `f(x) = x` => to get same values as Id
// `f(x) = "some string" + x` => to get values as BDK1, BDK2 ... etc...
// `f(x) = 1000+x` => to get values as 1001, 1002 ... etc...
return previousValueHolder.makeValueThenIncrement();
}
@Override
public EnumSet<EventType> getEventTypes() {
return INSERT_ONLY;
}
}
- Создайте для этого специальную аннотацию.
@ValueGenerationType(generatedBy = BugKeyValueGenerator.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface BugKeyGenerator {
}
- Наконец, обновите класс Entity, используя эту аннотацию.
public class Bug extends AbstractAuditingMappedEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// update column with annotation
@Column(unique = true, nullable = false, updatable = false)
@BugKeyGenerator
private Integer bugKey;
// ... other things
}
Выводы:
- Может использоваться только в том случае, если первичные ключи имеют любое значение.
number
тип. - Может использоваться с
string
вводите первичные ключи, когда вы не будетеdelete
любая таблица формы записи. В этом случае вместо поискаmax
в запросе sql вы можете использоватьcount
для генерации нового уникального номера. - Вы также можете использовать это, чтобы
auto insert
ценности, основанные наother value
как 1 параметра (obj
), полученные, содержат значения для записи, которую необходимо вставить.
Если у вас есть столбец с типом UNIQUEIDENTIFIER и генерация по умолчанию, необходимая для вставки, но столбец не является PK
@Generated(GenerationTime.INSERT)
@Column(nullable = false , columnDefinition="UNIQUEIDENTIFIER")
private String uuidValue;
В db у вас будет
CREATE TABLE operation.Table1
(
Id INT IDENTITY (1,1) NOT NULL,
UuidValue UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL)
В этом случае вы не будете определять генератор для нужного вам значения (это будет автоматически благодаря columnDefinition="UNIQUEIDENTIFIER"
). То же самое вы можете попробовать для других типов столбцов
Сегодня я боролся с этим, смог решить, используя этот
@Generated(GenerationTime.INSERT)
@Column(name = "internal_id", columnDefinition = "serial", updatable = false)
private int internalId;
"Я не хочу использовать триггер или что-то другое, кроме самого Hibernate, для генерации значения для моего свойства"
В этом случае, как насчет создания реализации UserType, которая генерирует требуемое значение, и настройки метаданных для использования этого UserType для сохранения свойства mySequenceVal?
Я был в такой ситуации, как вы (последовательность JPA/Hibernate для поля, отличного от @Id), и в итоге я создал триггер в моей схеме БД, который добавляет уникальный порядковый номер при вставке. Я просто никогда не работал с JPA/Hibernate
Проведя часы, это аккуратно помогло мне решить мою проблему:
Для Oracle 12c:
ID NUMBER GENERATED as IDENTITY
Для H2:
ID BIGINT GENERATED as auto_increment
Также сделайте:
@Column(insertable = false)