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 ?
Without exact code it's really hard to say what is going wrong.
Some observations:
ALWAYS use either @Transactional (for writing access) or @UnitOfWork (readonly). Everything that is outside @Transactional or @UnitOfWork will not have a connection. No field of your object will magically update if there is no connection.
Rendering a Hibernate proxy object inside a Ninja html template therefore may go badly wrong. Especially if the proxy is not detached or synchronized properly. There is no connection. And the templating may or may not know how to handle special proxy properties.
Regarding the Sessions in views pattern: I only know it as antipattern. While there are theoretical ways to make it work in Ninja I'd not recommend it. Rule of thumb is to render only POJOs without any magic. Reduces the percentage that something strange happens dramatically.
Apart from that your questions seems to be more related to guice-persist (used by Ninja) and Ninja.
Here are some links: