Hibernate JPA 序列(非 Id)
Posted
技术标签:
【中文标题】Hibernate JPA 序列(非 Id)【英文标题】:Hibernate JPA Sequence (non-Id) 【发布时间】:2010-09-21 14:45:21 【问题描述】:是否可以对不是标识符/不是复合标识符的一部分的某些列使用数据库序列?
我使用 hibernate 作为 jpa 提供程序,并且我有一个表,其中包含一些生成值(使用序列)的列,尽管它们不是标识符的一部分。
我想要的是使用序列为实体创建一个新值,其中序列的列是NOT(部分)主键:
@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 可以为主键生成值,为什么它不能为一个简单的属性生成值?
【问题讨论】:
【参考方案1】:在寻找这个问题的答案时,我偶然发现了this link
Hibernate/JPA 似乎无法自动为您的非 ID 属性创建值。 @GeneratedValue
注解仅与@Id
一起使用以创建自动编号。
@GeneratedValue
注释只是告诉 Hibernate 数据库正在自己生成这个值。
该论坛中建议的解决方案(或解决方法)是使用生成的 Id 创建一个单独的实体,如下所示:
@实体 公共类 GeneralSequenceNumber @ID @GeneratedValue(...) 私人长号; @实体 公共类 MyEntity @ID .. 私人长ID; @OneToOne(...) 私人 GeneralSequnceNumber myVal;【讨论】:
来自@GeneratedValue 的java doc:“GeneratedValue 注释可以与Id 注释一起应用于实体或映射超类的主键属性或字段” 我发现 @Column(columnDefinition="serial") 完美但仅适用于 PostgreSQL。对我来说这是完美的解决方案,因为第二个实体是“丑陋”的选择 @SergeyVedernikov 非常很有帮助。您介意将其作为单独的答案发布吗?它非常简单有效地解决了我的问题。 @MattBall 我已将此作为单独的答案发布:) ***.com/a/10647933/620858 我已经打开了一个提议,允许@GeneratedValue
用于不是 id 的字段。请投票加入 2.2 java.net/jira/browse/JPA_SPEC-113【参考方案2】:
我发现@Column(columnDefinition="serial")
工作完美,但仅适用于 PostgreSQL。对我来说这是完美的解决方案,因为第二个实体是“丑陋”的选择。
还需要在实体上调用saveAndFlush
,而save
不足以填充数据库中的值。
【讨论】:
嗨,我需要一个解释。你能告诉我更多吗? @EmaborsacolumnDefinition=
位基本上告诉 Hiberate 不要尝试生成列定义,而是使用您提供的文本。本质上,您的列的 DDL 将只是名称 + columnDefinition。在这种情况下(PostgreSQL),mycolumn serial
是表中的有效列。
mysql 的等价物是@Column(columnDefinition = "integer auto_increment")
这会自动产生它的价值吗?我尝试使用这样的字段定义来持久化实体,但它没有生成值。它在列 @Column(insertable = false, updatable = false, columnDefinition="serial")
来防止休眠尝试插入空值或更新字段。然后,如果您需要立即使用它,则需要在插入后重新查询数据库以获取生成的 id。【参考方案3】:
我知道这是一个非常古老的问题,但它首先显示在结果中,并且 jpa 自该问题以来发生了很大变化。
现在正确的做法是使用@Generated
注释。您可以定义序列,将列中的默认值设置为该序列,然后将列映射为:
@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)
【讨论】:
这仍然需要数据库生成值,这并不能真正回答问题。对于 12c 之前的 Oracle 数据库,您仍然需要编写数据库触发器来生成值。 另外,这是一个 Hibernate 注释,而不是 JPA。【参考方案4】: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 确实支持它们。
【讨论】:
@Generated 注解对应上面的 XML 配置。有关详细信息,请参阅this section of the hibernate docs。【参考方案5】:我使用 @PrePersist
注释修复了 Hibernate 的 UUID(或序列)的生成:
@PrePersist
public void initializeUUID()
if (uuid == null)
uuid = UUID.randomUUID().toString();
【讨论】:
这绝对是更清洁的解决方案恕我直言。【参考方案6】:下面是我如何让它工作的后续:
@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();
【讨论】:
Hibernate 4.2.19 和 oracle 的变体:SQLQuery sqlQuery = getSession().createSQLQuery("select NAMED_SEQ.nextval seq from dual"); sqlQuery.addScalar("seq", LongType.INSTANCE); return (Long) sqlQuery.uniqueResult();
【参考方案7】:
虽然这是一个旧线程,但我想分享我的解决方案,并希望得到一些反馈。请注意,我仅在某些 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。请注意,我使用 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。
希望这对某人有所帮助。
【讨论】:
【参考方案8】:如果你使用的是 postgresql 我在 spring boot 1.5.6 中使用
@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;
【讨论】:
它也对我有用,我使用的是 spring boot 2.1.6.RELEASE,Hibernate 5.3.10.Final,除了已经指出的之外,我还必须创建一个 secuenceseq_order
并从字段中引用,nextval('seq_order'::regclass)
【参考方案9】:
看起来线程很旧,我只是想在此处添加我的解决方案(在春季使用 AspectJ - AOP)。
解决方法是创建一个自定义注解@InjectSequenceValue
,如下所示。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue
String sequencename();
现在您可以注释实体中的任何字段,以便在运行时使用序列的下一个值注入底层字段(Long/Integer)值。
像这样注释。
//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;
【讨论】:
【参考方案10】:我在和你一样的情况下运行,如果基本上可以用 JPA 生成非 id 属性,我也没有找到任何严肃的答案。
我的解决方案是使用原生 JPA 查询调用序列,以便在持久化之前手动设置属性。
这并不令人满意,但目前可以作为一种解决方法。
马里奥
【讨论】:
【参考方案11】:我在 JPA 规范的会话 9.1.9 GeneratedValue Annotation 中找到了这个特定的注释: “[43] 便携式应用程序不应在其他持久字段或属性上使用 GeneratedValue 注释。” 因此,我认为至少使用 JPA 来自动生成非主键值是不可能的。
【讨论】:
【参考方案12】:我想在@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
旁边的 package-private 中,以使其成为包的实现细节:
@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);
我没有在实际应用中对其进行测试,因此请谨慎使用。
【讨论】:
【参考方案13】:“我不想使用触发器或除 Hibernate 本身之外的任何其他东西来为我的属性生成值”
在这种情况下,如何创建生成所需值的 UserType 实现,并配置元数据以使用该 UserType 来持久化 mySequenceVal 属性?
【讨论】:
【参考方案14】:这与使用序列不同。使用序列时,您不会插入或更新任何内容。您只是在检索下一个序列值。貌似hibernate不支持。
【讨论】:
【参考方案15】:如果您有一列具有 UNIQUEIDENTIFIER 类型并且插入时需要默认生成但列不是 PK
@Generated(GenerationTime.INSERT)
@Column(nullable = false , columnDefinition="UNIQUEIDENTIFIER")
private String uuidValue;
在数据库中你将拥有
CREATE TABLE operation.Table1
(
Id INT IDENTITY (1,1) NOT NULL,
UuidValue UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL)
在这种情况下,您不会为您需要的值定义生成器(它将自动感谢columnDefinition="UNIQUEIDENTIFIER"
)。您也可以尝试其他列类型
【讨论】:
【参考方案16】:我在 Spring 应用程序中使用 @PostConstruct 和 JdbcTemplate 在 MySql 数据库上找到了解决方法。它可能适用于其他数据库,但我将介绍的用例基于我对 MySql 的经验,因为它使用 auto_increment。
首先,我尝试使用 @Column 注释的 ColumnDefinition 属性将列定义为 auto_increment,但它不起作用,因为列需要成为键才能自动递增,但显然该列不会被定义为一个索引,直到它被定义之后,导致死锁。
这就是我产生的想法,即在没有 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");
【讨论】:
【参考方案17】:我今天正在为此苦苦挣扎,能够使用它来解决
@Generated(GenerationTime.INSERT)
@Column(name = "internal_id", columnDefinition = "serial", updatable = false)
private int internalId;
【讨论】:
【参考方案18】:我遇到过像你这样的情况(非 @Id 字段的 JPA/Hibernate 序列),我最终在我的数据库模式中创建了一个触发器,该触发器在插入时添加了一个唯一的序列号。我只是从来没有让它与 JPA/Hibernate 一起工作
【讨论】:
【参考方案19】:花了几个小时后,这巧妙地帮助我解决了我的问题:
对于 Oracle 12c:
ID NUMBER GENERATED as IDENTITY
对于 H2:
ID BIGINT GENERATED as auto_increment
同时制作:
@Column(insertable = false)
【讨论】:
以上是关于Hibernate JPA 序列(非 Id)的主要内容,如果未能解决你的问题,请参考以下文章
为啥在 JPA 2/Hibernate 中使用共享主键时实体需要可序列化?
当为 Postgres DB 的属性 spring.jpa.hibernate.ddl-auto 提供更新值时,Hibernate 不会生成序列