使用Hibernate Envers ValidityAuditStrategy时审计表无法批量插入如何解决
问题根因
你遇到的批量插入失效问题,是Hibernate Envers的ValidityAuditStrategy(有效性审计策略)的固有特性导致的:该策略为了维护审计表中每条记录的revend(结束修订号)字段,每次处理集合元素变更时,都会执行SELECT ... FOR UPDATE语句查询对应关联关系的上一个有效版本。而Hibernate在执行任何查询前都会自动flush session中排队的未执行语句,导致原本要批量执行的审计表insert语句被逐条刷到数据库,完全失去批量效果。
解决方案
方案1:切换为默认审计策略(最推荐,改造成本最低)
直接将Envers的审计策略切换为DefaultAuditStrategy,该策略不需要维护revend字段,不会触发额外的SELECT ... FOR UPDATE查询,集合变更的审计插入可以正常批量执行。
配置方式如下(Spring Boot环境为例,其他环境修改对应Hibernate配置即可):
# 切换Envers审计策略为默认策略 spring.jpa.properties.org.hibernate.envers.audit_strategy = org.hibernate.envers.strategy.internal.DefaultAuditStrategy
注意:该策略的缺点是查询历史版本时性能会低于ValidityAuditStrategy,如果你的业务以写入为主、历史版本查询频率低,该方案是最优选择。
方案2:升级Hibernate版本
你当前使用的Hibernate 5.4.27版本中,ValidityAuditStrategy的集合变更处理逻辑没有做批量优化。该问题在Hibernate 5.6及以上版本、6.0+版本中已经修复:Envers会对同一个集合的批量变更的查询逻辑做合并,减少SELECT ... FOR UPDATE的执行次数,同时优化了flush触发逻辑,不会每次查询都导致未执行的insert被逐条刷出。
方案3:(下策)关闭对应集合的审计
如果该集合的变更不需要留存审计记录,可以直接给集合字段加@NotAudited注解,跳过审计逻辑,自然不会有批量失效问题:
@ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.DETACH, CascadeType.MERGE }) @BatchSize(size = 30) @OrderBy("id ASC") @JoinTable( name = "element_groups", joinColumns = @JoinColumn(name = "group_id"), inverseJoinColumns = @JoinColumn(name = "member_id") ) @NotAudited // 关闭该字段的审计 protected List<Element> members = new LinkedList<Element>();
补充PostgreSQL批量插入必开配置
无论选择哪种方案,都要确保Hibernate和PostgreSQL驱动的批量配置已经开启,否则就算Envers逻辑正常也无法实现真正的批量插入:
# 配置Hibernate批量大小,根据业务场景调整数值 spring.jpa.properties.hibernate.jdbc.batch_size = 50 # 开启插入/更新排序,匹配批量执行要求 spring.jpa.properties.hibernate.order_inserts = true spring.jpa.properties.hibernate.order_updates = true spring.jpa.properties.hibernate.jdbc.batch_versioned_data = true # PostgreSQL驱动必须开该参数,否则会把Hibernate的批量语句拆为单条执行 spring.datasource.hikari.data-source-properties.reWriteBatchedInserts = true
内容的提问来源于stack exchange,提问作者Ploni

