Search code examples
jpajakarta-eejdbcjndi

Can I use JPA by specifing the database during runtime?


I use MySQL on my production server, where every customer has its own database and its own database user to connect to his database. Each database has the exact same structure. Customers (and therefore databases) are added during the lifetime of the server.

Currently I establish a MySQL database connection with the class java.sql.DriverManager, which works just fine. The databases url, user, password and driver name are stored in several properties-files. The servlet reads the properties-file from the current customer and creates the java.sql.Connection with the java.sql.DriverManager.

I really want to use the JPA, but as far as I know the JPA can only be used by explicitly specifing the databases in the configuration of the webcontainer (in my case Tomcat). But that means, when a new customer registers on my platform, I have to add the new database to the webcontainer-configuration and restart the webcontainer, which does not sound like a good idea.

Is there a way to use the JPA and still have the flexibility to add new databases during the lifetime of the webcontainer?


Solution

  • 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.