【问题标题】:Multi tenancy with spring data jpa and eclipselink使用 spring data jpa 和 eclipselink 进行多租户
【发布时间】:2014-12-22 16:31:25
【问题描述】:

我正在尝试向我的 Spring 数据 jpa 存储库添加多租户支持。我想为每个请求动态设置租户 ID,但它不适用于存储库上的自定义查找器 findBy* 方法。 我已遵循本指南:http://codecrafters.blogspot.sk/2013/03/multi-tenant-cloud-applications-with.html

我的存储库如下所示:

public interface CountryRepository extends PagingAndSortingRepository<Country, Long> {

    Country findByName(String name);
    Country findByIsoCountryCode(String isoCountryCode);

}

当我在存储库接口上调用任何自定义查找器 findBy* 方法时,我收到以下错误:

javax.persistence.PersistenceException: Exception [EclipseLink-6174] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.QueryException
Exception Description: No value was provided for the session property [eclipselink.tenant-id]. This exception is possible when using additional criteria or tenant discriminator columns without specifying the associated contextual property. These properties must be set through Entity Manager, Entity Manager Factory or persistence unit properties. If using native EclipseLink, these properties should be set directly on the session.
Query: ReadAllQuery(referenceClass=Country sql="SELECT ID, TENANT_ID, CONTINENT, CREATED_BY, CREATED_DATETIME, CURRENCY, INDEPENDENTFROM, ISOCOUNTRYCODE, LONGNAME, MODIFIED_BY, MODIFIED_DATETIME, NAME, POPULATION, REC_VERSION FROM COUNTRY WHERE ((NAME = ?) AND (TENANT_ID = ?))")
    at org.eclipse.persistence.internal.jpa.QueryImpl.getSingleResult(QueryImpl.java:547)
    at org.eclipse.persistence.internal.jpa.EJBQueryImpl.getSingleResult(EJBQueryImpl.java:400)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:360)
    at com.sun.proxy.$Proxy56.getSingleResult(Unknown Source)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution$SingleEntityExecution.doExecute(JpaQueryExecution.java:197)
    at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:74)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:97)
    at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:88)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:421)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:381)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodIntercceptor.invoke(CrudMethodMetadataPostProcessor.java:111)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy52.findByName(Unknown Source) 

我假设 spring data 在初始化阶段生成了那些自定义查找器 findBy* 方法的实现,并将它们与当前实体管理器一起放入缓存中,而没有设置租户 ID,我是无法在此缓存实体管理器上设置/更改租户 ID。我正在尝试根据请求动态更改实体管理器上的租户 ID,所以问题是如何更改/设置该缓存实体管理器上的租户 ID,当我调用任何自定义查找器 findBy* 方法。

这是我的多租户 querydsl 存储库实现:

public class MultiTenantQueryDslJpaRepository<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID> {
private final CurrentTenantResolver currentTenantResolver;
protected final EntityManager entityManager;

public MultiTenantQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, CurrentTenantResolver currentTenantResolver) {
    this(entityInformation, entityManager, SimpleEntityPathResolver.INSTANCE, currentTenantResolver);
}

public MultiTenantQueryDslJpaRepository(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager, EntityPathResolver resolver, CurrentTenantResolver currentTenantResolver) {
    super(entityInformation, entityManager, resolver);
    this.currentTenantResolver = currentTenantResolver;
    this.entityManager = entityManager;
}

protected void setCurrentTenant() {
    entityManager.setProperty(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, currentTenantResolver.getCurrentTenantId());
}

@Override
protected JPQLQuery createQuery(final Predicate... predicate) {
    setCurrentTenant();
    return super.createQuery(predicate);
}

@Override
public void delete(final T entity) {
    setCurrentTenant();
    super.delete(entity);
}

@Override
public T findOne(final ID id) {
    setCurrentTenant();
    return super.findOne(id);
}

@Override
public void deleteInBatch(final Iterable<T> entities) {
    setCurrentTenant();
    super.deleteInBatch(entities);
}

@Override
public void deleteAllInBatch() {
    setCurrentTenant();
    super.deleteAllInBatch();
}

@Override
public T getOne(final ID id) {
    setCurrentTenant();
    return super.getOne(id);
}

@Override
public boolean exists(final ID id) {
    setCurrentTenant();
    return super.exists(id);
}

@Override
protected TypedQuery<T> getQuery(final Specification<T> spec, final Sort sort) {
    setCurrentTenant();
    return super.getQuery(spec, sort);
}

@Override
public long count() {
    setCurrentTenant();
    return super.count();
}

@Override
protected TypedQuery<Long> getCountQuery(final Specification<T> spec) {
    setCurrentTenant();
    return super.getCountQuery(spec);
}

@Override
public <S extends T> S save(final S entity) {
    setCurrentTenant();
    return super.save(entity);
}
}

【问题讨论】:

  • 你找到解决方案了吗?
  • 是的。该解决方案基于 Eclipse-link 对 BindCallCustomParameter 的特定处理,该处理作为租户持有者添加到 EM 属性映射中。

标签: eclipselink spring-data-jpa multi-tenant


【解决方案1】:

该解决方案基于 Eclipse-link 对 BindCallCustomParameter 的特定处理,该处理作为租户持有者添加到 EM 属性映射。

public class TenantHolder extends BindCallCustomParameter {

private final TenantResolver tenantResolver;

private String defaultTenant;

public TenantHolder(String defaultTenant, TenantResolver tenantResolver) {
    this.defaultTenant = defaultTenant;
    this.tenantResolver = tenantResolver;
}

public String getDefaultTenant() {
    return defaultTenant;
}

@Override
public void set(DatabasePlatform platform, PreparedStatement statement, int index, AbstractSession session) throws SQLException {
    String resolvedTenant = resolveTenant();
    platform.setParameterValueInDatabaseCall(resolvedTenant, statement, index, session);
}

private String resolveTenant() {
    return tenantResolver.resolveTenant(defaultTenant);
}

}

【讨论】:

  • 那么这就是使用 Eclipselink 和 Spring Data 进行多租户工作所需的全部内容吗?不再需要自定义存储库了吗?
  • 您可以根据请求使用自定义存储库设置 tentant id 吗?
  • 你能分享一下,你用的是什么EM属性?
【解决方案2】:

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

使用字节码检测,我使用 Eclipse Link 和 Spring Data 创建了一个关于多租户(每个租户的表)的 java 示例。选择这个想法是为了充分利用 Spring Data 的强大功能。

可以执行MultiTenantTest 来查看它的工作情况。

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

步骤:

1.包含依赖

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

2.创建一个类,如下所示。包、类和方法必须完全相同。

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

3.启动java时添加javaagent

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

【讨论】:

    猜你喜欢
    • 2017-10-23
    • 2014-02-12
    • 1970-01-01
    • 2017-11-19
    • 2018-09-20
    • 2016-05-03
    • 1970-01-01
    • 2014-04-07
    • 2015-01-10
    相关资源
    最近更新 更多