使用JPA将包括关系的整个表加载到内存中

Mei*_*ini 7 java hibernate jpa java-ee wildfly

我必须处理分布在20个表中的大量数据(总结约500万条记录),我需要有效地加载它们.

我正在使用Wildfly 14和JPA/Hibernate.

最后,每个记录都将被业务逻辑使用(在同一个事务中),我决定通过以下方式将所需表的全部内容预加载到内存中:

em.createQuery("SELECT e FROM Entity e").size();
Run Code Online (Sandbox Code Playgroud)

之后,每个对象都应该在事务中可用,因此可以通过以下方式获得:

em.find(Entity.class, id);
Run Code Online (Sandbox Code Playgroud)

但是这在某种程度上不起作用,并且仍然有很多对DB的调用,特别是对于关系.

我怎样才能有效地加载所需表格的全部内容,包括关系,并确保我得到了所有内容/没有进一步的数据库调用?

我已经尝试过的:

  • FetchMode.EAGER:仍有太多的单选/对象图太复杂了
  • EntityGraphs :与FetchMode.EAGER相同
  • 加入fetch语句:迄今为止的最佳结果,因为它同时填充了与引用实体的关系
  • 第二级/查询缓存:不工作,可能与问题相同em.find

需要注意的一点是,数据是不可变的(至少在特定时间内),也可以用于其他事务.

编辑:

我的计划是加载和管理@Singletonbean中的整个数据.但我想确保以最有效的方式加载它并确保加载整个数据.当业务逻辑使用数据时,不应该有进一步的查询.在特定时间(ejb计时器)之后,我将丢弃整个数据并从DB重新加载当前状态(总是整个表).

小智 6

请记住,您可能需要64位JVM和大量内存.看看Hibernate二级缓存.由于我们没有您的代码,因此需要检查一些事项:

  1. @Cacheable 注释将线索Hibernate,以便实体可缓存
  2. 配置二级缓存以使用类似ehcache的东西,并将最大内存元素设置为足够大的内容以适合您的工作集
  3. 确保您不会在代码中意外使用多个会话.

如果您需要以这种方式处理事物,您可能需要考虑将设计更改为不依赖于内存中的所有内容,不使用Hibernate/JPA,或者不使用应用服务器.这将使您更好地控制事物的执行方式.这甚至可能更适合Hadoop之类的东西.没有更多信息,很难说哪个方向最适合您.


K.N*_*las 5

我明白你在问什么,但JPA/Hibernate不想为你缓存那么多数据,或者至少我不希望得到它的保证.考虑一下你描述了500万条记录.每条记录的平均长度是多少?100字节给出了500兆字节的内存,这只会让你的未经破坏的JVM崩溃.可能更像是5000字节的平均值和25 gB的内存.你需要考虑你要求的东西.

如果你想要它被缓存你应该自己或更好地做,但只要你有它们时使用结果.如果您想要基于内存的数据访问,您应该专门研究一种技术.http://www.ehcache.org/似乎很受欢迎,但这取决于您,您应该确保首先了解您的用例.

如果您想要提高数据库效率,那么您应该了解您的工作和设计并仔细测试.


Mei*_*ini 5

基本上加载整个表应该是一个非常简单的任务,但 JPA 的工作方式有所不同,如本示例所示。

最大的问题是@OneToMany/@ManyToMany关系:

@Entity
public class Employee {
    @Id
    @Column(name="EMP_ID")
    private long id;
    ...
    @OneToMany(mappedBy="owner")
    private List<Phone> phones;
    ...
}
@Entity
public class Phone {
    @Id
    private long id;    
    ...
    @ManyToOne
    @JoinColumn(name="OWNER_ID")
    private Employee owner;
    ...
}
Run Code Online (Sandbox Code Playgroud)

FetchType.EAGER

如果定义为,FetchType.EAGER并且查询Hibernate 会在每个加载的 SQL 语句之后SELECT e FROM Employee e生成 SQL 语句,通常称为1+n 问题SELECT * FROM EMPLOYEESELECT * FROM PHONE WHERE OWNER_ID=?Employee

我可以通过使用 JPQL-query 来避免 n+1 问题SELECT e FROM Employee e JOIN FETCH e.phones,这将导致类似的结果SELECT * FROM EMPLOYEE LEFT OUTER JOIN PHONE ON EMP_ID = OWNER_ID.

问题是,这不适用于涉及约 20 个表的复杂数据模型。

FetchType.LAZY

如果定义为FetchType.LAZY查询,SELECT e FROM Employee e则仅将所有员工加载为代理,仅在访问时加载相关电话phones,这最终也会导致 1+n 问题。

为了避免这种情况,很明显将所有电话加载到同一个会话中SELECT p FROM Phone p。但是当访问phonesHibernate时仍然会执行SELECT * FROM PHONE WHERE OWNER_ID=?,因为Hibernate不知道当前会话中已经存在所有Phone。

即使使用二级缓存,该语句也会在数据库上执行,因为Phone它是由二级缓存中的主键而不是OWNER_ID.

结论

Hibernate 中没有类似“仅加载所有数据”的机制。

似乎除了保持关系暂时性并手动连接它们或者甚至只使用普通的旧式 JDBC 之外,没有其他方法了。

编辑:

我刚刚找到了一个非常有效的解决方案。我将所有相关的@ManyToMany和定义@OneToManyFetchType.EAGER@Fetch(FetchMode.SUBSELECT)和 全部@ManyToOne结合@Fetch(FetchMode.JOIN),这会产生可接受的加载时间。接下来添加javax.persistence.Cacheable(true)到所有实体,我添加org.hibernate.annotations.Cache到每个相关集合,这使得集合缓存在二级缓存中。@Singleton我禁用了二级缓存超时驱逐,并通过EJB 结合@Startup服务器启动/部署来“预热”二级缓存。现在我对缓存拥有 100% 的控制权,在我手动清除缓存之前不会再有任何数据库调用。

  • 尽管如此,在休眠状态下还有“FetchMode.SUBSELECT”。这是非常重要的一项,因为它可以减少从数据库传输到应用程序服务器的数据。检查这个问题/sf/ask/2308935961/ (2认同)