如何防止使用 guice-persist 和 @Transactional 重用 EntityManager?
How to prevent reuse of EntityManager with guice-persist and @Transactional?
根据这个 question,当使用 guice-persist
时,EntityManager
是事务范围的。如果我理解正确的话,这意味着将为每笔交易创建一个新的EntityManager
。使用guice-persist
时,建议使用JpaPersistModule
,它提供了所有的绑定,只需将Provider<EntityManager>
注入到一些class中,像这样:
public class ProjectDAO {
private final Provider<EntityManager> entityManagerProvider;
@Inject
public ProjectDAO(Provider<EntityManager> entityManagerProvider) {
this.entityManagerProvider = entityManagerProvider;
}
}
注意:在这个answer it says that EntityManager
should not be injected directly, but to use Provider<EntityManager>
instead, to avoid this issue, therefore the injection of Provider<EntityManager>
. Also, by looking at the code for JpaPersistService中,EntityManager
个实例存储在ThreadLocal
中。同时,@Transactional
注解及其JpaLocalTxnInterceptor
注解应确保在每次交易后在ThreadLocal<EntityManager>
字段上调用.set()
和.remove()
。
现在,我试过了,每个线程都有自己的 EntityManager
。但是,它似乎并没有被删除并重新设置,而是被重新用于后续事务,即 Hibernate 的一级缓存没有被清除。
这是一个完整的示例,它从两个不同的线程(顺序地,而不是并行地)插入和删除一些实体,这会导致一个线程具有陈旧的信息:
项目(一个简单实体)
@NamedQueries({
@NamedQuery(name = "project.findAll", query = "from project"),
@NamedQuery(name = "project.deleteByProjectName", query = "delete from project p where p.name = :project_name")
}
)
@Entity(name = "project")
public class Project {
@Id
@GeneratedValue
private Long id;
@Column(name="name")
private String name;
// ... getters/setters
}
ProjectDAO
public class ProjectDAO {
private final Provider<EntityManager> entityManagerProvider;
@Inject
public ProjectDAO(Provider<EntityManager> entityManagerProvider) {
this.entityManagerProvider = entityManagerProvider;
}
public void insert(Project project) {
entityManagerProvider.get().persist(project);
}
public List<Project> findAll() {
return entityManagerProvider.get()
.createNamedQuery("project.findAll", Project.class)
.getResultList();
}
public void delete(String projectName) {
entityManagerProvider.get()
.createNamedQuery("project.deleteByProjectName")
.setParameter("project_name", projectName)
.executeUpdate();
}
public Project findById(Long id) {
return entityManagerProvider.get().find(Project.class, id);
}
}
项目服务
public class ProjectService {
private final ProjectDAO projectDAO;
@Inject
public ProjectService(ProjectDAO projectDAO) {
this.projectDAO = projectDAO;
}
@Transactional
public void addProject(Project project) {
projectDAO.insert(project);
}
@Transactional
public List<Project> findAll() {
return projectDAO.findAll();
}
@Transactional
public void delete(String projectName) {
projectDAO.delete(projectName);
}
@Transactional
public Project findById(Long id) {
return projectDAO.findById(id);
}
public EntityManager getEntityManager() {
return projectDAO.getEntityManager();
}
}
主要class
public class Start {
public static void main(String[] args) throws InterruptedException {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new JpaPersistModule("hibernatetesting"));
bind(ProjectService.class).in(Scopes.SINGLETON);
}
});
ProjectService projectService = injector.getInstance(ProjectService.class);
PersistService persistService = injector.getInstance(PersistService.class);
persistService.start();
// For the purpose of making transactions from different threads, we
// create two single threaded executors
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
// Execute a few queries from Thread 1
CountDownLatch countDownLatch1 = new CountDownLatch(1);
executorService1.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project1"));
projectService.addProject(new Project("project2"));
countDownLatch1.countDown();
});
countDownLatch1.await();
// Execute a few queries from Thread 2
CountDownLatch countDownLatch2 = new CountDownLatch(1);
executorService2.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project3"));
projectService.addProject(new Project("project4"));
//----
projectService.delete("project1");
//----
// project3 is not shown in this list
projectService.findAll().forEach(System.out::println);
countDownLatch2.countDown();
});
countDownLatch2.await();
// Execute a few more queries from Thread 1
CountDownLatch countDownLatch3 = new CountDownLatch(1);
executorService1.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project5"));
projectService.addProject(new Project("project6"));
// project3, which was deleted in Thread 2 is still visible in
// this EntityManager
// ----
Project project = projectService.findById(3L);
System.out.println("Project still exists " + project);
// ----
projectService.findAll().forEach(System.out::println);
countDownLatch3.countDown();
});
countDownLatch3.await();
}
}
pom.xml
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.11.Final</version>
</dependency>
</dependencies>
...
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="hibernatetesting" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:testDB"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
1) 这是将 EntityManager 与 guice-persist 一起使用并解决不同线程可能具有不同状态这一事实的常用方法吗?
2) 如果不是,如何确保在每次事务后在 ThreadLocal 上重新设置 EntityManager?
上面的代码有两个问题:
1) 下面一行
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
已添加用于调试目的。但是,调用 ProjectDAO.getEntityManager()
的方法 ProjectService.getEntityManager()
又调用 entityManagerProvider.get()
,但未使用 @Transactional
进行注释。这会导致 EntityManager
每个线程设置一次并且永远不会取消设置,即使稍后调用 ProjectService 中具有 @Transactional
注释的其他方法也是如此。只需添加此注释即可解决问题。
2) 在一个线程中,名称为 "project1" 的实体已被删除
//----
projectService.delete("project1");
//----
然而,在另一个线程中,验证了另一个实体的存在
// project3, which was deleted in Thread 2 is still visible in this EntityManager
Project project = projectService.findById(3L);
System.out.println("Project still exists " + project);
一开始就没有删除。实体被一个接一个地添加 - project1、project2、project3... 并分别为它们分配 ID 1、2、3...。所以代码应该是
// project1, which was deleted in Thread 2 is still visible in this EntityManager
Project project = projectService.findById(1L);
System.out.println("Project still exists " + project);
根据这个 question,当使用 guice-persist
时,EntityManager
是事务范围的。如果我理解正确的话,这意味着将为每笔交易创建一个新的EntityManager
。使用guice-persist
时,建议使用JpaPersistModule
,它提供了所有的绑定,只需将Provider<EntityManager>
注入到一些class中,像这样:
public class ProjectDAO {
private final Provider<EntityManager> entityManagerProvider;
@Inject
public ProjectDAO(Provider<EntityManager> entityManagerProvider) {
this.entityManagerProvider = entityManagerProvider;
}
}
注意:在这个answer it says that EntityManager
should not be injected directly, but to use Provider<EntityManager>
instead, to avoid this issue, therefore the injection of Provider<EntityManager>
. Also, by looking at the code for JpaPersistService中,EntityManager
个实例存储在ThreadLocal
中。同时,@Transactional
注解及其JpaLocalTxnInterceptor
注解应确保在每次交易后在ThreadLocal<EntityManager>
字段上调用.set()
和.remove()
。
现在,我试过了,每个线程都有自己的 EntityManager
。但是,它似乎并没有被删除并重新设置,而是被重新用于后续事务,即 Hibernate 的一级缓存没有被清除。
这是一个完整的示例,它从两个不同的线程(顺序地,而不是并行地)插入和删除一些实体,这会导致一个线程具有陈旧的信息:
项目(一个简单实体)
@NamedQueries({
@NamedQuery(name = "project.findAll", query = "from project"),
@NamedQuery(name = "project.deleteByProjectName", query = "delete from project p where p.name = :project_name")
}
)
@Entity(name = "project")
public class Project {
@Id
@GeneratedValue
private Long id;
@Column(name="name")
private String name;
// ... getters/setters
}
ProjectDAO
public class ProjectDAO {
private final Provider<EntityManager> entityManagerProvider;
@Inject
public ProjectDAO(Provider<EntityManager> entityManagerProvider) {
this.entityManagerProvider = entityManagerProvider;
}
public void insert(Project project) {
entityManagerProvider.get().persist(project);
}
public List<Project> findAll() {
return entityManagerProvider.get()
.createNamedQuery("project.findAll", Project.class)
.getResultList();
}
public void delete(String projectName) {
entityManagerProvider.get()
.createNamedQuery("project.deleteByProjectName")
.setParameter("project_name", projectName)
.executeUpdate();
}
public Project findById(Long id) {
return entityManagerProvider.get().find(Project.class, id);
}
}
项目服务
public class ProjectService {
private final ProjectDAO projectDAO;
@Inject
public ProjectService(ProjectDAO projectDAO) {
this.projectDAO = projectDAO;
}
@Transactional
public void addProject(Project project) {
projectDAO.insert(project);
}
@Transactional
public List<Project> findAll() {
return projectDAO.findAll();
}
@Transactional
public void delete(String projectName) {
projectDAO.delete(projectName);
}
@Transactional
public Project findById(Long id) {
return projectDAO.findById(id);
}
public EntityManager getEntityManager() {
return projectDAO.getEntityManager();
}
}
主要class
public class Start {
public static void main(String[] args) throws InterruptedException {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
install(new JpaPersistModule("hibernatetesting"));
bind(ProjectService.class).in(Scopes.SINGLETON);
}
});
ProjectService projectService = injector.getInstance(ProjectService.class);
PersistService persistService = injector.getInstance(PersistService.class);
persistService.start();
// For the purpose of making transactions from different threads, we
// create two single threaded executors
ExecutorService executorService1 = Executors.newSingleThreadExecutor();
ExecutorService executorService2 = Executors.newSingleThreadExecutor();
// Execute a few queries from Thread 1
CountDownLatch countDownLatch1 = new CountDownLatch(1);
executorService1.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project1"));
projectService.addProject(new Project("project2"));
countDownLatch1.countDown();
});
countDownLatch1.await();
// Execute a few queries from Thread 2
CountDownLatch countDownLatch2 = new CountDownLatch(1);
executorService2.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project3"));
projectService.addProject(new Project("project4"));
//----
projectService.delete("project1");
//----
// project3 is not shown in this list
projectService.findAll().forEach(System.out::println);
countDownLatch2.countDown();
});
countDownLatch2.await();
// Execute a few more queries from Thread 1
CountDownLatch countDownLatch3 = new CountDownLatch(1);
executorService1.execute(() -> {
System.out.println("TEST: " + Thread.currentThread().getName());
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
projectService.addProject(new Project("project5"));
projectService.addProject(new Project("project6"));
// project3, which was deleted in Thread 2 is still visible in
// this EntityManager
// ----
Project project = projectService.findById(3L);
System.out.println("Project still exists " + project);
// ----
projectService.findAll().forEach(System.out::println);
countDownLatch3.countDown();
});
countDownLatch3.await();
}
}
pom.xml
...
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-persist</artifactId>
<version>4.2.2</version>
</dependency>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.11.Final</version>
</dependency>
</dependencies>
...
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="hibernatetesting" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:testDB"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect" />
<property name="hibernate.hbm2ddl.auto" value="create" />
</properties>
</persistence-unit>
</persistence>
1) 这是将 EntityManager 与 guice-persist 一起使用并解决不同线程可能具有不同状态这一事实的常用方法吗?
2) 如果不是,如何确保在每次事务后在 ThreadLocal 上重新设置 EntityManager?
上面的代码有两个问题:
1) 下面一行
System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());
已添加用于调试目的。但是,调用 ProjectDAO.getEntityManager()
的方法 ProjectService.getEntityManager()
又调用 entityManagerProvider.get()
,但未使用 @Transactional
进行注释。这会导致 EntityManager
每个线程设置一次并且永远不会取消设置,即使稍后调用 ProjectService 中具有 @Transactional
注释的其他方法也是如此。只需添加此注释即可解决问题。
2) 在一个线程中,名称为 "project1" 的实体已被删除
//----
projectService.delete("project1");
//----
然而,在另一个线程中,验证了另一个实体的存在
// project3, which was deleted in Thread 2 is still visible in this EntityManager
Project project = projectService.findById(3L);
System.out.println("Project still exists " + project);
一开始就没有删除。实体被一个接一个地添加 - project1、project2、project3... 并分别为它们分配 ID 1、2、3...。所以代码应该是
// project1, which was deleted in Thread 2 is still visible in this EntityManager
Project project = projectService.findById(1L);
System.out.println("Project still exists " + project);