MyBatis spring in a multi tenant application

二次信任 提交于 2021-02-18 07:55:11

问题


Hi needed some help in using MyBatis spring in a multi tenant application ...

Is it possible ? Especially since i do not see how "MapperScannerConfigurer" can be configured with sqlSessionFactory at run time.


回答1:


Spring has AbstractRoutingDataSource to take care of this problem

http://spring.io/blog/2007/01/23/dynamic-datasource-routing/




回答2:


It is possible to create tenant scoped datasource using factory and wire it to SqlSessionFactory which is used by mappers generated by mybatis-spring. Here is relevant app-context.xml section

<bean id="dataSourceFactory" class="com.myapp.TenantDataSourceFactory"
      depends-on="tenant" scope="singleton"/>

<bean id="dataSource" factory-bean="dataSourceFactory" factory-method="getObject"
      destroy-method="close" scope="tenant" >
    <aop:scoped-proxy/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:myBatisConfig.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.myapp" />
    <property name="annotationClass" value="com.myapp.mapper.Mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

And TenantDataSourceFactory:

public class TenantDataSourceFactory {

@Autowired Tenant tenant;

  public DataSource getObject() throws Exception {
    BasicDataSource ds = new BasicDataSource();
    ds.setDriverClassName("org.postgresql.Driver");
    ds.setUrl(String.format("jdbc:postgresql://%s:%s/%s",
            tenant.getDbHost(), tenant.getDbPort(), tenant.getDbName()));
    ds.setUsername(tenant.getDbUser());
    ds.setPassword(tenant.getDbPassword());
    return ds;
  }

}

tenant scope is custom scope that holds Map<String, Map<String, Object>> that is tenant name to scope beans map. It dispatches to given tenant based on notion of current tenant.




回答3:


Here is another approach using a plugin (a.k.a. interceptor) to switch 'schema' or 'catalog'.

Depending on the database you use, each tenant has its own database or schema. A few examples:

  • MySQL : Each tenant has its own 'database' and the plugin should call setCatalog.
  • Oracle : Each tenant has its own 'schema' and the plugin should call setSchema.
  • SQL Server : Each tenant has its own 'database' and the plugin should call setCatalog.

Assuming you pass the tenant ID via ThreadLocal, here is an example plugin implementation.

import java.sql.Connection;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;

@Intercepts(
  @Signature(
    type = StatementHandler.class,
    method = "prepare",
    args = { Connection.class, Integer.class }))
public class MultiTenantInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    Object[] args = invocation.getArgs();
    Connection con = (Connection) args[0];
    con.setSchema(TenantIdHolder.getTenantId());
    // con.setCatalog(TenantIdHolder.getTenantId());
    return invocation.proceed();
  }
}

TenantIdHolder is just a ThreadLocal holder.

public class TenantIdHolder {
  private static ThreadLocal<String> value = new ThreadLocal<>();

  public static void setTenantId(String tenantId) {
    value.set(tenantId);
  }

  public static String getTenantId() {
    return value.get();
  }
}

Here is a demo using HSQLDB.



来源:https://stackoverflow.com/questions/9430294/mybatis-spring-in-a-multi-tenant-application

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!