如何创建一个线程安全的EntityManagerFactory?

8
我正在开发一个需要进行数据库操作的应用程序。
我创建了一个静态变量 EntityManagerFactory,并在应用程序调用的方法中对其进行了初始化。
 if (emf == null) {
      emf = Persistence.createEntityManagerFactory("example");
 }

 try {
      em = emf.createEntityManager();
 } catch (Exception ex) {
      logger.error(ex.getMessage());
 }

这个线程安全吗?如果我在同步块中创建EntityManagerFactory,等待线程的数量会增加并导致应用程序崩溃。

我查看了文档,但没有找到有关Persistence.createEntityManagerFactory是否线程安全的信息。

请引导我找到正确的资源。

5个回答

12

一种“解决”这个问题的简单方法是使用一个帮助类(比如HibernateUtil),并在静态初始化块中初始化EntityManagerFactory。例如:

public class JpaUtil { 
    private static final EntityManagerFactory emf;

    static {
        try {
            factory = Persistence.createEntityManagerFactory("MyPu");
        } catch (Throwable ex) {
            logger.error("Initial SessionFactory creation failed", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

   ...

}

问题已经解决。


我之前做过这个。但是,有些人觉得在静态块中初始化不是一个好习惯。这是对的吗? - Vanchinathan Chandrasekaran
1
@Vanchinathan 这是在非托管环境下的典型方法,我认为这种方法没有任何问题。如果您有反对意见,我们可以讨论一下,但在此之前,我仍然坚持这个建议。 - Pascal Thivent
2
我经常听到的一个论点是,在静态块中编写代码会妨碍测试。我们严格遵循测试驱动开发。因此,我需要一些更容易测试的东西。 - Vanchinathan Chandrasekaran
@Vanchinathan,尽管如此,您仍然希望仅初始化EMF一次,这就是使用上述模式的原因。然后,您应该使用此代码获取EntityManager并将其注入需要它的类中(这些类将很好地进行测试)。 - Pascal Thivent
问题是createEntityManager()是否线程安全?经过查看一些代码和类定义后,答案是它在“应用程序范围”内是线程安全的。然而,EntityManager实例不是线程安全的。所以这个方法可以工作,尽管我建议使用简单的Singleton模式来获取工厂的句柄。原因是static块取决于容器类加载器何时加载。这可能是在持久性管理器框架加载之前。 - Darrell Teague

3

我认为使用静态块的方法没有问题。或者您可以使用以下方式,这是一个带有双重锁定检查的单例模式

public class JPAHelper {

 private static JPAHelper myHelper = new JPAHelper();
 private static EntityManagerFactory myFactory = null;

 /**
  * Private constructor. Implementing synchronization with double-lock check
  */
 private JPAHelper() {

  if(myFactory == null) {
   synchronized (JPAHelper.class) {

    // This second check will be true only for the first thread entering the block incase 
    // of thread race
    if(myFactory == null) {
     myFactory = Persistence.createEntityManagerFactory("MyUnit");
    }
   }
  }
 }

 /**
  * Static Accessor Method
  * @return
  */
 public static JPAHelper getInstance() {
  if(myHelper == null) {
   myHelper = new JPAHelper();
  }
  return myHelper;
 }


 public EntityManagerFactory getJPAFactory() {
  return myFactory;
 }

你将会调用

EntityManager myManager = JPAhelper.getInstance().getJPAFactory().createEntityManager();

推荐使用单例模式,但没有必要(或效果)使用双重检查锁定。createEntityManagerFactory()已经是线程安全的,并且无论如何都会返回相同的工厂句柄。一个正确编写的静态加载工厂的单例将始终返回一个句柄。双重检查锁定在实际应用容器/系统中不起作用。请参见:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html - Darrell Teague
双重检查锁在实际应用中是有效的。上述代码应将myFactory声明为volatile。同一引用文章解释了“使用volatile修复双重检查锁定”。 - mcoolive

1
无论 createEntityManagerFactory() 是否线程安全,您需要一些策略来确保它只被调用一次。换句话说,那个问题是不相关的,因为您必须确保只有一个线程调用它。
如果简单地等待另一个线程创建工厂会使您的应用程序崩溃,那么当每个线程创建自己的工厂并在过程中破坏其他线程的工作时会发生什么?
您展示的代码应该在一个synchronized块内,否则它就不是线程安全的。

没有必要为了只调用一次工厂而制定策略。无论如何都必须进行查找,而Singleton模式将确保始终返回对当前(唯一)实例的句柄。所有调用线程将获得对同一工厂的相同引用。不需要同步,此外,这样做会创建性能问题,因为线程正在争夺监视器,只是为了返回唯一的工厂句柄。 - Darrell Teague
@DarrellTeague 一个调用者如何确定工厂是共享的还是非共享的,从而决定是否应该由该调用者关闭?根据原帖,工厂的初始化需要耗费时间。在应用程序的整个生命周期中创建多个不必要的实例似乎比任何可能发生的争用更严重,而这只需要同步和读取一次变量即可,一旦初始化完成。 - erickson
我正在使用Persistence.createEntityManagerFactory(ID)作为静态的、单例的实例,并且它是线程安全的。需要保护的是EntityManager的实例,来自EMF.createEntityManager()。这需要在给定的方法中进行保护,接着创建一个事务,提交它,然后调用EntityManager上的close()方法(这与J2EE DataSource Connection.close()相同,将JDBC连接释放回池管理器)。这与使用DataSource和通过池管理器使用JDBC连接的JPA之前的模式相同。 - Darrell Teague

1

在创建emf时,您需要在对象上放置锁。您可以将锁放在emf对象本身上,但这不是最佳实践。创建另一个对象:

private object factoryLockObject = new object();

在创建工厂时,请对其进行加锁

lock(factoryLockObject) {
   if (emf == null) {
      emf = Persistence.createEntityManagerFactory("example");
   }
}

这有帮助吗?


那有什么区别呢?我来试试看。 - Vanchinathan Chandrasekaran
这将在factoryLockObject上添加一个对象锁,并导致任何其他想要访问它的线程等待,直到锁被解除(在最后的大括号处)。 - Brad
但它不会阻塞线程吗?如果 Persistence.createEntityManagerFactory("example") 是线程安全的,那么我就不需要进行同步。 - Vanchinathan Chandrasekaran
我不知道Persistence是否在内部是线程安全的。我展示的锁定将会导致任何想要执行此代码块的线程等待,直到第一个线程完成它。这就是为什么你的IF语句也必须放在锁内部。 - Brad

0
问题的答案是:是的,根据类文档、源代码和实际应用结果,createEntityManagerFactory() 是线程安全的。
单例模式的答案最正确,可以有效地避免额外调用以检索一个工厂句柄,但请注意,不需要进行双重检查锁定,如之前所述。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接