Можно ли использовать последовательность БД для некоторого столбца, который не является идентификатором / не является частью составного идентификатора ?
Я использую спящий режим в качестве поставщика 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 может генерировать значения для первичных ключей, почему он не может генерировать для простого свойства?
@GeneratedValue
поля, которые не являются идентификаторами. Проголосуйте за включение в 2.2 java.net/jira/browse/JPA_SPEC-113Я обнаружил, что это
@Column(columnDefinition="serial")
отлично работает, но только для PostgreSQL. Для меня это было идеальным решением, потому что вторая сущность - «уродливый» вариант.Также необходим вызов
saveAndFlush
сущности, иsave
этого недостаточно для заполнения значения из БД.источник
columnDefinition=
бит в основном указывает Hiberate не пытаться сгенерировать определение столбца и вместо этого использовать текст, который вы указали. По сути, ваш DDL для столбца будет буквально просто name + columnDefinition. В этом случае (PostgreSQL)mycolumn serial
- допустимый столбец в таблице.@Column(columnDefinition = "integer auto_increment")
@Column(insertable = false, updatable = false, columnDefinition="serial")
чтобы спящий режим не пытался вставлять нулевые значения или обновлять поле. Затем вам нужно повторно запросить базу данных, чтобы получить сгенерированный идентификатор после вставки, если вам нужно использовать его сразу.Я знаю, что это очень старый вопрос, но он сначала проявился по результатам, и jpa сильно изменилась с момента появления вопроса.
Правильный способ сделать это сейчас - использовать
@Generated
аннотацию. Вы можете определить последовательность, установить значение по умолчанию в столбце для этой последовательности, а затем сопоставить столбец как:@Generated(GenerationTime.INSERT) @Column(name = "column_name", insertable = false)
источник
Hibernate определенно поддерживает это. Из документов:
«Сгенерированные свойства - это свойства, значения которых генерируются базой данных. Обычно приложениям Hibernate необходимо обновлять объекты, содержащие любые свойства, для которых база данных генерировала значения. Однако пометка свойств как сгенерированных позволяет приложению делегировать эту ответственность Hibernate. По сути, всякий раз, когда Hibernate выдает SQL INSERT или UPDATE для объекта, который определил сгенерированные свойства, он сразу же после этого выдает select для извлечения сгенерированных значений ».
Для свойств, созданных только при вставке, ваше сопоставление свойств (.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 их поддерживает.
источник
В качестве продолжения вот как я заставил это работать:
@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(); }
источник
SQLQuery sqlQuery = getSession().createSQLQuery("select NAMED_SEQ.nextval seq from dual"); sqlQuery.addScalar("seq", LongType.INSTANCE); return (Long) sqlQuery.uniqueResult();
Я исправил генерацию UUID (или последовательностей) с помощью Hibernate, используя
@PrePersist
аннотацию:@PrePersist public void initializeUUID() { if (uuid == null) { uuid = UUID.randomUUID().toString(); } }
источник
Хотя это старый поток, я хочу поделиться своим решением и, надеюсь, получить отзывы по этому поводу. Имейте в виду, что я тестировал это решение только с моей локальной базой данных в каком-то тестовом примере JUnit. Так что пока это не продуктивная функция.
Я решил эту проблему за себя, представив настраиваемую аннотацию под названием «Последовательность без свойства». Это просто маркер для полей, которым следует присвоить значение из увеличивающейся последовательности.
@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 для каждого класса сущности и резервирует пару порядковых номеров, определенных инкрементным значением сущности SequenceNumber. Если у него заканчиваются порядковые номера, он загружает сущность SequenceNumber для целевого класса и резервирует значения incrementValue для следующих вызовов. Таким образом, мне не нужно запрашивать базу данных каждый раз, когда требуется значение последовательности. Обратите внимание на сеанс StatelessSession, который открывается для резервирования следующего набора порядковых номеров. Вы не можете использовать тот же сеанс, в котором целевая сущность в настоящее время сохраняется, поскольку это приведет к исключению ConcurrentModificationException в EntityPersister.
Надеюсь, это кому-то поможет.
источник
Если вы используете postgresql,
и я использую весеннюю загрузку 1.5.6
@Column(columnDefinition = "serial") @Generated(GenerationTime.INSERT) private Integer orderID;
источник
seq_order
и сделать ссылку из поля,nextval('seq_order'::regclass)
Я столкнулся с той же ситуацией, что и вы, и я также не нашел серьезных ответов, можно ли генерировать свойства без идентификаторов с помощью JPA или нет.
Мое решение - вызвать последовательность с помощью собственного запроса JPA, чтобы вручную установить свойство, прежде чем сохранять его.
Это не удовлетворительно, но на данный момент работает как временное решение.
Марио
источник
Я нашел это конкретное примечание в сеансе 9.1.9. Аннотация GeneratedValue из спецификации JPA: «[43] Переносимые приложения не должны использовать аннотацию GeneratedValue для других постоянных полей или свойств». Итак, я предполагаю, что невозможно автоматически сгенерировать значение для значений, отличных от первичных ключей, по крайней мере, используя просто JPA.
источник
Похоже, поток устарел, я просто хотел добавить сюда свое решение (используя 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; }
источник
«Я не хочу использовать триггер или что-то еще, кроме самого Hibernate, для генерации значения моего свойства»
В этом случае, как насчет создания реализации UserType, которая генерирует требуемое значение, и настройки метаданных для использования этого UserType для сохранения свойства mySequenceVal?
источник
Это не то же самое, что использовать последовательность. При использовании последовательности вы ничего не вставляете и не обновляете. Вы просто получаете следующее значение последовательности. Похоже, спящий режим его не поддерживает.
источник
Если у вас есть столбец с типом 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"
). То же самое вы можете попробовать для других типов столбцовисточник
Я нашел обходной путь для этого в базах данных 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"); } }
источник
Я хочу предложить альтернативу принятому решению @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
. Я поместил его package-private рядом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
get. Он должен продемонстрировать принцип:@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); } }
Я не тестировал его в реальных приложениях, поэтому, пожалуйста, наслаждайтесь.
источник
Сегодня я боролся с этим, смог решить, используя этот
@Generated(GenerationTime.INSERT) @Column(name = "internal_id", columnDefinition = "serial", updatable = false) private int internalId;
источник
Я был в такой ситуации, как вы (последовательность JPA / Hibernate для поля, отличного от @Id), и в итоге я создал триггер в моей схеме db, который добавляет уникальный порядковый номер при вставке. Мне просто так и не удалось заставить его работать с JPA / Hibernate
источник
Проведя часы, это помогло мне решить мою проблему:
Для Oracle 12c:
Для H2:
Также производят:
@Column(insertable = false)
источник