Spring Data JPA - 模拟"创建+加入"查询现有集合

时间:2016-03-24 13:52:31

标签: java hibernate jpa spring-data spring-data-jpa

我们说我有一个实体列表:

0,593   0,250984    -20,523384  -25,406271
0,594   0,250984        
0,595   0,250984        
0,596   0,250984        
0,597   0,250984    -15,793088  -21,286336
0,598   0,250984        
0,599   0,908811        
0,6     0,893612        
0,601   0,784814    -12,130922  -11,825742
0,602   0,909238        
0,603   0,25309     
0,604   0,38435     
0,605   0,602954    -8,316167   -3,43328
0,606   0,642628        
0,607   0,39201     
0,608   0,384289        
0,609   0,251656    -11,825742  -5,874723

SomeEntity.java:

if (JSON.stringify(hits) === JSON.stringify(["a", "b", "c"])) {

MyEntityRepository.java:

List<SomeEntity> myEntities = new ArrayList<>();

所以当我跑:

@Entity
@Table(name = "entity_table")
public class SomeEntity{

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private int score;

public SomeEntity() {}

public SomeEntity(long id, int score) {
    this.id = id;
    this.score = score;
}

然后Hibernate会将表中的所有记录加载到内存中。 有数百万条记录,所以我不想要那样。然后,为了交叉,我需要将结果集中的每条记录与我的List进行比较。 在原生MySQL中,我在这种情况下所做的是:

  1. 创建一个临时表并在其中插入实体&#39;列表中的ID。
  2. 使用&#34; entity_table&#34;加入此临时表,使用分数过滤器,然后仅拉取与我相关的实体(首先列在列表中的实体)。
  3. 通过这种方式,我获得了很大的性能提升,避免了任何OutOfMemoryErrors,并让数据库的机器完成大部分工作。

    有没有办法通过Spring Data JPA的查询方法(将hibernate作为JPA提供程序)实现这样的结果?我无法在文档或SO中找到任何此类用例。

2 个答案:

答案 0 :(得分:1)

你可以:

1)通过JPA创建一个分页本机查询(记得向它添加一个order子句)并处理固定数量的记录

2)使用StatelessSession(参见the documentation

答案 1 :(得分:0)

我了解您有一组entity_table标识符,并且您想查找其标识符在该子集中其得分大于给定得分的每个entity_table

一个明显的问题是:您是如何到达entity_table s的初始子集的,您是否不能仅将该查询的条件添加到您的查询中,该查询还检查“得分大于 x ”?

但是,如果我们忽略这一点,我认为有两种可能的解决方案。如果some_entity标识符的列表很小(确切的“小”取决于您的数据库),则可以使用IN子句并将您的方法定义为:

List<SomeEntity> findByScoreGreaterThanAndIdIn(int score, Set<Long) ids)

如果标识符的数量太大而无法容纳在IN子句中(或者您担心使用IN子句的性能),则需要使用临时表,食谱是:

  1. 创建一个映射到您的临时表的实体。为其创建一个Spring Data JPA存储库:

    class TempEntity {
        @Id
        private Long entityId;
    }
    
    interface TempEntityRepository extends JpaRepository<TempEntity,Long> { }
    
  2. 使用其save方法将所有实体标识符保存到临时表中。只要启用插入批处理,就应该可以正常工作-启用方法因数据库和JPA提供程序而异,但对于Hibernate,至少要将hibernate.jdbc.batch_size Hibernate属性设置为足够大的值。此外,flush()clear()会定期entityManager或您的所有临时表实体在持久性上下文中累积,而您仍然会用尽内存。类似于:

    int count = 0;
    for (SomeEntity someEntity : myEntities) {
        tempEntityRepository.save(new TempEntity(someEntity.getId());
        if (++count == 1000) {
            entityManager.flush();
            entityManager.clear();
        }
    }
    
  3. 向您的find添加一个SomeEntityRepository方法,该方法运行一个本机查询,该查询在entity_table上进行选择并加入临时表:

    @Query("SELECT id, score FROM entity_table t INNER JOIN temp_table tt ON t.id = tt.id WHERE t.score > ?1", nativeQuery = true)
    List<SomeEntity> findByScoreGreaterThan(int score);
    
  4. 请确保您在同一个事务中同时运行这两种方法,所以请在@Service类中创建一个方法,并用@Transactional(Propagation.REQUIRES_NEW)进行注释,该方法将依次调用这两种存储库方法。否则,在运行SELECT查询时,临时表的内容将消失,结果将为零。

通过使临时表实体的@ManyToOneSomeEntity,可以避免本机查询,因为您可以加入JPQL;我只是不确定您是否能够避免在这种情况下实际加载SomeEntity来插入它们(或者是否可以仅使用ID创建新的SomeEntity)。但是,既然您说您已经有了SomeEntity的列表,那也许就不成问题了。

我自己也需要类似的东西,因此当我得到一个可行的版本时,将对其答案进行修改。