【问题标题】:Transient field not null when loading entity second time using HSQLDB使用 HSQLDB 第二次加载实体时瞬态字段不为空
【发布时间】:2013-03-19 19:51:05
【问题描述】:

我有一个Entity,简化后如下所示:

@Entity
@Table(name = "SOMETABLE")
public class SomeEntity {

    // real code has id and more columns

    @Column(name = "SOMECOLUMN")
    private String someColumn;

    @Transient
    private SomeObject transientObject;

    // getters and setters
}

DAO 方法通过使用 @NamedQuery 和 JPA EntityManager(大致存根)加载实体列表:

@Transactional
public List<SomeEntity> getSomeEntities() {
    TypedQuery<SomeEntity> query = entityManager.createNamedQuery("findSomeEntities", SomeEntity.class);
    List<SomeEntity> someEntities = query.getResultList();
    for (SomeEntity someEntity : someEntities) {
        someEntity.setTransientObject(<some value here>);
        return someEntities;
    }
}

请注意,此方法还设置了transientObject(在代码示例中进行了简化)。

下次调用getSomeEntities() 时,query.getResultList(); 返回一个对象列表,其中transientObject 仍然设置。我希望瞬态对象为空,但事实并非如此。没有启用一级或二级缓存。

为了进一步混淆这一点,这只发生在单元测试期间,我们使用 HSQL 内存数据库。在 Tomcat 服务器上运行 Web 应用程序时,它运行良好。

我调试了一下,我发现在运行单元测试时,在 会话缓存(我理解它总是为 Hibernate 启用)似乎加载了所有以前加载的对象,但它是在应用程序服务器上运行时为空。我怀疑这意味着 hibernate 从缓存而不是数据库中获取对象。

另外值得一提的是它是一个 Spring 应用程序。

这是什么原因?或者改写我的主要问题:为什么第二次使用 HSQLDB 加载实体时瞬态对象不为空?

【问题讨论】:

  • 所以在你打电话给someEntity.setTransientObject(&lt;some value here&gt;);,someEntity.transientObject != null?
  • 第一次调用该方法,是的。第二次,它 not 为空。是的,它是之前我再次设置它。试图在问题中澄清这一点。

标签: java hibernate jpa hsqldb transient


【解决方案1】:

您是否在测试用例中使用@Transactional?如果是,请尝试删除它。

【讨论】:

  • 不。我的测试正在做他们应该做的。
【解决方案2】:

听起来像是与一级缓存/会话缓存有关。

一级缓存存储会话上下文中的所有对象并重复使用它。这在您使用应用程序服务器时不会产生影响,因为每个事务都会启动一个新会话。

换句话说,每次调用您的 DAO 方法都会导致创建一个新会话,这意味着缓存是空的。

要解决此问题,请尝试在第二次通话之前关闭会话并创建一个新会话。

您也可以尝试在两个不同的交易中包含它。这也可能让它发挥作用。

编辑:回答 Magnus 的问题

我真的应该换一种说法。每个休眠会话将在事务结束时关闭。

根据Hibernate documenation for Session

Session 的生命周期以逻辑事务的开始和结束为界。 (长事务可能跨越多个数据库事务。)

从应用服务器的角度来看,在绝大多数情况下(可能是所有实际情况),它无法识别包含多个物理事务的逻辑事务。

因此,它将每个物理事务视为逻辑事务。这意味着会话在每个物理事务结束时关闭。

Seam 的对话范围和Java EE 6 中的新范围(如@ViewScoped)的上下文中,可以确定一个跨越多个物理事务的逻辑事务是可能的。但是,我不认为它那么简单,也不相信它是以这种方式实现的。但是,无论哪种方式,我都没有任何信息可以证实这一点。

【讨论】:

  • This does not have an impact when you use the application server since the a new session is started for each transaction. 这是为什么呢?我实际上可以通过在我的测试中调用entityManager.clear() 来解决这个问题,但这不是我想做的事情。而且DAO方法是事务性的,我也验证了其实是两个不同的事务。
  • @MagnusTengdahl - 编辑了答案以包含您的问题的答案。此外,清除 entitymanager 可能比创建新会话来模拟 App Server 更有效
  • 感谢您的努力。然而,我仍然不完全清楚。 :) 您对我的建议是在我的单元测试中实际调用.clear() 以确保会话缓存为空?这真的是我想避免的事情,它太容易出错了。既然 DAO 方法实际上封装在事务中,为什么 Hibernate/HSQL 不处理这个问题?您可能已经回答了这个问题,但我并没有从您的回答中完全理解。
  • 为每个事务获取新实体管理器(不是工厂)的替代方法。这将更接近地模仿应用服务器中的行为。至于为什么应用服务器会这样做,简明扼要的回答是 Hibernate 规范期望它这样做。
  • 听起来更像我们想要做的。你知道有没有办法用 Spring 来配置它?
【解决方案3】:

这是 Hibernate 一级缓存的效果——始终开启。一级缓存提供了这种行为:

如果您从同一个 Session 中获取同一行两次,您会从 Session 对象中获得相同的实体实例(即,具有 == 语义)。

听起来您在测试时对 getSomeEntities 的多次调用具有相同的会话 - 但在运行时没有。这让我认为您的测试用例的处理方式与您的应用程序代码不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-10
    • 1970-01-01
    相关资源
    最近更新 更多