hibernateJPA序列(非Id)

是否有可能使用一个DB序列的某些列不是标识符/不是一个复合标识符的一部分

我使用hibernate作为jpa提供程序,并且我有一个表有一些生成值的列(使用序列),尽pipe它们不是标识符的一部分。

我想要的是使用一个序列来创build一个实体的新值,其中序列的列不是主键的一部分:

@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()); 

该id将被生成,但mySequenceVal属性也将由我的JPA提供程序生成。

只是为了弄清楚事情:我希望Hibernate生成mySequencedValue属性的值。 我知道Hibernate可以处理数据库生成的值,但我不想使用触发器或除Hibernate之外的其他任何东西来为我的属性生成值。 如果Hibernate可以为主键生成值,为什么不能生成一个简单的属性?

寻找这个问题的答案,我偶然发现这个链接

看来Hibernate / JPA不能为你的非id属性自动创build一个值。 @GeneratedValue注释仅用于与@Id一起创build自动编号。

@GeneratedValue注解只是告诉Hibernate数据库本身正在生成这个值。

该论坛build议的解决scheme(或解决方法)是使用生成的ID创build一个单独的实体,如下所示:

 @实体
公共类GeneralSequenceNumber {
   @ID
   @GeneratedValue(...)
  私人长号码;
 }

 @实体 
公共类MyEntity {
   @ID ..
  私人长ID;

   @OneToOne(...)
  私人GeneralSequnceNumber myVal;
 }

我发现@Column(columnDefinition="serial")工作完美,但只适用于PostgreSQL。 对我来说这是完美的解决scheme,因为第二个实体是“丑陋的”选项。

hibernate绝对支持这一点。 从文档:

“生成的属性是由数据库生成值的属性,通常情况下,Hibernate应用程序需要刷新包含数据库生成值的任何属性的对象,然而,将属性标记为生成的,可以让应用程序将这个责任委托给Hibernate。从本质上讲,每当Hibernate为已定义生成属性的实体发出一个SQL INSERT或UPDATE时,它立即发出一个select来检索生成的值。

对于仅在插入时生成的属性,属性映射(.hbm.xml)将如下所示:

 <property name="foo" generated="insert"/> 

对于在插入和更新属性映射(.hbm.xml)时生成的属性如下所示:

 <property name="foo" generated="always"/> 

不幸的是,我不知道JPA,所以我不知道这个function是否通过JPA暴露(我猜可能不是)

或者,你应该能够从插入和更新排除属性,然后“手动”调用session.refresh(obj); 在插入/更新它之后,从数据库加载生成的值。

这就是你将如何排除在插入和更新语句中使用的属性:

 <property name="foo" update="false" insert="false"/> 

再次,我不知道JPA是否公开这些Hibernate特性,但是Hibernate确实支持它们。

我知道这是一个非常古老的问题,但首先显示结果,自从问题出现以后,jpa已经发生了很大的变化。

现在做的正确方法是使用@Generated注释。 您可以定义序列,将列中的默认值设置为该序列,然后将列映射为:

 @Generated(GenerationTime.INSERT) @Column(name = "column_name", insertable = false) 

作为一个后续,我是如何得到它的工作:

 @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(); } 

虽然这是一个古老的线程,我想分享我的解决scheme,并希望得到一些反馈。 被警告我只在一些JUnittesting用例中用我的本地数据库testing了这个解决scheme。 所以到目前为止这不是一个有效的function。

我解决了这个问题,我介绍了一个名为序列没有属性的自定义注释。 这只是一个字段的标记,应该从递增的序列中分配一个值。

 @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的实体,它保存序列当前值和增量大小。 我select了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。 请注意,我用spring作为bean容器。

 @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实体的incrementValue定义的几个序列号。 如果序列号用完,则加载目标类的SequenceNumber实体,并为下一次调用保留incrementValue值。 这样,我不需要每次需要序列值时查询数据库。 请注意正在打开的StatelessSession用于保留下一组序号。 您不能使用目标实体当前保持的同一个会话,因为这会导致EntityPersister中发生ConcurrentModificationException。

希望这有助于某人。

我和你一样运行,如果基本上可以使用JPA生成非id属性,我也没有find任何认真的答案。

我的解决scheme是使用本机JPA查询来调用该序列,以在持久化之前手动设置该属性。

这不是令人满意的,但它现在作为一个解决方法。

马里奥

我使用@PrePersist批注用Hibernate修复了UUID(或序列)的生成:

 @PrePersist public void initializeUUID() { if (uuid == null) { uuid = UUID.randomUUID().toString(); } } 

我在JPA规范的会话9.1.9 GeneratedValue Annotation中发现了这个特定的注释:“[43]可移植应用程序不应该在其他持久字段或属性上使用GeneratedValue注解。 所以,我认为不可能至less只使用JPA自动为非主键值生成值。

我一直在像你一样的情况(非@Id字段的JPA /hibernate序列),最后在我的数据库模式中创build了一个触发器,在插入时添加一个唯一的序列号。 我只是从来没有得到它与J​​PA /hibernate工作

“我不想使用触发器或除Hibernate之外的其他任何东西来为我的属性生成值”

在这种情况下,如何创build生成所需值的UserType的实现,并configuration元数据以使用该UserType来保存mySequenceVal属性?

这与使用序列不一样。 当使用序列时,您不插入或更新任何东西。 您只需检索下一个序列值。 它看起来像hibernate不支持它。