多线程环境下基于org.hibernate.SessionFactory按Session统计实体增删改量
多线程环境下按Hibernate Session独立统计实体增删改数量的解决方案
问题背景
存在控制器根据路径变量调用不同服务,每个服务的事务方法执行导入逻辑:调用外部API获取CSV文件、解析转换为实体后存入数据库。原方案通过SessionFactory.getStatistics()统计实体增删改数量,但多线程并行调用时,SessionFactory的统计是全局共享的,会混合不同线程的操作数据,无法单独获取当前Session的统计结果。
可行解决方案
方案1:自定义Session绑定的统计拦截器
通过Hibernate的EmptyInterceptor拦截实体的增删改操作,用ThreadLocal维护当前线程(对应Session)的统计计数,确保多线程下数据隔离。
自定义拦截器实现
public class SessionStatisticsInterceptor extends EmptyInterceptor { // 用ThreadLocal存储当前线程的统计数据,key为操作类型,value为计数 private final ThreadLocal<Map<String, AtomicInteger>> entityStats = ThreadLocal.withInitial(() -> { Map<String, AtomicInteger> stats = new HashMap<>(3); stats.put("INSERT", new AtomicInteger(0)); stats.put("UPDATE", new AtomicInteger(0)); stats.put("DELETE", new AtomicInteger(0)); return stats; }); @Override public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { entityStats.get().get("INSERT").incrementAndGet(); return super.onSave(entity, id, state, propertyNames, types); } @Override public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { entityStats.get().get("DELETE").incrementAndGet(); super.onDelete(entity, id, state, propertyNames, types); } @Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { entityStats.get().get("UPDATE").incrementAndGet(); return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types); } // 获取各操作计数 public int getInsertCount() { return entityStats.get().get("INSERT").get(); } public int getUpdateCount() { return entityStats.get().get("UPDATE").get(); } public int getDeleteCount() { return entityStats.get().get("DELETE").get(); } // 重置当前线程的统计计数 public void reset() { entityStats.get().forEach((key, counter) -> counter.set(0)); } }
修改Support类绑定拦截器
@Component public abstract class Support { private final ImporterLogService importerLogService; private final SessionStatisticsInterceptor sessionStatsInterceptor; @PersistenceContext private EntityManager entityManager; // 通过构造注入拦截器 public Support(ImporterLogService importerLogService, SessionStatisticsInterceptor sessionStatsInterceptor) { this.importerLogService = importerLogService; this.sessionStatsInterceptor = sessionStatsInterceptor; } public Session getCurrentSessionAndInitStats() { Session session = entityManager.unwrap(Session.class); // 将拦截器绑定到当前Session session.setInterceptor(sessionStatsInterceptor); // 重置统计计数,避免之前的线程数据残留 sessionStatsInterceptor.reset(); return session; } public void setSuccessfulImport(Session session, ImporterLog importerLog) { // 从拦截器获取当前Session的统计数据 int insertCount = sessionStatsInterceptor.getInsertCount(); int updateCount = sessionStatsInterceptor.getUpdateCount(); int deleteCount = sessionStatsInterceptor.getDeleteCount(); importerLogService.setSuccessfulImport(importerLog, updateCount, deleteCount, insertCount); } // flushSession方法保持不变 public void flushSession(Session session) { session.flush(); } }
方案2:手动统计实体操作
在解析CSV并处理实体的业务逻辑中,手动记录每个实体的操作类型及数量,不需要依赖Hibernate的统计API,逻辑直观可控。
修改导入器业务方法示例
@Service @Slf4j public class MarketReportImporterImpl extends Support implements MarketReportImporter { @Override @Transactional public void importMarketReports(ImporterLog importerLog) { try { String export = getCsvFile(); Session session = entityManager.unwrap(Session.class); int insertCount = 0; int updateCount = 0; int deleteCount = 0; // 解析CSV为实体列表 List<MarketReport> reports = parseCsvToEntities(export); for (MarketReport report : reports) { if (isNewEntity(report)) { // 自定义判断实体是否为新增的逻辑 session.persist(report); insertCount++; } else { session.merge(report); updateCount++; } } // 若有删除操作,同样记录deleteCount // deleteCount = handleDeleteOperations(session, export); flushSession(session); importerLogService.setSuccessfulImport(importerLog, updateCount, deleteCount, insertCount); } catch (Exception e) { log.error("Failed to import market reports. Unable to parse export", e); getTelemetryClient().trackException(e); importerLogService.setFailedImport(importerLog, e.getMessage()); } } // 自定义判断实体是否为新增的方法 private boolean isNewEntity(MarketReport report) { return report.getId() == null; // 示例逻辑,根据实际业务调整 } // 解析CSV的方法,根据实际业务实现 private List<MarketReport> parseCsvToEntities(String export) { // 实现CSV解析逻辑 return new ArrayList<>(); } }
方案3:使用Hibernate Session级统计(Hibernate 5.3+)
从Hibernate 5.3版本开始,Session提供了专属的SessionStatistics,可以直接获取当前Session的操作统计,无需依赖全局的SessionFactory统计。
修改Support类统计逻辑
@Component public abstract class Support { private final ImporterLogService importerLogService; @PersistenceContext private EntityManager entityManager; public Support(ImporterLogService importerLogService) { this.importerLogService = importerLogService; } public void flushSession(Session session) { session.flush(); } public void setSuccessfulImport(Session session, ImporterLog importerLog) { // 获取当前Session的专属统计 SessionStatistics sessionStats = session.getStatistics(); int entityInsertCount = (int) sessionStats.getEntityInsertCount(); int entityDeleteCount = (int) sessionStats.getEntityDeleteCount(); int entityUpdateCount = (int) sessionStats.getEntityUpdateCount(); importerLogService.setSuccessfulImport(importerLog, entityUpdateCount, entityDeleteCount, entityInsertCount); } public Session getCurrentSessionAndClearSessionStatistics() { Session session = entityManager.unwrap(Session.class); // 清空当前Session的统计数据,而非SessionFactory的全局统计 session.getStatistics().clear(); return session; } }
开启Session级统计配置
在Hibernate配置文件中添加以下配置:
# 开启统计功能 hibernate.generate_statistics=true # 开启Session级统计 hibernate.session.statistics=true
内容的提问来源于stack exchange,提问作者f.trajkovski
相关产品推荐
相关产品推荐

