多租户与弹簧数据jpa和eclipselink

时间:2014-10-27 13:21:51

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

我试图为我的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数据在初始化阶段生成那些自定义finder findBy * 方法的实现,并将它们放入当前实体管理器的缓存中,而不设置租户ID,我就是无法在此缓存的实体管理器上设置/更改租户ID。我试图根据请求动态更改实体管理器上的租户ID,所以问题是如何更改/设置该缓存实体管理器上的租户ID,当我调用任何自定义查找器时使用strong> 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);
}
}

2 个答案:

答案 0 :(得分:2)

该解决方案基于对BindCallCustomParameter的eclipse-link特定处理,该处理作为租户持有者添加到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);
}

}

答案 1 :(得分:1)

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

我使用字节码工具在Eclipse Link和Spring Data的Multi-Tenancy(每个租户表)上创建了一个Java示例。选择这个想法是为了利用Spring Data的全部功能。

一个人可以执行MultiTenantTest来查看其工作情况。

该想法是开源的,可在Maven Central上获得

步骤:

1.Include依赖项

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