Can I use JPA by specifing the database during runtime?

最后都变了- 提交于 2019-12-06 08:28:28

You can definitely do what you ask, but the demon is in the details.

I would call it JPA Multitenancy, and here is an interesting article provided by a random search, that proposes a valid method using CDI. I will start with a more generic approach, taking into account the fact that you also require runtime additions of new tenants.

Note also that Hibernate offers native solutions for multitenancy. I do not know which JPA provider you are using, but I would guess others have similar features.

Basics

It has to be done using separate, tenant-specific EntityManagerFactory instances. The exact DB connection URL can be given by the map passed to Persistence.createEntityManagerFactory(). E.g., given that META-INF/persistence.xml exists, the following code applies:

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

The props generally override any properties specified in persistence.xml. Probably overriding only the JDBC URL is enough, maybe you will need to override user/password or other things as well. From that point on, you can get the tenant-specific persistence context and work with it:

EntityManager tenantSpecificEm = tenantSpecificEntityManagerFactory.createEntityManager();

Catch: Efficiency

You could execute the code above every time you need a tenant-specific EntityManager (and then close the factory as well). But it would be grossly inefficient for the following reasons:

  1. Creating the EntityManagerFactory anew with each request is slow
  2. Creating the DB connection anew with each request is even slower

To address these issues you will need to:

  1. Cache the EntityManagerFactory instances
  2. Use connection pooling somehow

Caching the EntityManagerFactory instances

I assume there is a robust mechanism to relate each request with the appropriate tenant. Additionally you will need an application-scoped Map of tenant name to the corresponding EntityManagerFactory instance. How this is stored and used depends on the application, e.g. is there any dependency injection framework in place? The first link has a solution with CDI, similar solutions would apply for other DI containers.

Connection pooling

Application servers offer DB connection pooling. Tomcat does too. Application servers might allow you to add DB connection pools at runtime without the need to restart the server. I do not know if the version of Tomcat you are using supports it (I would guess not, but please correct me). So:

  • If the application server (Tomcat in this case) supports runtime connection pool creation, do that and tweak the props to use it
  • Otherwise you will have to use a custom connection pool that applies to each EntityManagerFactory. Hibernate at least seems to have this feature.

Persist the settings

I guess you already have figured this out, but the settings applied at runtime (tenants, tenant name to connection properties mappings) have to be persisted somehow, so that they are reapplied when the server restarts. This could be a configuration file or another "administration" database.

Not sure if this will help in your particular situation, but I set up an environment in which the JDBC connection URL specified in hibernate.cfg.xml

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

could be overriden with a Java System property like so:

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

You would likely get the override information from a different source, but the principle might still apply.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!