1

我有一个 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和工厂的最佳做法是什么。在文档中搜索我发现了这个:

关闭 EntityManagerFactory 不应掉以轻心。长期保持工厂开工比反复创建和关闭新工厂要好得多。因此,大多数应用程序永远不会关闭工厂,或者仅在应用程序退出时关闭它。

所以我想知道,在应用程序关闭时关闭工厂和实体管理器与不关闭它有什么区别?同样在我的情况下,我声明emfentityManagermysql案例内部,因为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);
}
4

2 回答 2

2

简短的回答,是的,它应该被关闭。原因可以在这个答案中找到:

JVM 将在终止时释放所有活动资源;但是,这并不能确保另一端也会释放资源,因此明确关闭资源符合每个程序员的最大利益。

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

如果您在没有先重新启动应用程序服务器的情况下重新部署 Web 应用程序,则必须确保在关闭 Web 应用程序时关闭 MongoClient。

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

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

MongoInitializer:

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;
    }
}
于 2022-02-14T11:37:08.170 回答
1

在应用程序关闭时关闭工厂和实体管理器与不关闭它有什么区别?

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

我尝试像下面的代码一样使用它,但它似乎不起作用。

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

于 2022-02-14T11:37:09.657 回答