Hibernate 存储过程更新数据库后,是否从数据库而不是缓存重新加载对象?
我正在使用Hibernate访问Oracle数据库 我想我在使用Hibernate的第一级(或会话)缓存时遇到了一些问题。我有代表账户的表:账户表、发票表和付款表。 Oracle数据库中定义了一些过程,以便添加付款将自动更新关联发票和帐户表中的列 我遇到的问题是,当我使用Hibernate执行以下操作时:Hibernate 存储过程更新数据库后,是否从数据库而不是缓存重新加载对象?,hibernate,Hibernate,我正在使用Hibernate访问Oracle数据库 我想我在使用Hibernate的第一级(或会话)缓存时遇到了一些问题。我有代表账户的表:账户表、发票表和付款表。 Oracle数据库中定义了一些过程,以便添加付款将自动更新关联发票和帐户表中的列 我遇到的问题是,当我使用Hibernate执行以下操作时: Account account = accountDao.get(accountId); assertEquals(0.00, account.getBalance()); // Savin
Account account = accountDao.get(accountId);
assertEquals(0.00, account.getBalance());
// Saving a payment will trigger stored procedures that
// will update the account balance.
paymentDao.save(createPaymentForAccount(accountId, 20.00));
account = accountDao.get(accountId);
assertEquals(20.00, account.getBalance());
public void save(Payment payment) {
getSession().persist(payment);
getSessionFactory().evict(Invoice.class);
getSessionFactory().evict(Account.class);
}
最后的断言将失败,因为account.getBalance()
返回0.00
,而不是20.00
我想第二次调用accountDao.get(…)
来访问数据库,并取回新的ACCOUNT对象。但是Hibernate似乎返回已经在其缓存中的account对象(当我检查查找调用的调试输出时,我看到对象数量:0
)
我假设Hibernate不知道数据库因为存储过程调用而发生了更改,这就是它在缓存中使用对象的原因
所以我开始考虑解决办法。一种是在保存付款时从hibernate会话缓存中删除任何帐户和付款对象。这将强制对任何帐户或发票操作进行数据库获取(使用新更新的值)
我尝试了以下方法:
Account account = accountDao.get(accountId);
assertEquals(0.00, account.getBalance());
// Saving a payment will trigger stored procedures that
// will update the account balance.
paymentDao.save(createPaymentForAccount(accountId, 20.00));
account = accountDao.get(accountId);
assertEquals(20.00, account.getBalance());
public void save(Payment payment) {
getSession().persist(payment);
getSessionFactory().evict(Invoice.class);
getSessionFactory().evict(Account.class);
}
但是hibernate跟踪日志显示什么也没有发生。我认为,sessionFactory.execute(…)
在第二级缓存上运行,该缓存未启用,因此没有任何可退出的内容
接下来,我尝试通过逐出我能找到的每个实例,从会话缓存中逐出所有ACCOUNT和INVOICE对象:
public void save(Payment payment) {
getSession().persist(payment);
for (Invoice invoice: lookupInvoices()) { // e.g. "from Invoice" query
getSession().evict(invoice);
}
for (Account account: lookupAccounts()) { // e.g. "from Account" query
getSession().evict(account);
}
}
这似乎可行,但效率极低,因为它在逐出会话之前将所有实例加载到hibernate会话缓存中,而我真正想做的就是逐出会话中的任何当前实例
我看不到任何方法可以清除指定类型的所有对象的一级缓存,因此还有其他可用的解决方案吗?您可以使用session.refresh()方法。请参阅文档中的。在从数据库再次读取account对象之前,为什么不先将其逐出?我会这样做:
Account account = accountDao.get(accountId);
assertEquals(0.00, account.getBalance());
// Saving a payment will trigger stored procedures that
// will update the account balance.
paymentDao.save(createPaymentForAccount(accountId, 20.00));
accountDao.getSession().evict(account)
account = accountDao.get(accountId);
assertEquals(20.00, account.getBalance());
此外,您还必须确保您的系统不处于可重复读取隔离级别。在这种隔离模式下,Spring保证同一事务中的两次读取总是返回相同的结果。由于隔离级别优先于缓存逐出,因此缓存逐出将不会产生任何可见的效果
有一种方法可以解决此行为:
将事务隔离级别声明为READ_UNCOMMITTED或READ_COMMITTED。如果使用标准方言,可能会遇到著名的“标准JPA不支持自定义隔离级别”异常。在这种情况下,您可以应用以下解决方法:
春季3.3:
春季3.2:谢谢。但是,我不确定
refresh()
是否是这里的解决方案。我的理解是,refresh()
对于重新加载由数据库更新的对象非常有用,例如,如果我保存了付款
,则如果触发器修改其状态,则刷新它。但在我的例子中,当更新付款时,我需要对缓存中的每个现有帐户和发票对象调用refresh
。如何从缓存中提取每个帐户和支付对象?您可以对这些关联设置cascade=“refresh”
。在这种情况下,每当刷新实体时,所有关联的实体都将被刷新。我可以按照您的建议设置cascade=“refresh”
,因为帐户和发票之间存在关联,这意味着我只需要刷新帐户对象。但我需要解决的问题是:如何从缓存中获取ACCOUNT对象,这样我就可以对每个对象调用refresh
,而无需执行from ACCOUNT
查询?e、 类似于getSession().refresh(Account.class)
(这在API AFAIK中不存在,但这正是我所寻求的功能)。我认为逐个刷新实体不是一个好主意。毕竟,刷新将为每个实体发出一个单独的查询。在一个查询中重新加载这些对象对我来说似乎更好。也许,在您的应用程序中,您可以限制重新加载帐户的数量?我也找不到访问会话中实体的方法。我想原因很清楚。您需要持有您的实体。谢谢您的评论。我不再从事那个项目(或在那个公司),所以我没有办法验证你提出的解决方案。在我最初的帖子中,我确实尝试过逐出所有帐户类,但是逐出实例可能是一种方法。