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
相关产品推荐
相关产品推荐

