Eclipse Link 多租户不工作

Eclipse Link Multitenancy not working

Eclipse Link 多租户无法正常工作。

示例实体(模式由 liquibase 创建):

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

为了在实体管理器上设置多租户 属性 我使用了一个方面,如下所示:

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

当我启动 Spring 启动应用程序时,它工作得很好。为了在集成测试期间提供租户信息,我定义了一个注释:

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

为了绑定这个值,我使用了 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());
    }
}

通过调试我可以清楚地说 TestExectionListener 在创建任何 EM 之前被调用并且 属性 已为 EM 正确设置。将任何内容保存到数据库时,Eclipse Link 不会为列设置值。

也许有人可以帮我解决这个问题,我不知道为什么 EclipseLink 多租户不起作用。

好的,我成功了。如果有人遇到过类似的问题,这是我的解决方案。

如果使用事务,必须在事务启动后设置租户区分的上下文属性(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");

为了在 Spring Boot/Data 环境中实现这一点,我定制了 Spring 的 JpaTransactionManager。这是问题中方面的补充,因为没有 SELECT 查询的事务。

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

这很容易通过 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";
    }
}

免责声明:这不会回答上述查询,但提供了替代方法。

使用字节码工具,我使用 Eclipse Link 和 Spring 数据创建了一个关于多租户(每个租户 Table)的 java 示例。选择这个想法是为了利用 Spring 数据的全部力量。

可以执行 MultiTenantTest 以查看它是否正常工作。

这个想法是开源的,可以在 Maven Central

步骤:

1.Include依赖

<dependency>
    <groupId>org.bitbucket.swattu</groupId>
    <artifactId>jpa-agent</artifactId>
    <version>2.0.2</version>
</dependency>

2.Create一个class如下图。包,Class 和方法必须完全相同。

package org.swat.jpa.base;
import javax.persistence.EntityManager;
public class EntityManagerFactoryListener {
    /**
     * This method is called by JPA Agent.
     *
     * @param entityManager the entity manager
     */
    public static void afterCreateEntityManager(EntityManager entityManager) {
        //Business logic to set appropriate values in entityManager
        final String tenantId = TenantContext.getCurrentTenantId();
        if (tenantId != null) {
            em.setProperty(EntityManagerProperties.MULTITENANT_PROPERTY_DEFAULT, tenantId);
        }       
    }
}

3.Addjava启动时代理java

-javaagent:{path-to-jpa-agent-jar}