EntityManagerFactory 是否应该在应用程序关闭时关闭?

Should EntityManagerFactory be closed at application shutdown?

我有一个 Java 应用程序,它有一个用 Swing 制作的 GUI,并且可以互换地使用两个数据库。两个数据库之一是mongoDB,另一个是MySQL。使用哪个数据库是通过命令行选项选择的。对于 MySQL 数据库,我还使用了 Hibernate 和 JPA。我的代码如下所示:

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import java.awt.EventQueue;
import java.util.concurrent.Callable;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

@Command(mixinStandardHelpOptions = true)
public class App implements Callable<Void> {
    private static final Logger LOGGER = LogManager.getLogger(App.class);

    @Option(names = { "--database" }, description = "'mongo' or 'mysql'")
    private String databaseType = "mysql";

    public static void main(String[] args) {
        new CommandLine(new App()).execute(args);
    }

    @Override
    public Void call() throws Exception {

        EventQueue.invokeLater(() -> {

            switch (databaseType) {
            case "mysql":
                EntityManagerFactory emf;
                EntityManager entityManager;

                try {
                    emf = Persistence.createEntityManagerFactory("name");
                    entityManager = emf.createEntityManager();
                    // other stuff
                    
                } catch (Exception e) {
                    LOGGER.log(Level.ERROR, "MySQL Exception", e);
                }

                break;
            case "mongo":
                // mongo stuff, no EntityManagerFactory here
                break;

            default:
                LOGGER.log(Level.ERROR, "--database must be either 'mysql' or 'mongo'");
                System.exit(1);
            }
            //...
            try {
                View view = new View();
                view.setVisible(true);
            } catch (Exception e) {
                LOGGER.log(Level.ERROR, "Exception", e);
            }
        });
        return null;
    }

mysql 的情况下,我正在创建一个 EntityManagerFactory 和一个 EntityManager。此处创建的 entityManager 作为参数传递给存储库的构造函数,并在应用程序的整个生命周期中使用。 我想知道关闭 entityManager 和工厂的最佳做法是什么。 在 documentation 中搜索,我发现了这个:

Closing an EntityManagerFactory should not be taken lightly. It is much better to keep a factory open for a long period of time than to repeatedly create and close new factories. Thus, most applications will never close the factory, or only close it when the application is exiting.

所以我想知道,在应用程序关闭时关闭工厂和实体管理器与不关闭它们有什么区别?同样在我的例子中,我在 mysql 案例中声明 emfentityManager,因为 mongodb 不需要。为了在应用程序关闭时关闭它们,我应该怎么做?我发现了一些关于 Runtime.getRuntime().addShutdownHook() 的信息。我试过像下面的代码一样使用它,但它似乎不起作用。

try {
    emf = Persistence.createEntityManagerFactory("name");
    entityManager = emf.createEntityManager();
    Thread closeHook = new Thread(() -> {
        if (emf != null) {
            entityManager.close();
            emf.close();
            LOGGER.log(Level.INFO, "Close entity manager and entity manager factory");
        }
    });
    Runtime.getRuntime().addShutdownHook(closeHook);
    // other stuff
                        
} catch (Exception e) {
    LOGGER.log(Level.ERROR, "MySQL Exception", e);
}

简答,是的,应该关闭。原因可以在 this answer:

找到

The JVM will release all active resources upon termination; however, this does not ensure that the other end will free the resource too, so explicitly closing resources is in every programmer's best interest.

所以在我的例子中,EntityManager 和工厂确实在应用程序关闭时关闭,但这并不能确保它们在另一端得到正确处理。 我没有在我的问题中提到它,但实际上同样的事情也适用于 Mongo 客户端(参见 this answer):

If you ever re-deploy your web application without first restarting your application server, you must ensure that the MongoClient is closed when your web application is shutdown.

关于实现,我做了一个接口,我称之为 DBInitializer。我在 main 方法中实例化了一个 MongoInitializerMySQLInitializer 类型的对象(都实现了 DBInitializer)。请参阅代码以获得更清晰的信息。

DBInitializer:

public interface DBInitializer {
    public void startDbConnection();
    public void closeDbConnection();
}

MySQL初始化程序:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class MySQLInitializer implements DBInitializer {
    private EntityManagerFactory emf;
    private EntityManager entityManager;
    private final Logger logger = LogManager.getLogger(MySQLInitializer.class);

    @Override
    public void startDbConnection() {
        try {
            emf = Persistence.createEntityManagerFactory("name");
            entityManager = emf.createEntityManager();
            // other stuff
        } catch (Exception e) {
            logger.log(Level.ERROR, "MySQL Exception", e);
        }
    }
    
    @Override
    public void closeDbConnection() {
        if (emf != null) {
            entityManager.close();
            emf.close();
        }
    }
}

Mongo初始化器:

import com.mongodb.MongoClient;
import com.mongodb.ServerAddress;

public class MongoInitializer implements DBInitializer {
    
    private MongoClient client;
    private final Logger logger = LogManager.getLogger(MongoInitializer.class);
    
    @Override
    public void startDbConnection() {
        try {
            client = new MongoClient(new ServerAddress("localhost", 27017));
            // other stuff
        } catch (Exception e) {
            logger.log(Level.ERROR, "Mongo Exception", e);
        }
    }

    @Override
    public void closeDbConnection() {
        client.close();
    }
}

应用程序:

import java.awt.EventQueue;
import java.util.concurrent.Callable;

import DBInitializer;
import MongoInitializer;
import MySQLInitializer;
import View;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

@Command(mixinStandardHelpOptions = true)
public class App implements Callable<Void> {

    private static final Logger LOGGER = LogManager.getLogger(App.class);

    @Option(names = { "--database" }, description = "Either 'mongo' or 'mysql'")
    private String databaseType = "mysql";

    public static void main(String[] args) {
        new CommandLine(new App()).execute(args);
    }

    DBInitializer dBInitializer;

    @Override
    public Void call() throws Exception {
        EventQueue.invokeLater(() -> {
            try {
                switch (databaseType) {
                case "mysql":
                    dBInitializer = new MySQLInitializer();
                    break;
                case "mongo":
                    dBInitializer = new MongoInitializer();
                    break;
                default:
                    LOGGER.log(Level.ERROR, "--database must be either 'mysql' or 'mongo'");
                    System.exit(1);
                }
                dBInitializer.startDbConnection();
                // other stuff
                View view = new View();
                view.setVisible(true);
                
            } catch (Exception e) {
                LOGGER.log(Level.ERROR, "Exception", e);
            }
        });

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                dBInitializer.closeDbConnection();
            }
        });
        return null;
    }
}

what is the difference between closing the factory and entity manager at application shutdown and not closing it?

潜在的资源泄漏(例如未关闭的连接池)与缺乏。还有:

I tried using it like the code below, but it seems like it is not working.

为什么不使用 try-with-resources 语句?