我可以通过在运行时指定数据库来使用 JPA 吗?

Can I use JPA by specifing the database during runtime?

我在我的生产服务器上使用 MySQL,其中每个客户都有自己的数据库和自己的数据库用户来连接到他的数据库。每个数据库都具有完全相同的结构。客户(以及数据库)是在服务器的生命周期内添加的。

目前我与 class java.sql.DriverManager 建立了一个 MySQL 数据库连接,工作正常。数据库 url、用户、密码和驱动程序名称存储在几个属性文件中。 servlet 从当前客户读取属性文件并创建 java.sql.Connectionjava.sql.DriverManager

我真的很想使用 JPA,但据我所知,只能通过在 webcontainer 的配置中明确指定数据库来使用 JPA(在我的例子中 Tomcat)。但这意味着,当新客户在我的平台上注册时,我必须将新数据库添加到 webcontainer-configuration 并重新启动 webcontainer,这听起来不是个好主意。

有没有一种方法可以使用 JPA 并在 web 容器的生命周期内仍然可以灵活地添加新数据库?

不确定这是否对您的特定情况有帮助,但我设置了一个环境,其中 JDBC 连接 URL 在 hibernate.cfg.xml

中指定
<property name="connection.url">
    jdbc:ucanaccess://C:/Users/Public/UCanHibernate.accdb;newDatabaseVersion=V2010
</property>

可以用 Java 系统 属性 覆盖,如下所示:

StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder()
        .configure(); // configures settings from hibernate.cfg.xml

// allow tester to specify their own connection URL (via -D JVM argument)
String runtimeUrl = System.getProperty("HIBERNATE_CONNECTION_URL"); 
if (runtimeUrl != null) {
    ssrb.applySetting("hibernate.connection.url", runtimeUrl);
}

您可能会从其他来源获得覆盖信息,但该原则可能仍然适用。

你绝对可以做到你所要求的,但细节决定成败。

我会称之为 JPA Multitenancy,here 是随机搜索提供的一篇有趣的文章,它提出了一种使用 CDI 的有效方法。我将从更通用的方法开始,考虑到您还需要运行时添加新租户这一事实。

另请注意,Hibernate 为多租户提供了 native solutions。我不知道您使用的是哪个 JPA 提供程序,但我猜其他人也有类似的功能。

基础知识

必须使用单独的、特定于租户的 EntityManagerFactory 实例来完成。确切的数据库连接 URL 可以通过传递给 Persistence.createEntityManagerFactory() 的映射给出。例如,假定 META-INF/persistence.xml 存在,则应用以下代码:

HashMap props = new HashMap();
props.put("javax.persistence.jdbc.url", /* tenant-specific JDBC URL*/);
EntityManagerFactory tenantSpecificEntityManagerFactory =
    Persistence.createEntityManagerFactory("name-of-persistence-unit-from-persistence.xml", props);

props 通常会覆盖 persistence.xml 中指定的任何属性。可能仅覆盖 JDBC URL 就足够了,也许您还需要覆盖 user/password 或其他东西。从那时起,您可以获得特定于租户的持久性上下文并使用它:

EntityManager tenantSpecificEm = tenantSpecificEntityManagerFactory.createEntityManager();

捕获:效率

每次需要租户特定 EntityManager 时都可以执行上面的代码(然后关闭工厂)。但由于以下原因,效率极低

  1. 每个请求重新创建 EntityManagerFactory 很慢
  2. 为每个请求重新创建数据库连接甚至更慢

要解决这些问题,您需要:

  1. 缓存 EntityManagerFactory 个实例
  2. 以某种方式使用连接池

缓存 EntityManagerFactory 个实例

我假设有一个强大的机制将每个请求与适当的租户相关联。此外,您将需要一个应用程序范围的 Map 租户名称到相应的 EntityManagerFactory 实例。如何存储和使用取决于应用程序,例如是否有任何依赖注入框架?第一个 link 有一个 CDI 解决方案,类似的解决方案将适用于其他 DI 容器。

连接池

应用程序服务器提供数据库连接池。 Tomcat 也是。应用程序服务器可能允许您在运行时添加数据库连接池,而无需重新启动服务器。不知道你用的Tomcat版本是否支持(我猜不支持,请指正)。所以:

  • 如果应用程序服务器(Tomcat 在这种情况下)支持 运行时连接池创建,请执行此操作并调整 props 以使用它
  • 否则,您将不得不使用适用于每个 EntityManagerFactory 的自定义连接池。至少 Hibernate 似乎有这个 feature.

保留设置

我想您已经明白了这一点,但是必须以某种方式保留在运行时应用的设置(租户、租户名称到连接属性的映射),以便在服务器重新启动时重新应用它们。这可能是一个配置文件或另一个 "administration" 数据库。