【问题标题】:JPA @ElementCollection mysteriously cachedJPA @ElementCollection 神秘地缓存
【发布时间】: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_cachehibernate.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);
   }

效果很好,无论我如何修改类别的 langMaplangMap 都正确存储到 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 ,因为它会使 @OneToManyLAZY 获取无效。我仍然希望有更好的解决方案。

标签: hibernate jpa jpa-2.0 hibernate-entitymanager guice-persist


【解决方案1】:

如果没有确切的代码,真的很难说出了什么问题。

一些观察:

始终使用@Transactional(用于写入访问)或@UnitOfWork(只读)。 @Transactional 或 @UnitOfWork 之外的所有内容都没有连接。如果没有连接,您对象的任何字段都不会神奇地更新。

因此,在 Ninja html 模板中渲染 Hibernate 代理对象可能会出错。特别是如果代理没有正确分离或同步。没有联系。模板可能知道也可能不知道如何处理特殊的代理属性。

关于视图中的会话模式:我只知道它是反模式。虽然有理论上的方法可以让它在 Ninja 中工作,但我不推荐它。经验法则是只渲染 POJO,没有任何魔法。显着降低奇怪事件发生的百分比。

除此之外,您的问题似乎与 guice-persist(由 Ninja 使用)和 Ninja 更相关。

这里有一些链接:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-06-07
    • 2013-01-21
    • 2011-06-25
    • 2011-03-03
    • 1970-01-01
    • 2013-10-12
    • 2020-02-29
    • 2021-12-11
    相关资源
    最近更新 更多