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

@PersistenceContext多线程下EntityManager与Session的Hash值疑问

问题描述

我遇到了一个问题,正在排查原因。据我理解,使用@PersistenceContext会向类中注入EntityManager,它应该具备线程感知能力,通过ThreadLocal将EntityManager与线程绑定。但查看我的代码:

package org.example.service;

import org.example.entity.Account;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.hibernate.Session;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountParallelService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void updateAccountAsync(Long accountId, String newName) {
        Session session = entityManager.unwrap(Session.class);
        org.hibernate.internal.SessionImpl realSession =
                entityManager.unwrap(org.hibernate.internal.SessionImpl.class);
        System.out.println("Session code: " + entityManager.unwrap(Session.class).hashCode());
        System.out.println("Thread code: " + Thread.currentThread().hashCode());
        System.out.println("Entity " + entityManager.hashCode());
        System.out.println("realSession " + System.identityHashCode(realSession));
        Account acc = entityManager.find(Account.class, accountId);
        if (acc != null) {
            acc.setFirstName(newName);
            entityManager.merge(acc);
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

}

当我用三个线程调用该方法时,方法在三个不同线程上成功执行,但EntityManager和unwrap得到的Session的哈希码相同,而每个线程的SessionImpl哈希码却不同。我曾见过朋友写的类似代码中,每个线程的Session哈希码是不同的,但可惜没有他的代码用于对比。我是Spring新手,正在学习中。

调用代码:

package org.example;

import org.example.service.AccountParallelService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainApp {

    public static void main(String[] args) {
        System.out.println("Starting Spring Application Context...");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        AccountParallelService parallelService = context.getBean(AccountParallelService.class);
        try {
            ExecutorService service = Executors.newFixedThreadPool(5);

            List<Callable<Void>> tasks = new ArrayList<>();
            tasks.add( () -> { parallelService.updateAccountAsync(1L, "Thread_Name_1"); return null; });
            tasks.add(() -> { parallelService.updateAccountAsync(2L, "Thread_Name_2"); return null; });
            tasks.add(() -> { parallelService.updateAccountAsync(3L, "Thread_Name_3"); return null; });
            service.invokeAll(tasks);
            service.shutdown();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("\nShutting down Spring Context...");
            context.close();
        }
    }
}

程序输出:

Session code: 62790143
Session code: 62790143
Thread code: 498198174
Session code: 62790143
Thread code: 2143293285
Entity 62790143
Thread code: 95675270
Entity 62790143
realSession 1071349779
realSession 620176498
Entity 62790143
realSession 105967982
问题分析与解答

你看到的现象完全正常,核心原因是Spring通过@PersistenceContext注入的不是真实的EntityManager实例,而是一个代理对象:

  • 代理对象的哈希码固定,所以无论哪个线程调用,entityManager.hashCode()都会返回同一个值;unwrap(Session.class)返回的也是Session的代理,哈希码同样固定。
  • 当你调用代理的方法时,它会根据当前线程的事务上下文,获取对应的真实EntityManager(以及Hibernate的SessionImpl实例)——这就是为什么每个线程的SessionImpl的System.identityHashCode不同,它们是各自独立的真实对象。

至于你朋友代码中每个线程Session哈希码不同,大概率是他直接获取了真实Session实例(比如手动创建Session,而非通过Spring代理的EntityManager获取),或者没有使用Spring的事务管理机制。

补充几个关键知识点:

  • 这个EntityManager代理具备线程感知能力,会自动绑定到当前Spring事务,每个事务(对应每个线程)都会分配独立的真实EntityManager和Hibernate Session。
  • 不要通过代理对象的哈希码判断实例唯一性,应该关注真实的SessionImpl对象,或者通过事务上下文来区分。
  • 你的代码逻辑没有问题,多线程下的事务隔离和线程安全由Spring与Hibernate保证,每个线程的操作都是独立的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.01 22:13:11