Session.flush()未按预期工作

6

在一个服务类中,我有一个方法,被从@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创建的插入语句,但在调用flush后刷新时抛出org.hibernate.UnresolvableObjectException: No row with the given identifier exists异常。

这可能是什么原因?使用的数据库是Oracle 11gR2。

更新

我已经把问题缩小到会话。调用currentSession.flush()似乎没有像预期的那样将数据写入数据库以供刷新使用。如果我注释掉方法的其余部分,它将在结束时提交,一切都将存储在数据库中。

执行flush/refresh不会返回填充后的对象,因此我无法在事务后面使用数据库填充的值(由列默认设置)。我也不能将事务拆分成多个,因为我需要能够在方法的任何点上回滚。

有什么想法,为什么flush没有给我可访问的数据在数据库中?

另一个更新

我已经移动了很多代码来尝试隔离问题,但仍然存在问题。我还取消了两个实体之间的关系,试图手动处理所有内容,只是为了看看是否会解决问题。考虑到Steve的所有评论,现在看起来是这样的:

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是一个链接表,有一个由@Embeddable(在本例中称为"key")组成的复合ID,其中包括引用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; }
}

我移除了级联样式表,然后它就正常工作了。我不知道为什么。谁能解释一下,我会给他“正确答案”的标记。 :)


顺便提一下,我建议你不要一直手动刷新(refresh()),而是看看Hibernate的“生成属性值”概念。基本上,你告诉Hibernate某些属性的值将由数据库生成,并在插入/更新后自动为你刷新该状态。请参见http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#mapping-generated。 - Steve Ebersole
有趣。所以我将所有东西都简化成了一个方法,在一行中完成了所有操作——对象的保存、flush()和refresh()。我确实看到了select,并且仍然遇到了相同的错误。我们现在正在使用OpenSessionInViewFilter,计划在未来某个时候移除它,如果这有任何区别的话。 - Andy
2
抱歉,我没有意识到你在中国工作 :) - Steve Ebersole
你确定每次调用sessionFactory.getCurrentSession()都会得到相同的会话吗?就像我之前说的,你的问题似乎与flush()和refresh()不在同一个会话(jdbc事务)上发生有关。 - Steve Ebersole
1
我建议在这个阶段将Spring从方程式中移除。这似乎真的是一个事务处理问题。你确定Spring在整个事务期间正确地管理连接吗?在“JTA”环境中,Hibernate通常会使用所谓的连接释放(意味着它获取一个连接,使用它并将其返回给数据源),依赖于JTA保证在同一事务中返回相同的连接。 - Steve Ebersole
显示剩余13条评论
1个回答

0

看起来你的对象在保存到数据库后没有拥有一个标识符,导致在调用refresh()时出现异常。

实际上,假设你的数据库表拥有定义为自增的主键。因此,当你保存第一个Foo对象时,主键列的值为:1

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

最好的方法是期望Hibernate在对象保存到数据库后立即重新分配正确的标识符。

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

@GeneratedValue(strategy = GenerationType.AUTO)

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


谢谢你的回答。我在映射中将 GeneratedValue 设置在 @OneToOne 关联上。我会更新我的问题并提供适当的代码,以便让你更好地了解我正在处理的内容。 - Andy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接