【问题标题】:Persistence with Hibernate/JPA where object identity is important对象身份很重要的 Hibernate/JPA 的持久性
【发布时间】:2019-07-10 16:21:43
【问题描述】:

我使用 Hibernate/JPA 作为持久性后端,本质上归结为一个用 Java 编写的游戏的模组。

在这种情况下,在主线程上尽可能少地查询数据库对我来说非常重要。尽管可能,但异步执行此操作是不切实际的,因为我必须从其他线程调用游戏对象的方法,而这通常不起作用。这意味着我必须使用缓存对象在内存中做尽可能多的事情,以最大限度地提高性能(因为使用内存比等待查询从数据库返回结果要快)。

假设我有如下定义的实体:

@Entity
class Town {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;

    @OneToMany(mappedBy = "town", fetch = FetchType.EAGER) // use eager fetching to save on having to query the database later for this
    private Set<Resident> residents;

    // ... other fields and associated getters/setters
}

@Entity
class Resident {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER) // use eager fetching to save on having to query the database later for this
    @JoinColumn(name = "town_id")
    private Town town;

    // ... otehr fields and associated getters/setters
}

我的问题如下:

如果我要使用 Hibernate 检索所有 Resident 实体,并将它们存储在内存中(例如,使用 HashMap ),然后如果我要继续使用 Hibernate 检索所有 Town 实体并以相同的方式缓存它们,调用Town#getResidents() 是否会返回对内存中某些与驻留缓存中存在的对象相同的对象的引用

本质上,Hibernate 是否会重用之前在查询中返回的仍然有效的对象来填充新创建的集合?

我也不反对任何对我的一般方法的批评或关于如何改进它的建议。先感谢您! :)

【问题讨论】:

  • 您可以查看 EclipseLink,它是 JPA 参考实现。从很久以前开始,它相对于 Hibernate 的优势之一就是它缓存对象而不是数据。这就是为什么它将其缓存称为“身份映射”。如果您使用的是纯 JPA,则可以将其放入您的应用程序中而不会带来太多痛苦。

标签: java performance hibernate jpa caching


【解决方案1】:

缓存是一个非常复杂的话题。您不必自己处理缓存。这就是休眠 second-level-cache 的用途。

ORM等数据库抽象层的优势之一 (对象关系映射)框架是它们的能力 透明地缓存从底层存储中检索到的数据。 这个 有助于消除频繁访问数据的数据库访问成本。

您仍然需要将实体配置为可缓存以及休眠应缓存的积极程度,但其余部分将由休眠处理

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
class Resident {
...

【讨论】:

  • EHCache 是否在内存中维护相同的对象,而不是为每个新查询创建新对象?
  • 据我所知,这正是将要发生的事情。因为 Hibernate 应该能够理解给定 id 的 Residents 已经在缓存中并且是有效的。
  • @Christian 是的。我认为,为了使其正常工作,实体在操作时必须永远分离。
  • @LppEdd 是的,这是肯定的。但据我了解,他不想在分离的实体上工作。还是我错过了什么?
  • @Christian 不,你是对的。我是提议分离它们的人,因为访问数据库的次数非常少,以至于让 EntityManager 监视它们只是开销。
【解决方案2】:

如果堆消耗不是问题,或者产生的实例不是那么多,那么你的方法还不错。我看到你已经在使用FetchType.EAGER,这是很重要的部分。

我想说您甚至不需要检索Resident(s),您只需收集每个Townresidents Set&lt;Resident&gt;

一旦检索到所有实例,我也会明确地EntityManager#detach 它们。

是的,Hibernate 维护了多个级别的缓存。见documentation


如果我可能会问,您为什么使用 JPA?毕竟,更底层的方法,也许使用 MyBatis,不是更好的方法吗?依赖 Hibernate 等重量级框架是不是大材小用?

【讨论】:

  • 我明白了。因此,与其进行另一个查询来收集数据库中的所有居民进行缓存,不如遍历所有城镇并收集他们的居民并将它们放入我想要的任何居民缓存中?
  • @HaedHutner 就个人而言,我会这样做。显然,如果可以的话,进行基准测试。
  • @HaedHutner 看到我在答案中提出的问题。
  • @HaedHutner 看看我对缓存的回答。在我看来,您可以使用 hibernate-possibilites 而无需手动执行太多操作
  • @LppEdd 这有点矫枉过正,但最终它是我能想到的最灵活的选择。在此之前,我尝试使用纯 JDBC 和其他持久性解决方案,如 MongoDB,但最终 Hibernate 是我最熟悉和最熟悉的解决方案。
【解决方案3】:

我不同意关于缓存的公认答案。我有另一个答案,我已经详细解释了为什么我不喜欢休眠二级缓存hibernate second level cache with Redis -will it improve performance?休眠二级缓存的使用到目前为止不是一种常见的缓存策略。这有几个原因:

  • 休眠二级缓存非常低效。它使用默认的 java 序列化,这非常慢而且内存非常低。
  • 经常使用休眠二级缓存需要保持关系的一致性。一个这样的例子是当您需要从集合中删除一个元素时。如果您使用简单的 pojoes,保持一致性通常不是什么大问题,但是当您开始将持久性逻辑与缓存混合时,它开始变得非常烦人。
  • 如果您决定从纯二级缓存转到带有休眠的分布式缓存。复杂性会以一种不好的方式飙升,然后您将很难了解为什么 hibernate 缓存效率低下。

我会与接受的答案完全相反,建议您将缓存与持久性分离为简单的 Pojos。并通过这些 Pojo 管理缓存。

现在关于你的模型。我不知道您所涵盖的功能,但我强烈怀疑任何人都无法获取拥有所有居民的城镇。我建议您删除从城镇到居民的 OneToMant 关系。基于此,我看到以下场景:

  • 以居民为中心的数据处理,您可能会对居民重复点击。您可以决定缓存整个居民和城镇,或者如果您没有命中同一个居民,您可以决定只缓存城镇。
  • 将城镇和居民缓存在同一区域,您的处理以居民为中心。您可以选择在城镇和居民的同一个键下缓存在一起,您会牺牲一些内存是的。但是你会一口气直接击中记忆和城镇。
  • 居民和城镇的两个缓存区域,但是您需要为 1 个居民执行两次查找。在内存方面效率更高,在性能方面并非如此。
  • 仅缓存城镇。无论如何,无论您如何决定。我个人不会使用休眠二级缓存:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-18
    • 2017-04-09
    • 2014-09-08
    • 2010-10-24
    • 1970-01-01
    • 2012-01-14
    • 1970-01-01
    相关资源
    最近更新 更多