我有一个简化的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
的对象列表。我希望瞬态对象为null,但事实并非如此。没有启用第一级或第二级缓存。
为了进一步混淆这一点,这只发生在我们使用HSQL内存数据库的单元测试期间。在Tomcat服务器上运行Web应用程序时,它可以正常工作。
我调试了一下,我发现在会话缓存(我明白它总是为Hibernate启用)似乎在运行单元测试时加载了所有以前加载的对象,但它是在应用程序服务器上运行时为空。我怀疑这意味着hibernate从缓存而不是数据库中获取对象。
另外值得一提的是它是一个Spring应用程序。
这是什么原因?或者重新解释我的主要问题:为什么在使用HSQLDB第二次加载实体时,瞬态对象不为空?
答案 0 :(得分:0)
听起来与第一级缓存/会话缓存有关。
第一级缓存存储会话上下文中的所有对象并重新使用它。当您使用应用程序服务器时,这不会产生任何影响,因为为每个事务启动了新会话。
换句话说,每次调用DAO方法都会导致创建一个新会话,这意味着缓存为空。
要解决此问题,请尝试在第二次调用之前关闭会话并创建新会话。
您还可以尝试在两个不同的交易中包含它。这也可能使它发挥作用。
编辑:回答Magnus提出的问题
我真的应该用另一种方式。每个休眠会话都将在事务结束时关闭。
根据Hibernate documenation for Session,
会话的生命周期受逻辑事务的开始和结束的限制。 (长事务可能跨越多个数据库事务。)
从应用服务器的角度来看,在绝大多数情况下(可能所有情况都是实用的),它无法识别包含多个物理事务的逻辑事务。
因此,它将每个物理事务视为逻辑事务。这意味着会话在每次物理交易结束时关闭。
在Seam
的会话范围和Java EE 6中的新范围(如@ViewScoped
)的上下文中,可以确定跨越多个物理事务的逻辑事务是可能的。但是,我认为它不是那么简单,并且不相信它是以这种方式实现的。但是,我没有任何信息可以证实这一点。
答案 1 :(得分:0)
你在测试用例上使用@Transactional吗?如果是,请尝试删除它。
答案 2 :(得分:0)
这是Hibernate第一级缓存的效果 - 总是 on。第一级缓存提供此行为:
如果从同一个Session中获取同一行两次,则会从Session对象获得相同的实体实例(即= = semantics)。
听起来你在测试时多次调用getSomeEntities会有相同的Session - 但不是在运行时。这让我觉得您的测试用例的处理方式与您的应用代码不同。