Search code examples
spring-booteclipselinkmulti-tenantspring-test

Eclipse Link Multitenancy not working


Eclipse Link Multitenancy is not working properly.

Example Entity (the schema is being created by liquibase):

@Entity
@Table(name = "ENTITIES")
@Multitenant(MultitenantType.SINGLE_TABLE)
@TenantDiscriminatorColumn(name = "TENANT_ID", contextProperty = "eclipselink.tenant-id")
public class EntityClass

To set the multitenancy property on entity managers I use an aspect, like following:

@Around("execution(* javax.persistence.EntityManagerFactory.*(..))")
public Object invocate(ProceedingJoinPoint joinPoint) throws Throwable {
    final Object result = joinPoint.proceed();

    if (result instanceof EntityManager) {
        EntityManager em = (EntityManager) result;

        final String tenantId = TenantContext.getCurrentTenantId();
        LOG.debug("Set EntityManager property for tenant {}.", tenantId);
        em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT,
                tenantId);

        return em;
    }

    return result;
}

When I start the Spring Boot application this works perfectly. To have tenant information available during integration tests, I defined an annotation:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AsTenant {
    String value();
}

To bind this value, I use a TestExecutionListener:

@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
    final Method testMethod = testContext.getTestMethod();
    final AsTenant asTenantAnnotation = testMethod
            .getAnnotation(AsTenant.class);

    if (asTenantAnnotation != null) {
        TenantContext.setCurrentTenantId(asTenantAnnotation.value());
    }
}

By debugging I can clearly say that the TestExectionListener is called before any EM is created and that the property is properly set for the EMs. When persisting anything to the database, Eclipse Link does not set a value for the column.

Maybe anybody can help me out with this, I have no Idea why EclipseLink Multitenancy is not working.


Solution

  • Ok, I got it working. If anybody ever faces a similar problem, here is my solution to it.

    If using transactions, the context property for the tenant discrimination has to be set after the transaction is started (http://www.eclipse.org/eclipselink/documentation/2.5/solutions/multitenancy002.htm).

    EntityManager em = createEntityManager(MULTI_TENANT_PU);
    em.getTransaction().begin();
    em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, "my_id");
    

    To realise this in Spring Boot/Data environment, I customized Spring's JpaTransactionManager. This stays in addition to the Aspect in the Question, since there is not Transaction for SELECT queries.

    public class MultitenantJpaTransactionManager extends JpaTransactionManager {
    
        /* (non-Javadoc)
         * @see org.springframework.orm.jpa.JpaTransactionManager#doBegin(java.lang.Object, org.springframework.transaction.TransactionDefinition)
         */
        @Override
        protected void doBegin(Object transaction, TransactionDefinition definition) {
            super.doBegin(transaction, definition);
    
            final EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.getResource(getEntityManagerFactory());
            final EntityManager em = emHolder.getEntityManager();
            final String tenantId = TenantContext.getCurrentTenantId();
    
            if (tenantId != null) {
                em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, tenantId);
            }
        }
    }
    

    This is easily wired via JpaConfiguration:

    /**
     * Configures Eclipse Link as JPA Provider.
     */
    @Configuration
    @EnableTransactionManagement
    @AutoConfigureAfter({ DataSourceAutoConfiguration.class })
    public class JpaConfiguration extends JpaBaseConfiguration {
    
        @Bean
        @Override
        public PlatformTransactionManager transactionManager() {
            return new MultitenantJpaTransactionManager();
        }
    
        @Override
        protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
            EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter();
            return adapter;
        }
    
        @Override
        protected Map<String, Object> getVendorProperties() {
            HashMap<String, Object> properties = new HashMap<String, Object>();
    
            properties.put(PersistenceUnitProperties.WEAVING, detectWeavingMode());
    
            return properties;
        }
    
        private String detectWeavingMode() {
            return InstrumentationLoadTimeWeaver.isInstrumentationAvailable()
                ? "true" : "static";
        }
    }