如何防止guice-persist和@Transactional重用EntityManager? [英] How to prevent reuse of EntityManager with guice-persist and @Transactional?

查看:83
本文介绍了如何防止guice-persist和@Transactional重用EntityManager?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据此问题,使用guice-persist时,EntityManager是事务作用域的.如果我理解正确,这意味着将为每个事务创建一个新的EntityManager.使用guice-persist时,建议使用提供所有绑定的JpaPersistModule,并简单地将Provider<EntityManager>注入某个类,如下所示:

According to this question, when using guice-persist, EntityManager is transaction-scoped. If I understand correctly, this means that a new EntityManager will be created for every transaction. When using guice-persist, it is suggested to use JpaPersistModule, which provides all the bindings, and simply inject Provider<EntityManager> into some class, like this:

public class ProjectDAO {

  private final Provider<EntityManager> entityManagerProvider;

  @Inject
  public ProjectDAO(Provider<EntityManager> entityManagerProvider) {

    this.entityManagerProvider = entityManagerProvider;
  }
} 

注意:在此 answer 中,该文件指出EntityManager不应直接注入,而是改用Provider<EntityManager>,以避免出现问题,因此注入了Provider<EntityManager>.此外,通过查看 JpaPersistService EntityManager实例存储在ThreadLocal中.同时,@Transactional注释及其对应的JpaLocalTxnInterceptor应该确保在每次交易后在ThreadLocal<EntityManager>字段上调用.set().remove().

Note: in this 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 instances are stored in a ThreadLocal. At the same time, @Transactional annotation and its JpaLocalTxnInterceptor counterpart should ensure that .set() and .remove() are called on ThreadLocal<EntityManager> field after every transaction.

现在,我已经尝试过了,每个线程都有自己的EntityManager.但是,似乎它没有被删除并再次设置,而是被重新用于后续事务,即未清除Hibernate的一级缓存.

Now, I've tried this and every thread has its own EntityManager. However, it seems like it is not removed and set again, but reused for subsequent transactions, i.e. Hibernate's first level cache is not cleared.

这是一个完整的示例,该示例从两个不同的线程中插入和删除某些实体(依次而不是并行),这导致一个线程具有陈旧的信息:

Here's a complete example that inserts and deletes some entities from two different threads (sequentially, not in parallel), which leads to one thread having stale information:

项目(一个简单的实体)

    @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);
      }
    }

ProjectService

    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();
      }
    }

主班

    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结合使用并解决不同线程可能具有不同状态的通常方法吗?

1) Is this the usual way of using EntityManager with guice-persist and working around the fact that different threads might have different state?

2)如果不是,如何确保每次交易后在ThreadLocal上重新设置EntityManager?

2) If not, how to make sure that EntityManager is re-set on the ThreadLocal after each transaction?

推荐答案

上面的代码中有两个问题:

There are two problems in the above code:

1)以下行

System.out.println("TEST: " +"EntityManager: " + projectService.getEntityManager().hashCode());

添加

用于调试目的.但是,调用ProjectDAO.getEntityManager()并依次调用entityManagerProvider.get()的方法ProjectService.getEntityManager()没有用@Transactional注释.即使稍后调用ProjectService中具有@Transactional批注的其他方法,这也会导致每个线程一次设置EntityManager且永不取消设置.只需添加此注释即可解决问题.

was added for debugging purposes. However, the method ProjectService.getEntityManager(), which calls ProjectDAO.getEntityManager(), which in turn calls entityManagerProvider.get(), is not annotated with @Transactional. This causes EntityManager to be set once per thread and never unset, even though other methods from ProjectService that have the @Transactional annotation are called later. Simply adding this annotation solves the problem.

2)在一个线程中,名称为"project1"的实体被删除

2) In one thread, entity with name "project1" was deleted

   //----
   projectService.delete("project1");
   //----

但是,在另一个线程中,验证了另一个实体的存在

however, in the other thread, presence is verified for another entity

   // 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....所以代码应该是

which was never deleted in the first place. Entities are added one by one - project1, project2, project3... and they are assigned IDs 1, 2, 3... respectively. So the code should be

   // 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);

这篇关于如何防止guice-persist和@Transactional重用EntityManager?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆