Java 使用JPA提高foreach持久化调用的性能

Java 使用JPA提高foreach持久化调用的性能,java,multithreading,oracle,hibernate,jpa,Java,Multithreading,Oracle,Hibernate,Jpa,我们有这样的业务逻辑: public class StuffLogic { @Autowired private Util util; // ... public void processStuffs() { // Simply returns a list of 50k objects List<Stuff> list = dao.getManyFilteredStuff(); for (Stuff

我们有这样的业务逻辑:

public class StuffLogic {
    @Autowired
    private Util util;
    // ...

    public void processStuffs() {
        // Simply returns a list of 50k objects
        List<Stuff> list = dao.getManyFilteredStuff();

        for (Stuff act : list) {
            act.setStatus(StatusEnum.SomeStatus);
        }

        util.mergeAll(list);
    }
}

public class Util {
    // ...

    @Transactional
    public void mergeAll(List<?> list) {
        for (Object o : entities) {
            entityManager.merge(o);
        }
    }
}
公共类填充逻辑{
@自动连线
私有Util Util;
// ...
public void processstuff(){
//只返回50k个对象的列表
List List=dao.getManyFilteredStuff();
用于(材料法案:列表){
act.setStatus(StatusEnum.SomeStatus);
}
util.mergeAll(列表);
}
}
公共类Util{
// ...
@交易的
公共无效合并所有(列表){
用于(对象o:实体){
实体管理器合并(o);
}
}
}
我们有很多实体(约50000个),希望提高性能(根据初步测量,目前我们每秒可以处理1000个实体)

你们当中有人对此有什么提示吗

我们迄今为止所做的尝试:

  • 多线程。如果我们使用多线程执行并在不同的线程中持久化处理,似乎不会真正加快执行时间,但会将执行时间降低大约5倍。我们在Oracle上,可能它在更新时使用表锁定而不是行锁定,所以所有线程都必须等待
  • 不幸的是,使用单个批量更新不是一个选项,因为我们有许多监听器引入了一些不使用单个update语句的魔术
一些技术细节:我们正在使用Oracle、JPA/Hibernate


任何提示都将不胜感激

您可以阅读这篇关于批处理的文章

<>也可以考虑动态生成查询:

UPDATE stuff
SET status = CASE WHEN id = 1 THEN 'status1'
            WHEN id = 2 THEN 'status2' 
            ...

您可以阅读这篇关于批处理的文章

<>也可以考虑动态生成查询:

UPDATE stuff
SET status = CASE WHEN id = 1 THEN 'status1'
            WHEN id = 2 THEN 'status2' 
            ...

您可以做几件事,当您一次更新所有内容时,一级缓存会不断增长。这可能会增加脏检查所需的时间

因此,在x记录(找到最佳点)之后,在entityManager上进行刷新和清除

public void mergeAll(List<?> list) {
    int i = 0;
    for (Object o : entities) {
        entityManager.merge(o);
        i++:
        if (i ^ 50 == 0) {
            entityManager.flush();
            entityManger.clear();
        }
    }
}
这将减少向oracle发出的查询量,而不是50个单一查询—这将是一个包含50个条目的单一查询

如果您的修改同时导致更新和插入,您可能希望对它们进行排序,以便hibernate可以将它们分组并使用批处理语句

hibernate.order_inserts=true
hibernate.order_updates=true
如果使用版本控制,可能还需要将
hibernate.jdbc.batch\u versioned\u data
设置为
true


您可以在这些属性上找到一篇很好的文章。

您可以做几件事,因为您要一次性更新所有内容,一级缓存会不断增长。这可能会增加脏检查所需的时间

因此,在x记录(找到最佳点)之后,在entityManager上进行刷新和清除

public void mergeAll(List<?> list) {
    int i = 0;
    for (Object o : entities) {
        entityManager.merge(o);
        i++:
        if (i ^ 50 == 0) {
            entityManager.flush();
            entityManger.clear();
        }
    }
}
这将减少向oracle发出的查询量,而不是50个单一查询—这将是一个包含50个条目的单一查询

如果您的修改同时导致更新和插入,您可能希望对它们进行排序,以便hibernate可以将它们分组并使用批处理语句

hibernate.order_inserts=true
hibernate.order_updates=true
如果使用版本控制,可能还需要将
hibernate.jdbc.batch\u versioned\u data
设置为
true


可以找到关于这些属性的好文章。

假设
mergeAll
在单独的事务中执行(否则,如果实体已经在持久性上下文中,则无需调用
merge
),那么您将有许多数据库往返(每个实体至少一次)获取要合并的实体

解决方案之一是通过一个查询读取所有这些实体(与处理之前用于读取实体的查询相同,或者通过使用构造
从entity where entity.id in(?、、?、…)
)中选择e,然后将其合并,因为合并时实体将处于持久性上下文中


您可能希望将此方法与成批刷新/清除持久性上下文相结合,以实现更好的内存管理,如其他答案所示。

假设
mergeAll
在单独的事务中执行(否则,如果实体已在持久性上下文中,则无需调用
merge
),然后您将有许多数据库往返(每个实体至少一次)来获取要合并的实体

解决方案之一是通过一个查询读取所有这些实体(与处理之前用于读取实体的查询相同,或者通过使用构造
从entity where entity.id in(?、、?、…)
)中选择e,然后将其合并,因为合并时实体将处于持久性上下文中


您可能希望将此方法与成批刷新/清除持久性上下文相结合,以便更好地管理内存,如其他答案所示。

我假设对
persist
的调用在
foreach
循环中?是的,抱歉,输入错误。基本上,我们在某个地方有一个实用函数,它为每个元素调用persist(),并用@Transactional.Plesae标记一些实际代码,因为目前我对事务没有任何线索,涉及多少实体管理器您使用的是单个循环还是多个循环。信息太少……我假设循环也在一个事务中,并且实际上只有一个事务。拥有5000+1事务不是很快,因为启动/提交很慢。使用批处理而不是单个提交将加快速度,您不应该使用
merge
而不是
persist
(后者用于新实体)。根据要求,请使用未派生的实际代码,因为您现在拥有的代码无法工作(没有参数,方法名称不匹配).我假设对
persist
的调用在
foreach
循环中?是的,对不起,输入错误。基本上,我们在某处有一个实用函数,它为每个元素调用persist(),并用@Transactional.Ple标记