【问题标题】:Session.flush() not working as expectedSession.flush() 没有按预期工作
【发布时间】:2025-12-03 22:10:02
【问题描述】:

在服务类中,我有一个从@Transactional 方法调用的方法。我已经验证在调用此代码时我有一个事务处于活动状态。我意识到我应该没有 DA 层,但我正在使用一个遗留应用程序,这使得以“正确”方式做事比现在值得做的事情更麻烦。

映射如下所示:

public class Foo {

  private String id;
  private Bar bar;

  @Id
  @Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
  @GeneratedValue(generator = "blahIdSeq")
  @GenericGenerator(name = "blahIdSeq",
                    strategy = "org.blah.CustomIdGenerator")
  public String getId() {return id;}

  @JoinColumn(name = "FOO_ID")
  @OneToOne(fetch = FetchType.LAZY, optional = false)
  public Bar getBar() { return bar; }

  // SETTERS INCLUDED

}

public class Bar {
  private String id;
  private Foo foo;

  @Id
  @Column(name = "FOO_ID")
  @GeneratedValue(generator = "someSeq")
  @GenericGenerator(name = "someSeq",
                    strategy = "foreign",
                    parameters = {
                      @Parameter(name = "property", value = "foo")
                    })
  public String getId() { return id; }

  @OneToOne(fetch = FetchType.LAZY)
  @PrimaryKeyJoinColumn(name = "FOO_ID")
  public Foo getFoo() { return foo; }

  // SETTERS INCLUDED

}

该方法如下所示:

public String createFoo(Foo foo) {
  Session ses = getSessionFactory().getCurrentSession();
  Bar bar = new Bar();
  bar.setFoo(foo);
  foo.setBar(bar);
  ses.save(foo);
  ses.save(bar);

  System.out.println(foo.getId()); // yields the ID value set by generator
  System.out.println(bar.getId()); // yields same ID value as above

  ses.flush();
  ses.refresh(foo);
}

现在,将 org.hibernate.SQL 日志记录设置为 DEBUG,我可以看到 Foo 和 Bar 的插入语句都已创建,但调用刷新后的刷新会引发 org.hibernate.UnresolvableObjectException: No row with the given identifier exists 异常。

是什么原因造成的?使用的数据库是Oracle 11gR2。

更新

我已将问题缩小到会话。似乎调用currentSession.flush() 并没有按预期将数据写入数据库以进行刷新。如果我注释掉该方法的其余部分,它将在最后提交,并且所有内容都将在数据库中。

但是,执行刷新/刷新不会返回水合对象,因此我不能在以后的事务中使用数据库填充的值(由列默认设置)。我也无法将事务拆分为多个事务,因为我需要能够在方法中的任何位置回滚。

关于为什么刷新没有为我提供数据库中可访问的数据的任何想法?

另一个更新

我已经移动了很多代码只是为了尝试隔离问题,但我仍然遇到问题。我还摆脱了两个实体之间的关系,尝试手动处理所有事情,看看这是否能解决问题。考虑到史蒂夫的所有 cmets,这就是我现在所拥有的:

public class Foo {

  private String id;
  private Bar bar;

  @Id
  @Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
  @GeneratedValue(generator = "blahIdSeq")
  @GenericGenerator(name = "blahIdSeq",
                    strategy = "org.blah.CustomIdGenerator")
  public String getId() {return id;}

  // SETTERS INCLUDED

}

public class Bar {
  private String id;
  private Foo foo;

  @Id
  @Column(name = "FOO_ID")
  public String getId() { return id; }

  // SETTERS INCLUDED

}

@Service('fooService')
@Transactional(readOnly = true)
class FooService {
  @Autowired
  SessionFactory sessionFactory // populated using Spring config:
                                // org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean

  @Transactional(readOnly = false)
  public void doSomeStuff(Foo fooToSave) {
    sessionFactory.getCurrentSession().saveOrUpdate(fooToSave);
    Bar bar = new Bar(fooToSave); // this populates the Bar.Id field
    sessionFactory.getCurrentSession().saveOrUpdate(bar);
    sessionFactory.getCurrentSession().flush();
    sessionFactory.getCurrentSession().refresh(fooToSave); // exception thrown here
  }
}

还有一个更新

在 Oracle 领域进行了相当多的尝试以确保 SQL 在同一个会话等上运行之后,我发现了问题所在。即使 Hibernate 记录了正在设置 SQL 绑定变量,但实际上并没有。使用 Oracle 11gR2 的V$SQL_BIND_CAPTURE,我能够看到使用最后执行的 SQL ID(验证为插入语句)有 24 个绑定变量,其中没有一个绑定了值。仍然不确定是什么导致这些值变为空白,但我离找到答案更近了一点。一定是我的映射有问题,我不能全部放在这里。

作为绑定变量,我猜 Oracle 不会因为无法插入而烦恼。 JDBC 通常只返回为 INSERT 语句插入的行数以进行验证,但我不确定 Hibernate 抽象是如何处理这些东西的。我目前正在使用 Hibernate 3.6.10——从 3.6.5 升级,看看它是否可以解决问题。它没有。 :P

我被误导了

忽略上面的“还有一个更新”部分。在事务提交之前,绑定变量似乎不会出现在V$SQL_BIND_CAPTURE 视图中。回到绘图板。

另一个修订版 - 我发誓我会被禁止

我决定回归基础。自从它处于工作状态以来,我发生了什么变化?主要是映射。一些服务层项目也发生了变化,但主要是将我们的 Hibernate 映射从 XML 移动到注释。因此,我采用了我一直在使用的相同服务方法,注释掉了所有其他内容,并尝试使用另一种持久对象类型来做与我尝试对Foo 做的事情完全相同的事情。你猜怎么着?这样可行。在这一点上,唯一可能导致我心痛的链接是我为Foo 提供的映射。我怀疑我的雇主是否希望我在 SO 上提供全部资源,所以我可能不得不自己解决这个问题。当我最终弄清楚时,我会以某种身份发布答案。

成功!但我不确定为什么...

这是给我带来麻烦的代码。请记住,BAZ 是一个链接表,其复合 ID 由 @Embeddable(在本示例中称为“键”)组成,由引用 FOO 表中的一行的 FOO_ID 和一个STATE_ID 引用另一个表。

public class Foo {

  // OTHER FIELDS INCLUDING IDs AND SUCH

  private Baz bazOfDoom;
  private Baz bazOfLight;
  private Set<Baz> allTheBaz;

  @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
  @JoinColumns({
    @JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
    @JoinColumn(name = "DOOM_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
  })
  public Baz getBazOfDoom() { return bazOfDoom; }

  @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
  @JoinColumns({
    @JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
    @JoinColumn(name = "LIGHT_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
  })
  public Baz getBazOfLight() { return bazOfLight; }

  @OneToMany(mappedBy = "key.foo", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
  public Set<Baz> getAllTheBaz() { return allTheBaz; }
}

我移除了级联并且它起作用了。我不知道为什么。谁能解释这将得到我的“正确答案”标记。 :)

【问题讨论】:

  • WRT 你的更新...不确定currentSession 应该引用什么。在您的代码示例中,您使用ses。那是一样的吗? flush() 将挂起的更改写入数据库。因此,如果ses.flush() 没有写入更改,那么ses 不认为有更改要写入。或者您有多个事务,并且隔离将它们彼此隔离。我们必须查看更多代码来判断这一点。最好将其简化为甚至只是一个方法调用(测试样式)并从那里开始工作。
  • 附带说明,我建议您不要一直手动刷新(),我建议您查看 Hibernate 的“生成的属性值”的概念。本质上,您告诉 Hibernate 某些属性的值将由 db 生成,并且它会在插入/更新后立即为您自动刷新该状态。见docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/…
  • 哇,谢谢。我一定会检查你留在那里的第二张纸条。我试过玩你在第一个笔记中提到的东西,但它似乎仍然有问题。是的,sescurrentSession 相同——对于不一致之处,请见谅。
  • 抱歉没有意识到你在中国工作:)
  • 我现在建议的是从等式中删除 Spring。这似乎确实是事务处理的问题。您确定 Spring 在事务期间正确管理连接吗?在“JTA​​”环境中,Hibernate 通常会使用它所谓的连接释放(意味着它获得一个连接,使用它并将其返回给数据源),这依赖于 JTA 保证在同一个事务中返回相同的连接这一事实。

标签: java spring hibernate


【解决方案1】:

您的对象在将其保存到数据库后似乎不拥有对象的标识符,从而导致调用 refresh() 时出现异常。

确实,假设您的数据库表拥有定义为 auto-increment 的主键。因此,当您保存第一个 Foo 对象时,主键列的值是:1

然而,Hibernate 必须在调用 save() 方法后知道这个新生成的标识符!

做到这一点的最佳方法是期望 Hibernate 在对象保存到数据库后立即重新影响良好的标识符。

因此,您可能会在实体类中错过这一行,以便在将对象保存在数据库中时自动提供标识符:

@GeneratedValue(strategy = GenerationType.AUTO)

当然,您不必自动生成它们,而是可以手动精确。

【讨论】:

  • 感谢您的回答。我在映射中的@OneToOne 关联上设置了GeneratedValue。我将使用适当的代码更新我的问题,以便让您更好地了解我正在处理的内容。