我的 POJO(名为 Category)有一个langMap
(Language Map) ,它存储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
并输入新值。(当然,它是@Transactional
merge() )
但是,当我刷新视图层时,有时会看到旧地图的结果。我确定我没有添加任何缓存层。
这是我看到的:
例如,有一个类别存储
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
, and设置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 。
那么,这里可能有什么问题?