[英]Load entire tables including relationships into memory with JPA
我必须处理分布在20个表中的大量数据(总结约500万条记录),我需要有效地加载它们。
我正在使用Wildfly 14和JPA / Hibernate。
最后,每个记录都将被业务逻辑使用(在同一个事务中),我决定通过以下方式将所需表的全部内容预先加载到内存中:
em.createQuery("SELECT e FROM Entity e").size();
之后,每个对象都应该在事务中可用,因此可以通过以下方式获得:
em.find(Entity.class, id);
但是这在某种程度上不起作用,并且仍然有很多对DB的调用,特别是对于关系。
我怎样才能有效地加载所需表格的全部内容,包括关系,并确保我得到了所有内容/没有进一步的数据库调用?
我已经尝试过的:
em.find
问题相同 需要注意的一点是,数据是不可变的(至少在特定时间内),也可以用于其他事务。
我的计划是在@Singleton
bean中加载和管理整个数据。 但我想确保以最有效的方式加载它并确保加载整个数据。 当业务逻辑使用数据时,不应该有进一步的查询。 在特定时间(ejb计时器)之后,我将丢弃整个数据并从DB重新加载当前状态(总是整个表 )。
请记住,您可能需要64位JVM和大量内存。 看看Hibernate二级缓存 。 由于我们没有您的代码,因此需要检查一些事项:
@Cacheable
注释将提示Hibernate,以便实体可缓存 如果您需要以这种方式处理事物,您可能需要考虑将设计更改为不依赖于内存中的所有内容,不使用Hibernate / JPA,或者不使用app服务器。 这将使您更好地控制事物的执行方式。 这甚至可能更适合Hadoop之类的东西。 没有更多信息,很难说哪个方向最适合您。
我明白你在问什么,但JPA / Hibernate不想为你缓存那么多数据,或者至少我不希望得到它的保证。 考虑一下你描述了500万条记录。 每条记录的平均长度是多少? 100字节给出了500兆字节的内存,这只会让你的未经破坏的JVM崩溃。 可能更像是5000字节的平均值和25 gB的内存。 你需要考虑你要求的东西。
如果你想要它被缓存你应该自己或更好地做,但只要你有它们时使用结果。 如果您想要基于内存的数据访问,您应该专门研究一种技术。 http://www.ehcache.org/似乎很受欢迎,但这取决于您,您应该确保首先了解您的用例。
如果您想要提高数据库效率,那么您应该了解您的工作和设计并仔细测试。
基本上,使用每个表一个查询加载整个表并链接对象应该是一个非常容易的任务,但JPA的工作方式不同,如本例所示。
最大的问题是@OneToMany
/ @ManyToMany
-relations:
@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生成SQL语句SELECT * FROM EMPLOYEE
并且SELECT * FROM PHONE WHERE OWNER_ID=?
对于每个单独的Employee
,通常称为1 + n问题 。
我可以通过使用JPQL查询SELECT e FROM Employee e JOIN FETCH e.phones
来避免n + 1问题,这将导致类似SELECT * FROM EMPLOYEE LEFT OUTER JOIN PHONE ON EMP_ID = OWNER_ID
。
问题是,这对于涉及约20个表的复杂数据模型不起作用。
FetchType.LAZY
如果定义为FetchType.LAZY
则查询SELECT e FROM Employee e
将仅将所有Employees加载为Proxies,仅在访问phones
时加载相关的phones
,这最终将导致1 + n问题。
为了避免这种情况,将所有电话加载到同一会话SELECT p FROM Phone p
是非常明显的。 但是当访问phones
Hibernate仍会执行SELECT * FROM PHONE WHERE OWNER_ID=?
,因为Hibernate不知道当前会话中已经存在所有电话。
即使使用二级缓存,该语句也将在DB上执行,因为Phone
在第二级缓存中由其主键索引,而不是由OWNER_ID
。
结论
在Hibernate中没有像“只加载所有数据”这样的机制。
似乎除了保持关系瞬态并手动连接它们甚至只使用普通的旧JDBC之外别无他法。
编辑:
我刚刚找到了一个非常有效的解决方案。 我将所有相关的@ManyToMany
和@OneToMany
定义为FetchType.EAGER
与@Fetch(FetchMode.SUBSELECT)
和所有@ManyToOne
与@Fetch(FetchMode.JOIN)
,这导致可接受的加载时间。 在将javax.persistence.Cacheable(true)
添加到所有实体之后,我将org.hibernate.annotations.Cache
添加到每个相关集合,这将在二级缓存中启用集合缓存。 我在服务器启动/部署时通过@Singleton
EJB和@Startup
禁用了第二级缓存超时驱逐和“预热”二级缓存。 现在我100%控制缓存,在手动清除之前没有进一步的DB调用。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.