使用JPA

时间:2018-10-29 11:15:14

标签: java hibernate jpa java-ee wildfly

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

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

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

em.createQuery("SELECT e FROM Entity e").size();

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

em.find(Entity.class, id);

但这不能以某种方式起作用,并且仍然有很多对数据库的调用,尤其是对于关系。

如何有效加载所需表的全部内容,包括 关系,并确保我掌握了一切/不会再有DB调用了?

我已经尝试过的:

  • FetchMode.EAGER :单项选择过多/对象图过于复杂
  • EntityGraphs :与FetchMode.EAGER
  • 相同
  • 加入获取语句:到目前为止,由于它同时填充了与所引用实体的关系,因此效果最佳
  • 第二级/查询缓存:不起作用,可能与em.find
  • 相同

要注意的一件事是数据是不可变的(至少在特定时间内),并且还可以在其他事务中使用。

编辑:

我的计划是在@Singleton bean中加载和管理整个数据。但是我想确保以最有效的方式加载它,并确保加载了整个数据。当业务逻辑正在使用数据时,就不需要进一步的查询。在特定时间(ejb计时器)之后,我将丢弃整个数据并从数据库中重新加载当前状态(始终整个表)。

3 个答案:

答案 0 :(得分:6)

请记住,您可能需要64位JVM和大量内存。看一下Hibernate 2nd Level Cache。由于我们没有您的代码,因此需要检查一些事情:

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

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

答案 1 :(得分:5)

我理解您的要求,但是JPA / Hibernate不会想要为您缓存那么多数据,或者至少我不希望得到它的保证。假设您描述了500万条记录。每条记录的平均长度是多少? 100字节提供500兆字节的内存,这只会使未损坏的JVM崩溃。大概更像是5000个字节,这就是25 gB的内存。您需要考虑自己的要求。

如果您希望将其缓存,则应该自己做,或者做得更好,但是只要有结果就使用它们。如果您想要基于内存的数据访问,则应查看专门用于该技术的技术。 http://www.ehcache.org/似乎很流行,但这取决于您,您应该确保首先了解用例。

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

答案 2 :(得分:2)

从根本上来说,加载整个表只是一个简单的任务,每个表一个查询并链接对象,但是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;
    ...
}

FetchType.EAGER

如果定义为FetchType.EAGER,并且查询SELECT e FROM Employee e,Hibernate会在加载的每个SELECT * FROM EMPLOYEE的{​​{1}}之后紧随SELECT * FROM PHONE WHERE OWNER_ID=?生成SQL语句,这是众所周知的作为 1 + n个问题

我可以通过使用JPQL查询Employee来避免n + 1问题,这将导致类似SELECT e FROM Employee e JOIN FETCH e.phones的情况。

问题是,这对于包含约20个表的复杂数据模型不起作用。

FetchType.LAZY

如果定义为SELECT * FROM EMPLOYEE LEFT OUTER JOIN PHONE ON EMP_ID = OWNER_ID,查询FetchType.LAZY只会将所有Employees加载为代理,仅在访问SELECT e FROM Employee e时才加载相关的Phones,最终将导致1 + n问题。

要避免这种情况,很明显,就是将所有电话都加载到同一会话phones中。但是访问SELECT p FROM Phone p时,Hibernate仍将执行phones,因为Hibernate不知道当前会话中已经有所有电话。

即使使用二级缓存,该语句也会在DB上执行,因为SELECT * FROM PHONE WHERE OWNER_ID=?是通过二级缓存中的主键而不是Phone来索引的。

结论

在Hibernate中没有像“仅加载所有数据”这样的机制。

似乎没有其他方法可以保持关系短暂并手动连接,甚至只是使用普通的旧JDBC。

编辑:

我刚刚找到了一个很好的解决方案。我将所有相关的OWNER_ID@ManyToMany定义为@OneToManyFetchType.EAGER组合在一起,而所有@Fetch(FetchMode.SUBSELECT)@ManyToOne组合在一起,这将导致可接受的加载时间。除了向所有实体添加@Fetch(FetchMode.JOIN)之外,我还向每个相关集合添加了javax.persistence.Cacheable(true),从而在第二级缓存中启用了集合缓存。我禁用了第二级缓存超时驱逐,并在服务器启动/部署时通过org.hibernate.annotations.Cache EJB和@Singleton结合“预热”了第二级缓存。现在,我已经100%地控制了缓存,直到手动清除缓存之后,再没有其他数据库调用。