【发布时间】:2014-03-05 10:36:43
【问题描述】:
我的 POJO(命名为 Category)有一个 langMap(语言映射),它存储了 Locale -> String 映射。它被定义为:
@Entity
class Category implements Serializable {
@ElementCollection
@MapKeyColumn(name = "locale")
@Column(name = "name")
@CollectionTable(name = "CategoryName", joinColumns = @JoinColumn(name = "category_id"))
private Map<Locale, String> langMap = new HashMap<>();
// other fields skipped.
}
在我更新地图之前它运行良好: 代码很简单:
public void replaceLangMap(Map<Locale, String> map) {
langMap.clear();
langMap.putAll(map);
}
清除所有langMap 并输入新值。 (当然是@Transactionalmerged())
但是,当我刷新视图层时,有时会看到旧地图的结果。我确定我没有添加任何缓存层。
这是我看到的:
例如,有一个分类存储
en -> Vocation
de_DE -> Berufung
在 mysql 中正确显示:
mysql> select * from CategoryName where category_id = 1;
+-------------+----------+--------+
| category_id | name | locale |
+-------------+----------+--------+
| 1 | Berufung | de_DE |
| 1 | Vocation | en |
+-------------+----------+--------+
2 rows in set (0.00 sec)
在视图层,我特意在每个名字后面加了“X”:
提交后,它正确地替换了旧地图,并且在 mysql 中的值被真正修改:
mysql> select * from CategoryName where category_id = 1;
+-------------+-----------+--------+
| category_id | name | locale |
+-------------+-----------+--------+
| 1 | BerufungX | de_DE |
| 1 | VocationX | en |
+-------------+-----------+--------+
2 rows in set (0.00 sec)
但是当我重新加载此类页面时,我偶尔会看到显示的旧地图(并非总是如此,大约 50/50):
18:14:42.698 INFO models.Category - en -> VocationX
18:14:42.698 INFO models.Category - de_DE -> BerufungX
18:14:42.706 INFO models.Category - en -> VocationX
18:14:42.706 INFO models.Category - de_DE -> BerufungX
18:14:44.165 INFO models.Category - en -> Vocation
18:14:44.165 INFO models.Category - de_DE -> Berufung
日志写入域对象(Category),而不是视图层。每次刷新都会触发 POJO 中的日志。所以我确信视图层没有缓存任何东西。
似乎有一个过时的langMap 没有从内存中清除,hibernate 偶尔会得到那个版本。如果我再修改一下,就会有3个版本的地图随机旋转……很奇怪。
只有重启服务器,总能得到正确的langMap。
这里有什么问题?
环境:
hibernate-jpa-2.1-api-1.0.0.Final
Hibernate 4.3.1.Final
MySQL 5.5.21 - MySQL Community Server
Table is innodb
mysql client library : mysql-connector-java 5.1.27
------------ 更新了 ------------
出于好奇,我想知道 CategoryDao.get(1) 是否真的命中 db 。我打开了 hibernate.show_sql=true ,在 CategoryDao.get(1) 中添加了一些日志记录,然后重新运行这些进程。
@Override
public Category get(Serializable id) {
if (id == null)
throw new PersistenceException("id may not be null");
Category obj = emp.get().find(Category.class, id);
logger.info("get id={} of object class {}", id, Category.class.getSimpleName());
return obj;
}
结果:
select aaa as aaa , bbb as bbb , … // fields skipped
from
Category category0_
left outer join
CategoryName langmap1_
on category0_.id=langmap1_.category_id
where
category0_.id=?
INFO d.CategoryDao$$EnhancerByGuice$$1904dfdf - get id=1 of object class Category
INFO d.CategoryDao$$EnhancerByGuice$$1904dfdf - get id=1 of object class Category
INFO d.CategoryDao$$EnhancerByGuice$$1904dfdf - get id=1 of object class Category
每个get() 都会触发记录器,但是,正如预期的那样,旧数据没有显示 SQL 日志。看来他们没有打分贝。有时最新数据未显示 SQL 日志。无论如何,如果它显示SQL代码,结果肯定是最新的。
这似乎是一个缓存问题。但我在这里没有使用任何缓存(包括 ehcahce)。我什至将 hibernate.cache.use_query_cache 和 hibernate.cache.use_second_level_cache 设置为 false ,但徒劳无功。
这里有什么问题?
------------ 更新 2 ------------
在评论中,我认为我通过将@Transactional 引入DAO 的get(id) 方法来解决问题。但它仅在整个(网络)操作仅检索 category 时才有效。例如,以下是可以的:
public Result category(@Param("id") Long id ) {
Category category = categoryDao.get(id);
return Results.html()
.render("category" , category);
}
效果很好,无论我如何修改类别的 langMap ,langMap 都正确存储到 db 并从 db 检索。我看到了 SQL ,每个get(id) 都真正命中了数据库。
但实际上,这个动作一般不会只渲染一个类别对象。例如,我有其他查询获取 subCategories 或类别下的项目:
Category category = categoryDao.get(id);
Map<Category , Long> catMap = categoryDao.getSubCategories(category).stream()
.collect(Collectors.toMap(cat -> cat, cat -> categoryDao.getChildCount(cat)));
List<DataDto> dataList = dataService.getDataList(category , page , count);
这样的动作看起来还可以,离线测试也可以。但是当在线时,在更新类别的langMap 之后,神秘缓存的langMap 有时会再次浮动(WTF!)。而且categoryDao.get(id) 也并不总是命中 DB。
好吧,这里可能出了什么问题?
【问题讨论】:
-
最后,我在我的 DAO 的
public Category get(Serializable id)中添加了@Transactional,这些奇怪的行为消失了……我还是觉得奇怪……为什么 read 应该是事务性的…… -
糟糕,我必须撤回我之前的评论。见
Update 2 -
我发现摆脱神秘缓存的唯一方法是标记所有DAO的方法
@Transactional(或者只标记DAO类@Transactional)。不管是阅读还是其他。 C/U/D 肯定是@Transactional。但我不喜欢标记阅读@Transactional,因为它会使@OneToMany与LAZY获取无效。我仍然希望有更好的解决方案。
标签: hibernate jpa jpa-2.0 hibernate-entitymanager guice-persist