hibernatejpajpa-2.0hibernate-entitymanagerguice-persist

JPA @ElementCollection mysteriously cached


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 :

enter image description here

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 ?


Solution

  • 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: