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

多线程环境下基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 10:15:28