Спящий режим последовательности JPA (не Id)
можно ли использовать последовательность DB для некоторого столбца, который не является идентификатором/не является частью составного идентификатора?
Я использую 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, чтобы генерировать значение для моего свойства. Если Спящий Режим может генерировать значения для первичных ключей, почему он не может генерировать для простого свойства?
13 ответов:
ищу ответы на эту проблему, я наткнулся на этой ссылке
похоже, что Hibernate / JPA не может автоматически создавать значение для ваших неидентификационных свойств. Элемент
@GeneratedValueаннотация используется только в сочетании с@Idдля создания автоматических номеров.The
@GeneratedValueаннотация просто сообщает Hibernate, что база данных сама генерирует это значение.решение (или обходной путь), предложенное на этом форуме, заключается в создании отдельный объект с сгенерированным идентификатором, что-то вроде этого:
@Entity public class GeneralSequenceNumber { @Id @GeneratedValue(...) private Long number; } @Entity public class MyEntity { @Id .. private Long id; @OneToOne(...) private GeneralSequnceNumber myVal; }
Я нашел это
@Column(columnDefinition="serial")отлично работает, но только для PostgreSQL. Для меня это было идеальное решение, потому что вторая сущность-это "уродливый" вариант.
Я знаю, что это очень старый вопрос, но он показал сначала по результатам, и jpa сильно изменился с момента вопроса.
правильный способ сделать это сейчас-с
@GeneratedПримечание. Вы можете определить последовательность, установить значение по умолчанию в столбце В этой последовательности, а затем сопоставить столбец как:@Generated(GenerationTime.INSERT) @Column(name = "column_name", insertable = false)
спящий режим, безусловно, поддерживает это. Из документов:
"генерируемые свойства-это свойства, значения которых генерируются базой данных. Как правило, Hibernate приложения необходимы для обновления объектов, которые содержат любые свойства, для которых база данных генерирует значения. Однако маркировка свойств как сгенерированных позволяет приложению делегировать эту ответственность в спящий режим. По сути, всякий раз, когда Hibernate выдает SQL-вставку или обновление для сущности, которая имеет создается определенными свойствами, он немедленно выдает выберите затем для получения сгенерированных значений."
для свойств, созданных только при вставке, сопоставление свойств (.hbm.xml) будет выглядеть так:
<property name="foo" generated="insert"/>для свойств, созданных при вставке и обновлении сопоставления свойств (.hbm.xml) будет выглядеть так:
<property name="foo" generated="always"/>к сожалению, я не знаю JPA, поэтому я не знаю, открыта ли эта функция через JPA (я подозреваю, возможно нет)
кроме того, вы должны иметь возможность исключить свойство из вставок и обновлений, а затем "вручную" вызвать сеанс.обновить (obj); после того, как вы вставили/обновили его, чтобы загрузить сгенерированное значение из базы данных.
вот как вы бы исключили свойство из использования в инструкциях insert и update:
<property name="foo" update="false" insert="false"/>опять же, я не знаю, предоставляет ли JPA эти функции Hibernate, но Hibernate поддерживает их.
в качестве продолжения вот как я получил его на работу:
@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 testcase. Так что это пока не продуктивная функция.
Я решил эту проблему для себя, введя пользовательскую аннотацию под названием 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, который содержит текущее значение последовательности и размер приращения. Я выбрал имя класса как уникальный ключ, поэтому каждый класс сущности получит свою собственную последовательность.
@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.
надеюсь, это кому-то поможет.
Я бегу в той же ситуации, что и вы, и я также не нашел серьезных ответов, если в принципе можно генерировать неидентифицированные свойства с помощью JPA или нет.
мое решение-вызвать последовательность с помощью собственного запроса JPA, чтобы установить свойство вручную перед его сохранением.
Это не удовлетворяет, но он работает как обходной путь на данный момент.
Марио
я исправил генерацию UUID (или последовательностей) с помощью Hibernate с помощью
@PrePersistаннотация:@PrePersist public void initializeUUID() { if (uuid == null) { uuid = UUID.randomUUID().toString(); } }
Я нашел эту конкретную заметку в аннотации GeneratedValue сеанса 9.1.9 из спецификации JPA: "[43] портативные приложения не должны использовать аннотацию GeneratedValue для других постоянных полей или свойств." Итак, я предполагаю, что невозможно автоматически генерировать значение для значений не первичного ключа, по крайней мере, используя просто JPA.
"Я не хочу использовать триггер или любую другую вещь, кроме Hibernate, чтобы генерировать значение для моего свойства"
в этом случае, как насчет создания реализации UserType, которая генерирует требуемое значение, и настройки метаданных для использования этого UserType для сохранения свойства mySequenceVal?
Это не то же самое, что использовать последовательность. При использовании последовательности вы ничего не вставляете и не обновляете. Вы просто получаете следующее значение последовательности. Похоже, что hibernate не поддерживает его.
Я был в такой ситуации, как вы (последовательность JPA/Hibernate для поля non @Id), и я в конечном итоге создал триггер в своей схеме БД, который добавляет уникальный порядковый номер при вставке. Я просто никогда не получал его для работы с JPA / Hibernate
проведя несколько часов, это аккуратно помогло мне решить мою проблему:
для Oracle 12c:
ID NUMBER GENERATED as IDENTITYН2:
ID BIGINT GENERATED as auto_incrementтакже:
@Column(insertable = false)
Comments