用于从序列生成 id 的 Oracle 触发器的 HIbernate 问题

Posted

技术标签:

【中文标题】用于从序列生成 id 的 Oracle 触发器的 HIbernate 问题【英文标题】:HIbernate issue with Oracle Trigger for generating id from a sequence 【发布时间】:2011-12-21 13:46:55 【问题描述】:

我们有一个插入前触发器,可以从序列中获取下一个值。当对象使用 save() 方法持久化时,hibernate 从序列中获取值并将其添加到对象中。当事务从 Spring 的服务层提交时,ID 值在数据库上再次增加。如果对象已经有一个 id,我如何避免得到 nextval() ..

这就是我想做的事情..

用户道

public User saveUser(User user)
      session.getCurrentSession.save(user);//line2
      return user;//line3  
 

用户服务

public void saveUserAndWriteToAudit(User user, UserAudit userAudit)
  userDao.saveUser(user);//line1
  userAudit.setUserId(user.getId);//line4
  userAudit.saveUserAudit(userAudit);//line5

还有用户类

 @Entity
  public class User

     @Id
     @GeneratedValue(strategy=GenerationType.AUTO, generator="a1")
     @SequenceGenerator(name="a1", sequenceName="usersequence")
     private Long id;
     /////////////////
 

当光标到达 line1 并且 line2 用户对象的 id 属性为 null 时。在第 2 行之后,它具有序列中的下一个值 - 比如说 1。在第 4 行,我已将用户的 id=1 添加到 useraudit 对象。当在第 5 行之后提交事务时,将 2 插入到用户的 id 列中,将 1 插入到 UserAudit 的 userId 列中。这对我没有任何意义:(我该如何避免这个问题?谢谢!

【问题讨论】:

【参考方案1】:

只需将您的触发器更新为仅在未指定 ID 时触发。

create or replace
trigger sa.my_trigger
before insert on sa.my_table
for each row
when (new.id is null)
begin
   select sa.my_sequence.nextval
    into :new.id
    from dual;
end;

【讨论】:

试过了,但是当我从 JPA 进行一些插入,然后通过 SQL 进行一些插入时,序列不是最新的,并且在使用 SQL 时出现“违反唯一约束”。我在实体中有@GeneratedValue@SequenceGenerator 注释。【参考方案2】:

上面的解决方案很棒,它让我在这个问题上省了很多麻烦。

我唯一的抱怨是它为用户/代码打开了插入任何 ID 值而不实际查询序列的大门。

我发现以下解决方案也有效。它让 Hibernate 找到最大 ID 并在每次执行插入语句时增加它。但是当它到达数据库时,ID被忽略并被触发器生成的ID替换,因此没有uniqueness in a cluster problem:

    @Id
    @GeneratedValue(generator="increment")
    @GenericGenerator(name="increment", strategy = "increment")
    private Long id;

最大的缺点是@GenericGenerator 是一个Hibernate 注解,所以你失去了JPA 的可移植性。程序员也不清楚这个 ID 实际上是链接到一个序列,而事实上,它是使用序列的最紧密耦合的解决方案之一。

【讨论】:

感谢 Christopher,我一直在努力使用数据库触发器来填充主键,这解决了问题。 这也适用于我。无需更改 Oracle DB 中的 DB 触发器。非常感谢! 已更新!在我收到“违反唯一约束”错误之前,这一切正常。如前所述,执行此操作后 DB 序列不同步。同样在同一个表中插入数据的 Web 服务出现错误,因为 nextval 正在获取一个已经存在的值。【参考方案3】:

理想情况下,您应该删除 BEFORE INSERT TRIGGER。如果你不这样做,Hibernate 就无法知道主键,它确实需要这些信息。如果您不关心 INSERT 之后的对象,那可能没问题(二级缓存仍然是一个问题),但如果您需要立即使用它,那就是一个真正的问题。在后一种情况下,您可以尝试这种讨厌的方法:

    告诉 Hibernate 您自己管理主键。 创建新对象并将任意值放入主键。 刷新您的 Hibernate 会话和 SELECT sequence.CURRVAL(假设只有一个 INSERT)。 使用获取的当前序列值加载对象,不要使用上面的实例。

【讨论】:

@FelixM.. 感谢您的回复.. 如果我们修改触发器以检查 :new.id=null 与否,这行得通吗?.. 我无法测试它,因为我没有 db修改触发器的权限..在要求 dba 修改它之前,我需要确保它足够 如果您按照其他响应中的说明修改触发器,则可以告诉 Hibernate 使用该序列。【参考方案4】:
create or replace
trigger sa.my_trigger
before insert on sa.my_table
for each row
when (new.id is null)
begin
   select sa.my_sequence.nextval
    into :new.id
    from dual;
end;

这在数据库世界中确实有问题。

考虑到上述解决方案,假设 sa.my_sequence.nextval 为 51,您的休眠框架将毫无问题地工作。但是,如果有人使用您的主键值作为 66 覆盖序列(例如,当前值为 52)进行直接 jdbc 插入,则触发器只会插入。

真正的问题是当序列值增加到 66 时,这将在触发器中引发异常,以重置序列值结束。这实际上以数据库方面的架构设计结构不良而告终。

【讨论】:

以上是关于用于从序列生成 id 的 Oracle 触发器的 HIbernate 问题的主要内容,如果未能解决你的问题,请参考以下文章

用于在表上自动生成 ID 的序列和触发 PL/SQL 脚本

将 Oracle 序列增加一定数量

使用从序列对象生成的数据库 ID 创建 Oracle 视图

ORACLE 如何生成序号

如何使用 @ID 和 @GeneratedValue 从 Hibernate + JPA 中的序列中获取 Oracle 生成的值

oracle 自增序列与触发器