You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Quarkus Hibernate ORM Panache批量插入失效问题求助

批量插入失效问题排查与解决

近期对应用进行压力测试,发现高并发事务场景下性能极差,定位到插入操作未批量执行。搭建最小复现项目后问题依旧,尝试直接使用EntityManager替代Panache也无法解决。

技术栈

  • Java 21
  • Quarkus 3.22.3
  • PostgreSQL 15

相关代码

TestEntity.java

package org.example;

import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.util.UUID;

@Entity
@Table(name = "TestEntity", schema = "testdb")
public class TestEntity extends PanacheEntityBase {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "id")
    private UUID id;

    @Column(name = "testCol")
    private final Integer testCol;

    private TestEntity(final Builder builder) {
        this.testCol = builder.testCol;
    }

    protected TestEntity() {
        this.id = null;
        this.testCol = null;
    }

    public UUID getId() {
        return id;
    }

    public Integer getTestCol() {
        return testCol;
    }

    public static final class Builder {
        private Integer testCol;

        public Builder testCol(final Integer testCol) {
            this.testCol = testCol;
            return this;
        }

        public TestEntity build() {
            return new TestEntity(this);
        }
    }
}

TestResource.java

package org.example;

import jakarta.transaction.Transactional;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Response;

import java.util.ArrayList;
import java.util.List;

@Path("/test")
public class TestResource {

    @POST
    @Transactional
    public Response hello() {
        try {

            List<TestEntity> testEntities = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                testEntities.add(new TestEntity.Builder().testCol(i).build());
            }

            TestEntityRepository testEntityRepository = new TestEntityRepository();

            testEntityRepository.persist(testEntities);

            return Response.ok().build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }

    }
}

TestEntityRepository.java

package org.example;

import io.quarkus.hibernate.orm.panache.PanacheRepository;
import jakarta.enterprise.context.ApplicationScoped;

import java.util.Optional;
import java.util.UUID;

@ApplicationScoped
public class TestEntityRepository implements PanacheRepository<TestEntity> {

public Optional<TestEntity> findById(final UUID id) {
    return find("id", id).firstResultOptional();
  }
}

控制台日志(批量未生效)

2025-07-02 15:20:59,929 DEBUG [org.hib.eng.jdb.spi.SqlStatementLogger] (executor-thread-2)
    insert 
    into
        testdb.TestEntity
        (testCol, id) 
    values
        (?, ?)
2025-07-02 15:20:59,930 DEBUG [org.hib.eng.jdb.spi.SqlStatementLogger] (executor-thread-2)
    insert 
    into
        testdb.TestEntity
        (testCol, id) 
    values
        (?, ?)
 2025-07-02 15:20:59,930 DEBUG [org.hib.cac.int.TimestampsCacheEnabledImpl] (executor-thread-2) Pre-invalidating space [testdb.TestEntity], timestamp: 1751462519930
 2025-07-02 15:20:59,932 DEBUG [org.hib.eng.jdb.bat.int.BatchImpl] (executor-thread-2) PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : insert into testdb.TestEntity (testCol,id) values (?,?)
 2025-07-02 15:20:59,933 DEBUG [org.hib.eng.tra.int.TransactionImpl] (executor-thread-2) On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
 2025-07-02 15:20:59,933 DEBUG [org.hib.res.jdb.int.LogicalConnectionManagedImpl] (executor-thread-2) Initiating JDBC connection release from beforeTransactionCompletion
 2025-07-02 15:20:59,933 DEBUG [org.hib.eng.jdb.bat.int.BatchImpl] (executor-thread-2) PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : insert into testdb.TestEntity (testCol,id) values (?,?)
 2025-07-02 15:20:59,946 FINE [org.pos.jdb.PgConnection] (executor-thread-2) setAutoCommit = true
 2025-07-02 15:20:59,947 DEBUG [org.hib.eng.jdb.bat.int.BatchImpl] (executor-thread-2) PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : insert into testdb.TestEntity (testCol,id) values (?,?)
 2025-07-02 15:20:59,947 DEBUG [org.hib.res.jdb.int.LogicalConnectionManagedImpl] (executor-thread-2) Initiating JDBC connection release from afterTransaction
 2025-07-02 15:20:59,947 DEBUG [org.hib.cac.int.TimestampsCacheEnabledImpl] (executor-thread-2) Invalidating space [testdb.TestEntity], timestamp: 1751462459947
 2025-07-02 15:20:59,948 DEBUG [org.hib.eng.jdb.int.JdbcCoordinatorImpl] (executor-thread-2) HHH000420: Closing un-released batch
 2025-07-02 15:20:59,948 DEBUG [org.hib.eng.jdb.bat.int.BatchImpl] (executor-thread-2) PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : insert into testdb.TestEntity (testCol,id) values (?,?)
 2025-07-02 15:20:59,948 DEBUG [org.hib.eng.jdb.bat.int.BatchImpl] (executor-thread-2) PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : insert into testdb.TestEntity (testCol,id) values (?,?)

当前配置(application.properties)

quarkus.datasource.jdbc.transactions=enabled
quarkus.transaction-manager.default-transaction-timeout=6000 #high timeout for testing purposes
quarkus.hibernate-orm.jdbc.statement-batch-size=1000
quarkus.hibernate-orm.unsupported-properties."hibernate.order_inserts" = true

排查与解决方法

1. 调整实体ID生成策略

当前使用GenerationType.UUID,Hibernate会预生成UUID并立即分配给实体,导致每个插入操作都需要单独执行,无法触发批量。

修改方案:
改用数据库端生成的ID策略,比如PostgreSQL的IDENTITY:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id; // 对应数据库BIGSERIAL类型

或使用SEQUENCE(需提前在数据库创建序列):

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "test_entity_seq")
@SequenceGenerator(name = "test_entity_seq", sequenceName = "testdb.test_entity_seq", allocationSize = 1000)
@Column(name = "id")
private Long id;

注意:allocationSize需与statement-batch-size匹配,减少序列查询次数。

2. 完善Hibernate批量配置

在application.properties中补充以下配置:

# 启用版本化数据的批量操作
quarkus.hibernate-orm.jdbc.batch-versioned-data=true
# 显式设置批量大小
quarkus.hibernate-orm.unsupported-properties."hibernate.jdbc.batch_size"=1000
# 优化更新操作的批量排序
quarkus.hibernate-orm.unsupported-properties."hibernate.order_updates"=true

3. 修复Repository的注入方式

TestResource中手动new TestEntityRepository()是错误的,PanacheRepository是CDI Bean,必须通过依赖注入获取,否则无法正确关联Hibernate上下文。

修改TestResource:

@Path("/test")
public class TestResource {

    @Inject
    TestEntityRepository testEntityRepository; // 依赖注入

    @POST
    @Transactional
    public Response hello() {
        try {
            List<TestEntity> testEntities = new ArrayList<>();
            for (int i = 0; i < 10; i++) {
                testEntities.add(new TestEntity.Builder().testCol(i).build());
            }

            testEntityRepository.persist(testEntities);
            testEntityRepository.flush(); // 手动触发批量执行

            return Response.ok().build();
        } catch (Exception e) {
            e.printStackTrace();
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }
}

4. 优化PostgreSQL JDBC驱动配置

在数据源URL中添加reWriteBatchedInserts=true,驱动会将批量insert转换为PostgreSQL原生的多行insert语法,提升插入性能:

quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/your_database?reWriteBatchedInserts=true

5. 验证批量生效

修改后观察控制台日志,若出现包含多组(?, ?)的insert语句,说明批量已生效:

2025-xx-xx xx:xx:xx,xxx DEBUG [org.hib.eng.jdb.spi.SqlStatementLogger] (executor-thread-2)
    insert 
    into
        testdb.TestEntity
        (testCol, id) 
    values
        (?, ?), (?, ?), (?, ?)

内容的提问来源于stack exchange,提问作者mrlihere

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 19:09:52