My POJO (named Category) have a langMap
(Language Map) , which stores Locale -> String
mapping. It's defined as :
@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.
}
It works well until I update the map : The code is simple :
public void replaceLangMap(Map<Locale, String> map) {
langMap.clear();
langMap.putAll(map);
}
Clear all the langMap
and put new values. (Sure , it is @Transactional
merged() )
But , when I refresh the view layer, I sometimes see result of old map . I am sure I am not adding any caching layer.
Here is what I see :
For example , there is one category that stores
en -> Vocation
de_DE -> Berufung
In mysql it is correctly shown :
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)
And in the view layer , I purposely added "X" to each name :
After committing , it correctly replace the old map and in the mysql the values are truly modified:
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)
But when I reload such page , I occasionally see old maps shown (not always , about 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
The log is written in the domain object (Category
) , not in the view layer . Every refresh will trigger the log in the POJO . So I am sure view layer is not caching anything.
It seems there is one outdated langMap
not purged from the memory, and hibernate occasionally gets that version. If I modify it again , there will be 3 versions of map randomly rotating… It is weird.
Only restarting the server can always get the correct langMap
.
What can be wrong here ?
Environment :
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
------------ updated ------------
Out of curiosity , I want to find whether the CategoryDao.get(1)
really hits db . I turned on hibernate.show_sql=true
, adding some logging in CategoryDao.get(1)
, then re-run the processes.
@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;
}
And the result :
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
Every get()
triggers the logger , but , as was to be expected, old data are not showing the SQL log. It seems they are not hitting the db. Sometimes latest data are not showing the SQL log. Anyway , if it shows the SQL code , the result is definitely latest.
This seems a cache problem. But I am not using any cache (including ehcahce) here. I even set hibernate.cache.use_query_cache
, and hibernate.cache.use_second_level_cache
to false , but in vain.
What can be wrong here ?
------------ update 2 ------------
In the comment , I thought I solve the problem by introducing @Transactional
to DAO's get(id)
method. But it only works when the whole (web's) action only retrieve the category . For example , the following is OK :
public Result category(@Param("id") Long id ) {
Category category = categoryDao.get(id);
return Results.html()
.render("category" , category);
}
It works well , no matter how I modify the category's langMap
, the langMap
is correctly stored to db and retrieve from db . I see the SQL , every get(id)
really hits the db.
But in reality , this action generally won't just render one category object. For example , I have other queries that fetch subCategories
, or items beneath the category :
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);
Such actions seem OK , off-line test are OK too. But when online , after updating the category's langMap
, the mysteriously cached langMap
sometimes floats up again (WTF!). And the categoryDao.get(id)
is not always hitting DB , either.
Well , what may be wrong here ?