基于Mybatis拦截器实现不同用户使用不同的数据表
一、实现思路
将数据表命名格式定为:baseTableName_usertId,sql语句中的表名就直接写为 baseTableName,然后通过定义Mybatis拦截器将baseTableName替换为baseTableName_usertId。
二、定义一个Mybatis拦截器
package com.ruoyi.framework.interceptor;
import com.ruoyi.common.utils.ShiroUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* @description: 动态替换表名拦截器
* @author: zengxi
* @created: 2023/03/03
*/
//method = "query"拦截select方法、而method = "update"则能拦截insert、update、delete的方法
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
//@Component
public class ReplaceTableInterceptor implements Interceptor {
private final static List<String> TABLE_LIST = new ArrayList<String>();
static {
//表名长的放前面,避免字符串匹配的时候先匹配替换子集
TABLE_LIST.add("test");
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 拦截sql
Object[] args = invocation.getArgs();
// 获取MappendStatement对象
MappedStatement ms = (MappedStatement) args[0];
Object parameteObject = args[1];
BoundSql boundSql = ms.getBoundSql(parameteObject);
String sql = boundSql.getSql();
if (isReplaceTableName(sql)){ // 判断是否需要替换表名
String newSql = replaceTableName(sql);
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
MappedStatement newMs = copyMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
args[0] = newMs;
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// 创建代理对象
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
// 可选实现,不需要做任何事情
}
/***
* MappedStatement构造器接受的是SqlSource
* 实现SqlSource接口,将BoundSql封装进去
*/
public static class BoundSqlSqlSource implements SqlSource {
private BoundSql boundSql;
public BoundSqlSqlSource(BoundSql boundSql) {
this.boundSql = boundSql;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return boundSql;
}
}
/***
* 复制一个新的MappedStatement
* @param ms
* @param newSqlSource
* @return
*/
private MappedStatement copyMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
builder.resource(ms.getResource());
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
builder.keyProperty(String.join(",", ms.getKeyProperties()));
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
builder.resultMaps(ms.getResultMaps());
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
/***
* 判断是否需要替换表名
* @param sql
* @return
*/
private boolean isReplaceTableName(String sql){
for(String tableName : TABLE_LIST){
if(sql.contains(tableName)){
return true;
}
}
return false;
}
/***
* 将sql中的表名替换
* @param sql
* @return
*/
private String replaceTableName(String sql){
Long userId = ShiroUtils.getUserId(); // 获取用户id
if (userId != null){
for(String tableName : TABLE_LIST){
String newTableName = tableName + "_" + userId;
sql = sql.replace(tableName,newTableName);
}
}
return sql;
}
}
三、注入Mybatis拦截器
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 将自定义的拦截器加入到拦截器列表中
sessionFactory.setPlugins(new Interceptor[] {new ReplaceTableInterceptor()});
return sessionFactory;
}
注意如果在项目中使用了PageHelper分页插件,会导致query时,拦截器失效,但是insert、update、delete操作时不会失效,解决方法如下
可以在mybatis-config.xml文件中配置拦截器执行顺序
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
<plugin interceptor="com.ruoyi.framework.interceptor.ReplaceTableInterceptor" />
</plugins>