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

