Multi tenancy with EclipseLink and inherited entities

I suppose you know what multi tenancy (MT) is and have read articles about EclipseLink MT. If not good place to start are these articles:

EclipseLink documentation for MT
Examples in EclipseLink wiki
DesignDoc for TablePerTenant MT
Project example from EclipseLink - I don't think this one is very useful but maybe for someone... (it seems quite old anyway)

My environment is Tomcat 7, Java 1.7 32bit and EclipseLink 2.6.0 on Windows 7. But I have also tried the bug mentioned later with Glassfish 3.1 and also EclipseLink 2.6.1 nightly build somewhere around date 24.6.2015 and EclipseLink 2.7.0.

We used one common DB for all tenants and schema per tenant separation. List of all tenants is saved in common DB and each tenant have own DB schema for tenant data. When user access app he needs to have access to his tenant DB but also common DB. This is easy with EclipseLink.

MT Basics

There are two ways how to use EclipseLink and MT. One is shared entity manager factory (EMF) or non shared EMF. Read EclipseLink docs (links above) if you don't know what I mean. I haven't had problem when using non shared EMF. Therefore I advice you to first setup your project and entities according to EclipseLink docs and try it with non shared EMF. Good to notice this one in their docs:
"Multi-tenant metadata can be applied only at the root level of the inheritance hierarchy when using a SINGLE_TABLE or JOINED inheritance strategy."

After that try to use shared EMF if you want to go that way. If you don't have any problems you can just stop reading and be happy :) Or if you want to configure MT with Spring JPA go to section where I wrote about Spring. You can find there configuration advice for Spring even if you don't have any problems with EclipseLink itself.

Problems

I had problems with inherited entities. It could not even initialize as there was still some exception. After a very long time trying to determine what's the problem I think problem is in EclipseLink. I've created bug. It is quite hard go deeply in EclipseLink lib if you don't know much about it and I leave that after 2 weeks... so there is no fix provided directly for EclipseLink lib.

I have decided to use non shared EMF and create one EMF for each tenant in app. I needed to integrate it with Spring JPA transactions and that was the time when another problem started. We are using declarative transactions in code. Maybe if you use transactions in programmatic way it would be easier to use non shared EMF. I haven't tried it because that would mean to rewrite quite a lot code in our app. Anyway it could be handy to know how transactions in Spring works if you have time. These articles provide quite detailed analyse:

Spring @Transactional explained
Spring @PersistenceContext/@PersistenceUnit explained

Spring use JpaTransactionManager which use shared EMF. You can define them more but you would need to know list of tenants in advice and also you would have problem if you would like to use same service method for more tenants as you would need to specify what transaction manager should be used in annotation @PersistenceContext/@PersistenceUnit. I don't think this approach is something you want if you are trying to use MT. Again it is different if implementing transactions in programmatic way. I have tried to dynamically create more JPA transaction managers and pick the one to use when starting transactions. I had it almost working but I didn't like very much the way how it interferes with Spring so I tried to use only one transaction manager with shared EMF for all tenants. It seems I was successful therefore I won't write more on using multiple transaction managers.

Spring configuration

I suppose you know how to configure Spring with JPA. If not read some articles or contact me if you are stuck. If you don't have any problems with EclipseLink and shared EMF you can configure JpaTransactionManager like this:

Instead of this Spring XML configuration you would normally use:

<bean id="emfTransactionManager"
      class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="emf"></property>
</bean>

User this one with new class extending from Spring's JpaTransactionManager:

<bean id="emfTransactionManager"
      class="com.project.MultiTenantJpaTransactionManager">
  <property name="entityManagerFactory" ref="emf"></property>
</bean>

And MultiTenantJpaTransactionManager class:

public class MultiTenantJpaTransactionManager extends JpaTransactionManager {
  @Override
  public Map<String, Object> getJpaPropertyMap() {
    super.getJpaPropertyMap()
         .put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT,
              "tenantIdentificator");
    return super.getJpaPropertyMap();
  }
}

I guess this should work but I haven't tried it as I had problems with EclipseLink. I implemented other solution.

Spring configuration in case of problems

Just overriding getJpaPropertyMap method won't work if you have problems with entity inheritance and EclipseLink MT. It is needed to overwrite method createEntityManagerForTransaction and reinit shared EMF with needed tenant ID. After that entity manager (EM) could be returned. But... it would be too easy, right? I still had problems with my inherited entities so I needed to add @Multitenant annotation also to child (inherited) entities. Not for all but only those with InheritanceType.JOINED strategy. According to EclipseLink docs adding @Multitenant annotation isn't allowed for inherited entities. Adding it fixes first exception but brings in another one. This new one could be fixed with EclipseLink descriptor customizer. If you are really interested why there is need to use the customizer you can read it in closed bug created by me.

Entities example:

@Entity
@Table(name="crm_contact")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="type", discriminatorType=DiscriminatorType.STRING)
@Multitenant(MultitenantType.TABLE_PER_TENANT)
@TenantTableDiscriminator(type= TenantTableDiscriminatorType.SCHEMA, contextProperty= Const.JPA_CONTEXT_PROPERTY)
public abstract class Contact {
...
}

@Entity
@Table(name = "crm_person")
@DiscriminatorValue("PERSON")
@Multitenant(MultitenantType.TABLE_PER_TENANT)
@Customizer(InheritanceJoinedMTFixCustomizer.class)
public class Person extends Contact {
...
}

Descriptor customizer:

public class InheritanceJoinedMTFixCustomizer implements DescriptorCustomizer {

  @Override
  public void customize(ClassDescriptor descriptor) throws Exception {
    // set discriminator to SCHEMA otherwise it stays at SUFFIX which is default in TablePerMultitenantPolicy
    // and ins't set good as EclipseLink don't await @Multitenant annotation on inherited entities
    ((TablePerMultitenantPolicy) descriptor.getMultitenantPolicy())
      .setTenantTableDiscriminatorType(TenantTableDiscriminatorType.SCHEMA);
  }
}

Class MultiTenantJpaTransactionManager:

public class MultiTenantJpaTransactionManager extends JpaTransactionManager {

  /**
   * This process shouldn't be interrupted by concurrent thread.
   * Therefore the method is synchronized.
   */
  @Override
  protected synchronized EntityManager createEntityManagerForTransaction() {
    EntityManager em = null;
    Map<String, Object> properties = getJpaPropertyMap();
    // get EMF from JpaTransactionManager
    EntityManagerFactory emf = ((EntityManagerFactoryInfo) getEntityManagerFactory()).getNativeEntityManagerFactory();
    boolean isMetadataExpired = ((EntityManagerFactoryImpl) emf).unwrap().getSetupImpl().isMetadataExpired();
    // it is needed to get EM to update metadata if they are marked as expired otherwise serverSesstion and
    // therefore also actualTenant value being get below wouldn't be actual
    if (isMetadataExpired) {
      em = (!CollectionUtils.isEmpty(properties) ?
           emf.createEntityManager(properties) : emf.createEntityManager());
    }
    Server ss = JpaHelper.getServerSession(emf);
    String actualTenant = (String) ss.getProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT);
    // don't run it if tenant didn't change
    // it should be quite faster then
    if ((actualTenant == null && SecurityHelper.getActiveTenantSchema() != null) ||
        (actualTenant != null  && !actualTenant.equals(SecurityHelper.getActiveTenantSchema()))) {
      Map newProperties = new HashMap();
      newProperties.put(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, SecurityHelper.getActiveTenantSchema());
      JpaHelper.getEntityManagerFactory(emf).refreshMetadata(newProperties);
    } else
    if (em != null) {
      // don't get it again
      // it is unnecessary
      return em;
    }

    return (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
  }
}

I don't know if this is really good approach but it seems to work so feel free to try it. Comments in code should explain it. SecurityHelper.getActiveTenantSchema() is just my helper method. Replace it with tenant ID you need to use.



Contact form

Please feel free to contact me if you want ask something, offer me cooperation or anything else...





You can also call me at +421 908 284 529 (Slovak Republic).
What I use:

PHP, (X)HTML, Javascript, XML, XSLT, XPath, CSS, Python, MySQL, AJAX, Java, Sencha ExtJs, Aculo Script, MooTools, JQuery, ModX, Kohana PHP framework Apache, XUL, Windows, Linux and more

For more info ask me for my Curriculum Vitae.