From 046c2f46877cb6621c4759076a9f2d301f58d223 Mon Sep 17 00:00:00 2001 From: xiaohanzi Date: Tue, 8 Sep 2020 16:30:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=88=86=E8=A1=A8=E5=A4=84?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shard/annotation/IgnoreSharding.java | 16 ++ .../plugin/shard/impl/EnumShardingRule.java | 27 +++ .../shard/impl/ShardingRuleExcuter.java | 29 +++ .../plugin/shard/impl/ShardingSphere.java | 38 +++ .../shard/impl/ShardingTableExcuter.java | 33 +++ .../impl/rule/ShardingRuleDefaultService.java | 17 ++ .../shard/impl/rule/ShardingRuleService.java | 6 + .../shard/plugin/ShardingSpherePlugin.java | 227 ++++++++++++++++++ 8 files changed, 393 insertions(+) create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/annotation/IgnoreSharding.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/EnumShardingRule.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingRuleExcuter.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingSphere.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingTableExcuter.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleDefaultService.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleService.java create mode 100644 mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/plugin/ShardingSpherePlugin.java diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/annotation/IgnoreSharding.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/annotation/IgnoreSharding.java new file mode 100644 index 000000000..c8388fb10 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/annotation/IgnoreSharding.java @@ -0,0 +1,16 @@ +package com.iformall.plugin.shard.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 在mapper方法上面用 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface IgnoreSharding { +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/EnumShardingRule.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/EnumShardingRule.java new file mode 100644 index 000000000..1d48fcf4e --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/EnumShardingRule.java @@ -0,0 +1,27 @@ +package com.iformall.plugin.shard.impl; + +public enum EnumShardingRule { + + HASH(1,"哈希值分表"); + + + private Integer code; + private String name; + + private EnumShardingRule(Integer code, String name) { + this.code = code; + this.name = name; + } + public Integer getCode() { + return code; + } + public void setCode(Integer code) { + this.code = code; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingRuleExcuter.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingRuleExcuter.java new file mode 100644 index 000000000..c6b24dd74 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingRuleExcuter.java @@ -0,0 +1,29 @@ +package com.iformall.plugin.shard.impl; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.druid.util.StringUtils; +import com.iformall.plugin.shard.impl.rule.ShardingRuleDefaultService; +import com.iformall.plugin.shard.impl.rule.ShardingRuleService; + +public class ShardingRuleExcuter { + + private static Map serviceMap = null; + + private static Map getServiceMap() { + if (null == serviceMap) { + serviceMap = new ConcurrentHashMap(); + serviceMap.put(EnumShardingRule.HASH.getCode(), new ShardingRuleDefaultService()); + } + return serviceMap; + } + + public static String getTableIndex(Integer rule,Object value,int tableCount) { + String index = getServiceMap().get(rule).getTableIndex(value, tableCount); + if (StringUtils.isEmpty(index)) { + throw new RuntimeException("Sharding Rule["+rule+"] not support."); + } + return index; + } +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingSphere.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingSphere.java new file mode 100644 index 000000000..b2a6ca318 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingSphere.java @@ -0,0 +1,38 @@ +package com.iformall.plugin.shard.impl; + +public class ShardingSphere { + + public static final String TABLE_SUFFIX = "_"; + /*表名,不用带下划线**/ + private String tableName; + /*分表列名**/ + private String column; + /*分表规则**/ + private Integer rule; + /*表数量**/ + private Integer count; + public String getTableName() { + return tableName; + } + public void setTableName(String tableName) { + this.tableName = tableName; + } + public String getColumn() { + return column; + } + public void setColumn(String column) { + this.column = column; + } + public Integer getRule() { + return rule; + } + public void setRule(Integer rule) { + this.rule = rule; + } + public Integer getCount() { + return count; + } + public void setCount(Integer count) { + this.count = count; + } +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingTableExcuter.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingTableExcuter.java new file mode 100644 index 000000000..7bc62b972 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/ShardingTableExcuter.java @@ -0,0 +1,33 @@ +package com.iformall.plugin.shard.impl; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class ShardingTableExcuter { + + private static Map tableMap = new ConcurrentHashMap(); + + public static Map setTableSharding(String table ,ShardingSphere ss) { + tableMap.put(table, ss); + return tableMap; + } + + public static ShardingSphere getShardingSphere(String table) { + return tableMap.get(table); + } + + public static boolean isShardingTable(String table) { + ShardingSphere ss = getShardingSphere(table); + if (null == ss) { + return false; + } + return true; + } + + public static boolean hasShardingConfig() { + if (null == tableMap || tableMap.size() <= 0) { + return false; + } + return true; + } +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleDefaultService.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleDefaultService.java new file mode 100644 index 000000000..f4a3eec00 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleDefaultService.java @@ -0,0 +1,17 @@ +package com.iformall.plugin.shard.impl.rule; + +public class ShardingRuleDefaultService implements ShardingRuleService { + + @Override + public String getTableIndex(Object value, int count) { + if (null == value || count <= 0 ) { + return null; + } + int hash = value.hashCode(); + if (hash < 0 ) { + hash = Math.abs(hash); + } + return String.valueOf(hash%count); + } + +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleService.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleService.java new file mode 100644 index 000000000..54291bd49 --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/impl/rule/ShardingRuleService.java @@ -0,0 +1,6 @@ +package com.iformall.plugin.shard.impl.rule; + +public interface ShardingRuleService { + + public String getTableIndex(Object value,int count); +} diff --git a/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/plugin/ShardingSpherePlugin.java b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/plugin/ShardingSpherePlugin.java new file mode 100644 index 000000000..438f9916e --- /dev/null +++ b/mybatis-multi-tenancy/src/main/java/com/iformall/plugin/shard/plugin/ShardingSpherePlugin.java @@ -0,0 +1,227 @@ +package com.iformall.plugin.shard.plugin; + +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.Parenthesis; +import net.sf.jsqlparser.expression.StringValue; +import net.sf.jsqlparser.expression.operators.conditional.AndExpression; +import net.sf.jsqlparser.expression.operators.conditional.OrExpression; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.ExpressionList; +import net.sf.jsqlparser.expression.operators.relational.ItemsList; +import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.create.table.ColDataType; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.statement.delete.Delete; +import net.sf.jsqlparser.statement.insert.Insert; +import net.sf.jsqlparser.statement.select.*; +import net.sf.jsqlparser.statement.update.Update; +import net.sf.jsqlparser.util.TablesNamesFinder; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.BoundSql; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.SystemMetaObject; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.ResultHandler; + +import com.iformall.plugin.shard.annotation.IgnoreSharding; +import com.iformall.plugin.shard.impl.ShardingRuleExcuter; +import com.iformall.plugin.shard.impl.ShardingSphere; +import com.iformall.plugin.shard.impl.ShardingTableExcuter; + +import java.lang.reflect.Method; +import java.sql.Connection; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + *分表 + */ +@Intercepts({ + //@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), + //@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) + @Signature( method = "prepare", type = StatementHandler.class,args = {Connection.class, Integer.class}), + @Signature(method = "query", type = StatementHandler.class, args = {java.sql.Statement.class, ResultHandler.class}) +}) +public class ShardingSpherePlugin implements Interceptor { + + + public ShardingSpherePlugin(){ + } + + //属性参数信息 + private Properties properties; + private String dialect = "mysql"; + private List ShardingSpheres; + public String getDialect() { + return dialect; + } + public void setDialect(String dialect) { + this.dialect = dialect; + } + public List getShardingSpheres() { + return ShardingSpheres; + } + public void setShardingSpheres(List shardingSpheres) { + ShardingSpheres = shardingSpheres; + } + + /** + * 生成代理对象 + * + * @param target + * @return + */ + @Override + public Object plugin(Object target) { + if (target instanceof StatementHandler) { + return Plugin.wrap(target, this); + } else { + return target; + } + } + + /** + * 获取配置信息 + * {dialect=mysql, tenantInfo=org.xue.test.TenantInfoImpl, tenantIdColumn=tenant_id, parentTenantIdColumn=parent_tenant_id} + * @param properties + */ + @Override + public void setProperties(Properties properties) { + this.properties=properties; + this.dialect = this.properties.getProperty("dialect"); + if (null != this.ShardingSpheres) { + for (ShardingSphere ss : this.ShardingSpheres) { + ShardingTableExcuter.setTableSharding(ss.getTableName(), ss); + } + } + } + + private Method findMethod(Method[] methods, String methodName) { + for (Method method : methods) { + if (method.getName().equals(methodName)) { + return method; + } + } + + return null; + } + public Object intercept(Invocation invocation) throws Throwable { + if (!ShardingTableExcuter.hasShardingConfig()) { + return invocation.proceed(); + } + StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); + MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); + MappedStatement ms = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement"); + String id = ms.getId(); + String className = id.substring(0, id.lastIndexOf(".")); + String methodName = id.substring(id.lastIndexOf(".") + 1); + Class clazz = Class.forName(className); + Method method = findMethod(clazz.getDeclaredMethods(), methodName); + if (method != null) { + IgnoreSharding ignore = method.getAnnotation(IgnoreSharding.class); + if (null == ignore) { + //Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration"); + Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject"); + BoundSql boundSql = statementHandler.getBoundSql(); + String newSQL = buildTable(boundSql,parameterObject); + metaStatementHandler.setValue("delegate.boundSql.sql", newSQL); + } + } + return invocation.proceed(); + } + + private String buildTable(BoundSql boundSql,Object parameterObject) throws JSQLParserException { + String oldSql = boundSql.getSql(); + Statement stmt = CCJSqlParserUtil.parse(oldSql); + if (stmt instanceof Insert) { + return buildInsert((Insert) stmt,oldSql,parameterObject); + } + + if (stmt instanceof Delete) { + return buildDelete((Delete) stmt,oldSql,parameterObject); + } + + if (stmt instanceof Update) { + return buildUpdate((Update) stmt,oldSql,parameterObject); + } + + if (stmt instanceof Select) { + return buildSelect((Select) stmt,oldSql,parameterObject); + } + + if (stmt instanceof CreateTable) { + return oldSql; + } + throw new RuntimeException("非法sql语句,请检查"+oldSql); + } + + private String buildInsert(Insert stmt,String oldSql,Object parameterObject) { + return buildTable(stmt.getTable(), oldSql, parameterObject); + } + + private String buildDelete(Delete stmt,String oldSql,Object parameterObject) { + return buildTable(stmt.getTable(), oldSql, parameterObject); + } + + private String buildUpdate(Update stmt,String oldSql,Object parameterObject) { + return buildListTables(stmt.getTables(), oldSql, parameterObject); + } + + private String buildSelect(Select stmt,String oldSql,Object parameterObject) { + TablesNamesFinder tablesNamesFinder = new TablesNamesFinder(); + List tableList = tablesNamesFinder.getTableList(stmt); + return buildByListTableNames(tableList, oldSql, parameterObject); + } + + private String buildByTableName(String table,String oldSql,Object parameterObject) { + if (!ShardingTableExcuter.isShardingTable(table)) { + return oldSql; + } + String newtable = getShareTable(table,parameterObject); + return oldSql.replaceAll(table, newtable); + } + + private String buildTable(Table table,String oldSql,Object parameterObject) { + return buildByTableName(table.getName(), oldSql, parameterObject); + } + + private String buildListTables(List tables,String oldSql,Object parameterObject) { + for (Table t : tables) { + oldSql = buildTable(t,oldSql,parameterObject); + } + return oldSql; + } + + private String buildByListTableNames(List tables,String oldSql,Object parameterObject) { + for (String t : tables) { + oldSql = buildByTableName(t,oldSql,parameterObject); + } + return oldSql; + } + + private String getShareTable(String table,Object parameterObject) { + ShardingSphere ss = ShardingTableExcuter.getShardingSphere(table); + return ss.getTableName()+ShardingSphere.TABLE_SUFFIX+ShardingRuleExcuter.getTableIndex(ss.getRule(), getValue(ss.getColumn(),parameterObject), ss.getCount()); + } + + private Object getValue(String column,Object parameterObject) { + if (parameterObject instanceof Map) { + return ((Map) parameterObject).get(column); + } + return parameterObject; + } + +}