前言: 因为系统要服务另外一家公司,那么问题就来了。1,采用分布式系统,推翻原有的系统架构。2,业务是一致的主要是不同的公司,互补影响,那么就要从数据上做隔离,想到了分库。总之,大概思考了一个小时,决定来使用mybatis拦截器尝试来实现。大致是在现有数据库所有的业务表都 加入 companyId 字段,以此字段来作为区分和拦截的由于之前没有使用过mybatis拦截器,那么一切都从0 开始百度+查询资料;网上有很多关于拦截器的资料信息,这里贴上两处本人认为有用的参考资料 A 拦截器原理出处:https://www.jianshu.com/p/7c7b8c2c985d
1 拦截器的四种代理模式, StatementHandler是Mybatis直接和数据库执行sql脚本的对象。另外它也实现了Mybatis的一级缓存。这里我采用的是其中的一种模式用插件来实现对一级缓存的操作(禁用等等)。
2 拦截器模式定下,那么开始在spring-mybatis.xml 加入拦截器插件;
<property name="plugins">
<array>
<bean class="com.wangzhixuan.commons.intercept.TestInterceptor"></bean>
</array>
</property>
package com.wangzhixuan.commons.intercept;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.util.Properties;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.wangzhixuan.commons.sqlparser.SelectVisitorImpl;
import net.sf.jsqlparser.parser.CCJSqlParserManager;
import net.sf.jsqlparser.statement.select.Select;
@Intercepts({
@Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class, Integer.class})
})
public class TestInterceptor implements Interceptor {
// 控制器本来就是单例,这样似乎更加合理
protected Logger logger = LogManager.getLogger(getClass());
private CCJSqlParserManager parserManager = new CCJSqlParserManager();
@Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
if(invocation.getTarget() instanceof StatementHandler) {
//逻辑代码区
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
// 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
// 可以分离出最原始的的目标类)
while (metaStatementHandler.hasGetter("h")) {
Object object = metaStatementHandler.getValue("h");
metaStatementHandler = SystemMetaObject.forObject(object);
}
// 分离最后一个代理对象的目标类
while (metaStatementHandler.hasGetter("target")) {
Object object = metaStatementHandler.getValue("target");
metaStatementHandler = SystemMetaObject.forObject(object);
}
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
//获取sql
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
if(sql.contains("NEXTVAL")){ // 去掉子序列函数
return invocation.proceed();
}
//获取方法id
String id = mappedStatement.getId();
if (id.contains("InventoryInoutMapper") || id.contains("selectSaleReportByGmtCreate")
|| id.contains("selectBiShipReportByGmtCreate") || id.contains("selectTransNoinventoryReport")
|| id.contains("queryPurStoDetails") || id.contains("queryTaskDailyCloseIds")) { // 剔除不需要走拦截器的方法
return invocation.proceed();
}
//获得方法类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
if ("SELECT".equals(sqlCommandType.toString())) { // 拦截主要是针对查询语句的
//如果是select就将sql转成SELECT对象
try {
Select select = (Select)parserManager.parse(new StringReader(sql));
//访问各个visitor
select.getSelectBody().accept(new SelectVisitorImpl());
//将增强后的sql放回
metaStatementHandler.setValue("delegate.boundSql.sql", select.toString());
return invocation.proceed();
} catch(Throwable e) {
logger.error("拦截器异常Start");
logger.error(e);
logger.error("拦截器异常End");
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//生成代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
//下面是sqlpase的具体用法 (这里我采用的是访问者模式对于所涉及的业务表进行隔离)
package com.wangzhixuan.commons.sqlparser;
import java.util.HashSet;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Sets;
import com.wangzhixuan.commons.shiro.ShiroUser;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.Between;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.GreaterThan;
import net.sf.jsqlparser.expression.operators.relational.GreaterThanEquals;
import net.sf.jsqlparser.expression.operators.relational.IsNullExpression;
import net.sf.jsqlparser.expression.operators.relational.LikeExpression;
import net.sf.jsqlparser.expression.operators.relational.MinorThan;
import net.sf.jsqlparser.expression.operators.relational.MinorThanEquals;
import net.sf.jsqlparser.expression.operators.relational.NotEqualsTo;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.SubJoin;
import net.sf.jsqlparser.statement.select.SubSelect;
import net.sf.jsqlparser.statement.select.TableFunction;
import net.sf.jsqlparser.statement.select.ValuesList;
public class FromItemVisitorImpl implements FromItemVisitor {
private static Logger logger = LoggerFactory.getLogger(FromItemVisitorImpl.class);
// 声明增强条件
private Expression enhancedCondition;
// 定义需要数据隔离的表
private static HashSet<String> interceptTableSet = Sets.newHashSet();
static {
interceptTableSet.add("ITEM");//商品管理数据隔离; 同时item 表中新增 company_id 字段
interceptTableSet.add("ITEM_SKU");//SKU管理数据隔离;
interceptTableSet.add("INVENTORY");//SKU 管理展示库存
interceptTableSet.add("BRAND");//品牌管理
interceptTableSet.add("`OUTER_ORDER`");//主订单管理
interceptTableSet.add("`OUTER_ORDER_DETAIL`");// 主订单明细管理
interceptTableSet.add("ERP_ORDER");//子订单管理
interceptTableSet.add("TASK_DAILY");//采购单
interceptTableSet.add("TASK_DAILY_DETAIL");//采购单明细
interceptTableSet.add("PURCHASE_STORAGE");//采购入库单
interceptTableSet.add("PURCHASE_STORAGE_DETAIL");//采购入库单明细
interceptTableSet.add("INVENTORY_AREA");//库存占用表
interceptTableSet.add("INVENTORY_OUT");//库存记录库记录表
interceptTableSet.add("WAREHOUSE");//仓库改造
interceptTableSet.add("INVENTORY_INOUT");//出入库记录表 {硬编码}
//interceptTableSet.add("`SHIPPING_ORDER`");//发货管理 {硬编码}
//interceptTableSet.add("TASK_DAILY_ORDER");//订单生成采购单 {硬编码}
//interceptTableSet.add("TASK_DAILY_ORDER_HISTORY");// 订单生成采购单历史记录 {硬编码}
interceptTableSet.add("TASK_DAILY_ORDER_UNCOMPLETE"); //未完成采购表
interceptTableSet.add("`ERP_RETURN_ORDER`"); //退单表
interceptTableSet.add("`SELLER_TYPE`");//销售类别表
interceptTableSet.add("`SELLER`");//销售人员表
interceptTableSet.add("`WX_PURCHASE_USER`");//买手人员表
interceptTableSet.add("`GOODS_CASE`");//货源信息反馈表
interceptTableSet.add("LOGISTIC_COMPANY");//快递公司
interceptTableSet.add("RECEIPT");//小票
interceptTableSet.add("RECEIPT_GROUP");//小票另存表
interceptTableSet.add("TASK_RECEIPT");//小票明细
interceptTableSet.add("FIND_ITEM");//发现商品
interceptTableSet.add("FIND_ITEM_SKU");//发现商品sku
interceptTableSet.add("FILE_MANAGE");//文件管理
}
// FROM 表名 <----主要的就是这个,判断用户对这个表有没有权限
@Override
public void visit(Table tableName) {
//判断该表是否是需要操作的表
if (isActionTable(tableName.getFullyQualifiedName())) {
// 声明表达式数组
Expression[] expressions;
// 其他情况,也就是最常用的情况,比如where 1 = 1
Column column = new Column(new Table(tableName.getAlias()!=null?tableName.getAlias().getName():tableName.getFullyQualifiedName()), "company_id");
if(getShiroUser()== null){
return;
}else {
expressions = new Expression[] {column, new LongValue(getShiroUser().getCompanyId())};
}
// 根据运算符对原始数据进行拼接
Expression operator = this.getOperator("=", expressions);
if (this.enhancedCondition != null) {
enhancedCondition = new AndExpression(enhancedCondition , operator);
} else {
enhancedCondition = operator;
}
}
}
// FROM 子查询
@Override
public void visit(SubSelect subSelect) {
// 如果是子查询的话返回到select接口实现类
subSelect.getSelectBody().accept(new SelectVisitorImpl());
}
// FROM subjoin
@Override
public void visit(SubJoin subjoin) {
subjoin.getLeft().accept(new FromItemVisitorImpl());
subjoin.getJoin().getRightItem().accept(new FromItemVisitorImpl());
}
// FROM 横向子查询
@Override
public void visit(LateralSubSelect lateralSubSelect) {
lateralSubSelect.getSubSelect().getSelectBody()
.accept(new SelectVisitorImpl());
}
// FROM value列表
@Override
public void visit(ValuesList valuesList) {
}
// FROM tableFunction
@Override
public void visit(TableFunction tableFunction) {
}
// 将字符串类型的运算符转换成数据库运算语句
private Expression getOperator(String op, Expression[] exp) {
if ("=".equals(op)) {
EqualsTo eq = new EqualsTo();
eq.setLeftExpression(exp[0]);
eq.setRightExpression(exp[1]);
return eq;
} else if (">".equals(op)) {
GreaterThan gt = new GreaterThan();
gt.setLeftExpression(exp[0]);
gt.setRightExpression(exp[1]);
return gt;
} else if (">=".equals(op)) {
GreaterThanEquals geq = new GreaterThanEquals();
geq.setLeftExpression(exp[0]);
geq.setRightExpression(exp[1]);
return geq;
} else if ("<".equals(op)) {
MinorThan mt = new MinorThan();
mt.setLeftExpression(exp[0]);
mt.setRightExpression(exp[1]);
return mt;
} else if ("<=".equals(op)) {
MinorThanEquals leq = new MinorThanEquals();
leq.setLeftExpression(exp[0]);
leq.setRightExpression(exp[1]);
return leq;
} else if ("<>".equals(op)) {
NotEqualsTo neq = new NotEqualsTo();
neq.setLeftExpression(exp[0]);
neq.setRightExpression(exp[1]);
return neq;
} else if ("is null".equalsIgnoreCase(op)) {
IsNullExpression isNull = new IsNullExpression();
isNull.setNot(false);
isNull.setLeftExpression(exp[0]);
return isNull;
} else if ("is not null".equalsIgnoreCase(op)) {
IsNullExpression isNull = new IsNullExpression();
isNull.setNot(true);
isNull.setLeftExpression(exp[0]);
return isNull;
} else if ("like".equalsIgnoreCase(op)) {
LikeExpression like = new LikeExpression();
like.setNot(false);
like.setLeftExpression(exp[0]);
like.setRightExpression(exp[1]);
return like;
} else if ("not like".equalsIgnoreCase(op)) {
LikeExpression nlike = new LikeExpression();
nlike.setNot(true);
nlike.setLeftExpression(exp[0]);
nlike.setRightExpression(exp[1]);
return nlike;
} else if ("between".equalsIgnoreCase(op)) {
Between bt = new Between();
bt.setNot(false);
bt.setLeftExpression(exp[0]);
bt.setBetweenExpressionStart(exp[1]);
bt.setBetweenExpressionEnd(exp[2]);
return bt;
} else if ("not between".equalsIgnoreCase(op)) {
Between bt = new Between();
bt.setNot(true);
bt.setLeftExpression(exp[0]);
bt.setBetweenExpressionStart(exp[1]);
bt.setBetweenExpressionEnd(exp[2]);
return bt;
} else {
// 如果没有该运算符对应的语句
return null;
}
}
public Expression getEnhancedCondition() {
return enhancedCondition;
}
// 判断传入的table是否是要进行操作的table
public boolean isActionTable(String tableName) {
boolean flag = false;
if (interceptTableSet.contains(tableName.toUpperCase())) {
flag = true;
}
return flag;
}
/**
* 获取当前登录用户对象
* @return {ShiroUser}
*/
public ShiroUser getShiroUser() {
ShiroUser one = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
return one;
}
}