diff --git a/README.md b/README.md index a014419..c003b8b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ # tsharding -TSharding is the simple sharding component used in mogujie trade platform. -![alt text](https://raw.githubusercontent.com/juxuechen/ShareKit/cut-for-baby/share.jpg) +###TSharding is the simple sharding component used in mogujie trade platform. +###分库分表业界方案 +![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-select.png) + +###分库分表TSharding +#####TSharding组件目标 +*很少的资源投入即可开发完成 +*支持交易订单表的Sharding需求,分库又分表 +*支持数据源路由 +*支持事务 +*支持结果集合并 +*支持读写分离 + +#####TSharding Resources Abstract +![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-abstract.png) + +#####TSharding Resources Classes +![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-classes.png) + +#####TSharding组件接入过程: +*引入TSharding JAR包 +*配置所有分库的JDBC连接信息 +*Mybatis Mapper方法参数增加ShardingOrderPara/ShardingBuyerPara/ShardingSellerPara注解 +*批量查询增加结果集合并逻辑 diff --git a/tsharding-client/README.md b/tsharding-client/README.md new file mode 100644 index 0000000..cce5b61 --- /dev/null +++ b/tsharding-client/README.md @@ -0,0 +1,17 @@ +交易分库分表组件TSharding + +1.0.0-SNAPSHOT + +##2015-09-08 +##版本1.0.0-SNAPSHOT +new features: +com.mogujie.trade.tsharding.route.orm.MapperEnhancer#enhanceMapperClass //增加注解信息和注解内容,以支持mybatis mapper类的动态绑定 + +##2015-09-18 +##版本1.0.0 +已经支持交易订单分库分表完整需求,灰度完成100%发布上线。发布tsharding稳定版. + +##版本1.0.1.1 +优化orm元数据增强扩展时的内存消耗 +##版本1.0.2 +增加XDItemOrderEx表sharding支持。 diff --git a/tsharding-client/pom.xml b/tsharding-client/pom.xml new file mode 100644 index 0000000..409e178 --- /dev/null +++ b/tsharding-client/pom.xml @@ -0,0 +1,107 @@ + + 4.0.0 + + com.mogujie.trade + tsharding + 1.0.0 + + + tsharding-client + 1.0.2 + tsharding client + + + 3.2.8 + 1.2.2 + + + + + com.mogujie.trade + trade.switch + 1.0.2.1111switch + + + + org.springframework + spring-jdbc + + + + org.mybatis + mybatis + ${mybatis.version} + + + + org.mybatis + mybatis-spring + ${mybatis-spring.version} + + + + javax.persistence + persistence-api + 1.0 + + + + org.springframework + spring-test + + + + org.springframework + spring-core + + + + org.springframework + spring-context + provided + + + + org.javassist + javassist + 3.19.0-GA + + + + com.mogujie.tesla + tesla-support + ${tesla.support.version} + pom + + + com.mogujie.tesla + tesla-dal-db-orm + + + + + + com.mogujie.tesla + tesla-dal-db + 1.0.11-SNAPSHOT + + + + com.mogujie.trade + trade.switch + 1.0.2 + + + + com.mogujie.service + trade.service.base + 1.0.6 + + + + junit + junit + + + diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/ShardingExtensionMethod.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/ShardingExtensionMethod.java new file mode 100644 index 0000000..8fed6df --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/ShardingExtensionMethod.java @@ -0,0 +1,20 @@ +package com.mogujie.trade.tsharding.annotation; + +import com.mogujie.trade.tsharding.route.orm.MapperResourceEnhancer; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 需要sharding扩展的dao层方法 + * @auther qigong on 6/4/15 11:02 AM. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface ShardingExtensionMethod { + Class type() default MapperResourceEnhancer.class; + + String method() default "enhancedShardingSQL"; +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingBuyerPara.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingBuyerPara.java new file mode 100644 index 0000000..f85b2c7 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingBuyerPara.java @@ -0,0 +1,21 @@ +package com.mogujie.trade.tsharding.annotation.parameter; + +/** + * sharding参数注解 + * @auther qigong on 5/28/15 1:00 PM. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +/** + * sharding计算用的Para buyerUserId + */ +public @interface ShardingBuyerPara { + String value() default "buyerUserId"; +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingOrderPara.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingOrderPara.java new file mode 100644 index 0000000..c18b34d --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingOrderPara.java @@ -0,0 +1,21 @@ +package com.mogujie.trade.tsharding.annotation.parameter; + +/** + * sharding参数注解 + * @auther qigong on 5/28/15 1:00 PM. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +/** + * sharding计算用的Para orderId或parentOrderId或itemOrderId或shopOrderId或payOrderId + */ +public @interface ShardingOrderPara { + String value() default "orderId"; +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingSellerPara.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingSellerPara.java new file mode 100644 index 0000000..6c85bf2 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingSellerPara.java @@ -0,0 +1,21 @@ +package com.mogujie.trade.tsharding.annotation.parameter; + +/** + * sharding参数注解 + * @auther qigong on 5/28/15 1:00 PM. + */ + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +/** + * sharding计算用的Para sellerUserId + */ +public @interface ShardingSellerPara { + String value() default "sellerUserId"; +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/client/ShardingCaculator.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/client/ShardingCaculator.java new file mode 100644 index 0000000..49fcc4f --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/client/ShardingCaculator.java @@ -0,0 +1,119 @@ +package com.mogujie.trade.tsharding.client; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 分片计算器 + * + * @auther qigong on 5/28/15 1:06 PM. + */ +public class ShardingCaculator { + + /** + * 根据分片参数值计算分表名 + * + * @param shardingPara + * @return 分表名0xxx + */ + public static String caculateTableName(Long shardingPara) { + if (shardingPara >= 0) { + return "TestTable" + getNumberWithZeroSuffix((shardingPara % 10000) % 512); + } + return null; + } + + /** + * 根据分片参数值计算分表名 + * + * @param shardingPara + * @return 分表名0xxx + */ + public static Integer caculateTableIndex(Long shardingPara) { + if (shardingPara >= 0) { + return new Long(shardingPara % 10000 % 512).intValue(); + } + return null; + } + + + /** + * 根据分片参数值计算分库名(逻辑库) + * + * @param shardingPara + * @return 分库名000x + */ + public static String caculateSchemaName(String fieldName, Long shardingPara) { + if (shardingPara >= 0) { + + if ("sellerUserId".equals(fieldName)) { + return "sellertestschema" + getNumberWithZeroSuffix(((shardingPara % 10000) % 512) / 64); + } else { + return "testschema" + getNumberWithZeroSuffix(((shardingPara % 10000) % 512) / 64); + } + } + return null; + } + + /** + * 根据分片参数值计算数据源名 + * + * @param shardingPara + * @return DatasourceName 见数据源配置文件 + */ + public static String caculateDatasourceName(String fieldName, Long shardingPara) { + if (shardingPara >= 0) { + if ("sellerUserId".equals(fieldName)) { + return "seller_ds_" + ((shardingPara % 10000) % 512) / 256; + } else { + return "buyer_ds_" + ((shardingPara % 10000) % 512) / 256; + } + } + return null; + } + + public static String getNumberWithZeroSuffix(long number) { + if (number >= 100) { + return "0" + number; + } else if (number >= 10) { + return "00" + number; + } else if (number >= 0) { + return "000" + number; + } + return null; + } + + /** + * 按订单号批量查询:跨表查,先按分表做分组 + * + * @param listShopOrderIds + * @return tableNo -> orderIds + */ + public static Map> getTableNoAndOrderIdsMap(List listShopOrderIds) { + + HashMap> shopOrderIdsMap = new HashMap(); + if (listShopOrderIds == null || listShopOrderIds.size() == 0) { + return shopOrderIdsMap; + } + for (Long shopOrderId : listShopOrderIds) { + Integer tableNo = ShardingCaculator.caculateTableIndex(shopOrderId); + List orderIds = shopOrderIdsMap.get(tableNo); + if (orderIds == null) { + orderIds = new ArrayList<>(); + } + orderIds.add(shopOrderId); + shopOrderIdsMap.put(tableNo, orderIds); + } + return shopOrderIdsMap; + } + + public static void main(String args[]) { + System.out.println(caculateTableName(6000004386417L)); + System.out.println(caculateSchemaName("buyerUserId", 6000004386417L)); + + System.out.println(caculateTableName(35586213L)); + System.out.println(caculateSchemaName("sellerUserId", 35586213L)); + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandler.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandler.java new file mode 100644 index 0000000..94b8c75 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandler.java @@ -0,0 +1,30 @@ +ackage com.mogujie.trade.tsharding.route; + +import com.mogujie.tesla.db.DataSourceRoutingHandler; +import com.mogujie.trade.tsharding.annotation.parameter.*; +import com.mogujie.trade.tsharding.client.ShardingCaculator; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + + +/** + * @author qigong 06/05/2015 + */ +public class TShardingRoutingHandler implements DataSourceRoutingHandler { + + @Override + public String dynamicRoute(Method method, Object[] args) { + //route逻辑见TShardingRoutingInvokeFactory + return "testschema"; + } + + @Override + public Collection values() { + //暂未使用 + return null; + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandlerForPressureTest.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandlerForPressureTest.java new file mode 100644 index 0000000..77f2a38 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandlerForPressureTest.java @@ -0,0 +1,25 @@ +package com.mogujie.trade.tsharding.route; + +import com.mogujie.tesla.db.DataSourceRoutingHandler; + +import java.lang.reflect.Method; +import java.util.Collection; + + +/** + * @author qigong 06/05/2015 + */ +public class TShardingRoutingHandlerForPressureTest implements DataSourceRoutingHandler { + + @Override + public String dynamicRoute(Method method, Object[] args) { + //route逻辑见TShardingRoutingInvokeFactory + return "testschema"; + } + + @Override + public Collection values() { + //暂未使用 + return null; + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperAnnotationEnhancer.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperAnnotationEnhancer.java new file mode 100644 index 0000000..c8eaedc --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperAnnotationEnhancer.java @@ -0,0 +1,50 @@ +package com.mogujie.trade.tsharding.route.orm; + +import javassist.CtMethod; +import javassist.bytecode.ConstPool; +import javassist.bytecode.ParameterAnnotationsAttribute; +import javassist.bytecode.annotation.Annotation; +import javassist.bytecode.annotation.MemberValue; +import javassist.bytecode.annotation.StringMemberValue; + +import java.lang.reflect.Method; + +/** + * @author qigong on 15/9/8 下午9:13. + */ +public class MapperAnnotationEnhancer { + + public static ParameterAnnotationsAttribute duplicateParameterAnnotationsAttribute(ConstPool cp, Method method) { + ParameterAnnotationsAttribute oldAns = new ParameterAnnotationsAttribute(cp, ParameterAnnotationsAttribute.visibleTag); + javassist.bytecode.annotation.Annotation[][] anAr = new javassist.bytecode.annotation.Annotation[method.getParameterAnnotations().length][]; + for (int i = 0; i < anAr.length; ++i) { + anAr[i] = new javassist.bytecode.annotation.Annotation[method.getParameterAnnotations()[i].length]; + for (int j = 0; j < anAr[i].length; ++j) { + anAr[i][j] = createJavassistAnnotation(method.getParameterAnnotations()[i][j], cp); + } + } + oldAns.setAnnotations(anAr); + return oldAns; + } + + public static javassist.bytecode.annotation.Annotation createJavassistAnnotation(java.lang.annotation.Annotation annotation, ConstPool cp) { + try { + javassist.bytecode.annotation.Annotation newAnnotation = new Annotation(annotation.annotationType().getName(), cp); + for (Method m : annotation.annotationType().getDeclaredMethods()) { + Object val = m.invoke(annotation); + newAnnotation.addMemberValue(m.getName(), createMemberValue(m.getReturnType(), val, cp)); + } + return newAnnotation; + } catch (Exception e) { + throw new RuntimeException("createJavassistAnnotation error!", e); + } + } + + private static MemberValue createMemberValue(Class type, Object val, ConstPool cp) { + if (type == String.class) { + return new StringMemberValue((String) val, cp); + } else { + throw new RuntimeException("Only support string param value! Invalid param value type:" + type + " and value: " + val); + } + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperEnhancer.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperEnhancer.java new file mode 100644 index 0000000..c6ae3ab --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperEnhancer.java @@ -0,0 +1,242 @@ +package com.mogujie.trade.tsharding.route.orm; + +import com.mogujie.trade.tsharding.client.ShardingCaculator; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ResultMap; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.reflection.MetaObject; +import org.apache.ibatis.reflection.factory.DefaultObjectFactory; +import org.apache.ibatis.reflection.factory.ObjectFactory; +import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; +import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; +import org.apache.ibatis.session.Configuration; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 通用Mapper增强基类,扩展Mapper sql时需要继承该类 + * + * @author qigong on 5/1/15 + */ +public abstract class MapperEnhancer { + + private static ClassPool pool = ClassPool.getDefault(); + + private Map methodMap = new HashMap(); + private Class mapperClass; + + public MapperEnhancer(Class mapperClass) { + this.mapperClass = mapperClass; + } + + /** + * 代码增加方法标记 + * + * @param record + */ + public String enhancedShardingSQL(Object record) { + return "enhancedShardingSQL"; + } + + public MapperEnhancer() { + super(); + } + + /** + * 对mapper进行增强,生成新的mapper,并主动加载新mapper类到classloader + * + * @param mapperClassName + */ + public static void enhanceMapperClass(String mapperClassName) throws Exception { + + Class originClass = Class.forName(mapperClassName); + Method[] originMethods = originClass.getDeclaredMethods(); + + CtClass cc = pool.get(mapperClassName); + + for (CtMethod ctMethod : cc.getDeclaredMethods()) { + CtClass enhanceClass = pool.makeInterface(mapperClassName + "Sharding" + ctMethod.getName()); + for (long i = 0L; i < 512; i++) { + CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName() + ShardingCaculator.getNumberWithZeroSuffix(i), ctMethod.getParameterTypes(), enhanceClass); + + Method method = getOriginMethod(newMethod, originMethods); + if(method.getParameterAnnotations()[0].length > 0) { + ClassFile ccFile = enhanceClass.getClassFile(); + ConstPool constPool = ccFile.getConstPool(); + + //拷贝注解信息和注解内容,以支持mybatis mapper类的动态绑定 + newMethod.getMethodInfo().addAttribute(MapperAnnotationEnhancer.duplicateParameterAnnotationsAttribute(constPool, method)); + } + enhanceClass.addMethod(newMethod); + } + Class loadThisClass = enhanceClass.toClass(); + + //2015.09.22后不再输出类到本地 +// enhanceClass.writeFile("."); + } + } + + private static Method getOriginMethod(CtMethod ctMethod, Method[] originMethods) { + for (Method method : originMethods) { + int len = ctMethod.getName().length(); + if (ctMethod.getName().substring(0, len-4).equals(method.getName())) { + return method; + } + } + throw new RuntimeException("enhanceMapperClass find method error!"); + } + + /** + * 添加映射方法 + * + * @param methodName + * @param method + */ + public void addMethodMap(String methodName, Method method) { + methodMap.put(methodName, method); + } + + + private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); + private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); + + /** + * 反射对象,增加对低版本Mybatis的支持 + * + * @param object 反射对象 + * @return + */ + public static MetaObject forObject(Object object) { + return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); + } + + /** + * 是否支持该通用方法 + * + * @param msId + * @return + */ + public boolean supportMethod(String msId) { + Class mapperClass = getMapperClass(msId); + if (this.mapperClass.isAssignableFrom(mapperClass)) { + String methodName = getMethodName(msId); + return methodMap.get(methodName) != null; + } + return false; + } + + /** + * 重新设置SqlSource + * + * @param ms + * @param sqlSource + */ + protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) { + MetaObject msObject = forObject(ms); + msObject.setValue("sqlSource", sqlSource); + } + + /** + * 重新设置SqlSource + * + * @param ms + * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException + */ + public void setSqlSource(MappedStatement ms, Configuration configuration) throws Exception { + Method method = methodMap.get(getMethodName(ms)); + try { + if (method.getReturnType() == Void.TYPE) { + method.invoke(this, ms); + } else if (SqlSource.class.isAssignableFrom(method.getReturnType())) { + //代码增强 扩充为512个方法。 + for (long i = 0; i < 512; i++) { + + //新的带sharding的sql + SqlSource sqlSource = (SqlSource) method.invoke(this, ms, configuration, i); + + String newMsId = ms.getId() + ShardingCaculator.getNumberWithZeroSuffix(i); + newMsId = newMsId.replace("Mapper.", "MapperSharding" + getMethodName(ms) + "."); + + //添加到ms库中 + MappedStatement newMs = copyFromMappedStatement(ms, sqlSource, newMsId); + configuration.addMappedStatement(newMs); + setSqlSource(newMs, sqlSource); + } + } else { + throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void和SqlNode!"); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e); + } + } + + protected MappedStatement copyFromMappedStatement(MappedStatement ms, + SqlSource newSqlSource, String newMsId) { + MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, newSqlSource, ms.getSqlCommandType()); + builder.resource(ms.getResource()); + builder.fetchSize(ms.getFetchSize()); + builder.statementType(ms.getStatementType()); + builder.keyGenerator(ms.getKeyGenerator()); + // setStatementTimeout() + builder.timeout(ms.getTimeout()); + // setParameterMap() + builder.parameterMap(ms.getParameterMap()); + // setStatementResultMap() + List resultMaps = ms.getResultMaps(); + builder.resultMaps(resultMaps); + builder.resultSetType(ms.getResultSetType()); + // setStatementCache() + builder.cache(ms.getCache()); + builder.flushCacheRequired(ms.isFlushCacheRequired()); + builder.useCache(ms.isUseCache()); + return builder.build(); + } + + /** + * 根据msId获取接口类 + * + * @param msId + * @return + * @throws ClassNotFoundException + */ + public static Class getMapperClass(String msId) { + String mapperClassStr = msId.substring(0, msId.lastIndexOf(".")); + try { + return Class.forName(mapperClassStr); + } catch (ClassNotFoundException e) { + throw new RuntimeException("无法获取Mapper接口信息:" + msId); + } + } + + /** + * 获取执行的方法名 + * + * @param ms + * @return + */ + public static String getMethodName(MappedStatement ms) { + return getMethodName(ms.getId()); + } + + /** + * 获取执行的方法名 + * + * @param msId + * @return + */ + public static String getMethodName(String msId) { + return msId.substring(msId.lastIndexOf(".") + 1); + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperHelperForSharding.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperHelperForSharding.java new file mode 100644 index 0000000..3c94e81 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperHelperForSharding.java @@ -0,0 +1,284 @@ +package com.mogujie.trade.tsharding.route.orm; + +import com.mogujie.trade.tsharding.annotation.ShardingExtensionMethod; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSession; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * Mapper处理主要逻辑,最关键的一个类 + *

+ *

+ * 参考项目地址 : https://github.com/abel533/Mapper + *

+ * + * @author qigong on 5/1/15 + */ +public class MapperHelperForSharding { + + /** + * 注册的通用Mapper接口 + */ + private Map, MapperEnhancer> registerMapper = new HashMap, MapperEnhancer>(); + + /** + * 缓存msid和MapperTemplate + */ + private Map msIdCache = new HashMap(); + /** + * 缓存skip结果 + */ + private final Map msIdSkip = new HashMap(); + + /** + * 缓存已经处理过的Collection + */ + private Set> collectionSet = new HashSet>(); + + /** + * 是否使用的Spring + */ + private boolean spring = false; + + /** + * 是否为Spring4.x以上版本 + */ + private boolean spring4 = false; + + /** + * Spring版本号 + */ + private String springVersion; + + /** + * 缓存初始化时的SqlSession + */ + private List sqlSessions = new ArrayList(); + + /** + * 针对Spring注入需要处理的SqlSession + * + * @param sqlSessions + */ + public void setSqlSessions(SqlSession[] sqlSessions) { + if (sqlSessions != null && sqlSessions.length > 0) { + this.sqlSessions.addAll(Arrays.asList(sqlSessions)); + } + } + + /** + * Spring初始化方法,使用Spring时需要配置init-method="initMapper" + */ + public void initMapper() { + // 只有Spring会执行这个方法,所以Spring配置的时候,从这儿可以尝试获取Spring的版本 + // 先判断Spring版本,对下面的操作有影响 + // Spring4以上支持泛型注入,因此可以扫描通用Mapper + if (!initSpringVersion()) { + throw new RuntimeException("Error! Spring4 is necessary!"); + } + + for (SqlSession sqlSession : sqlSessions) { + processConfiguration(sqlSession.getConfiguration()); + } + } + + /** + * 检测Spring版本号,Spring4.x以上支持泛型注入 + */ + private boolean initSpringVersion() { + try { + // 反射获取SpringVersion + Class springVersionClass = Class.forName("org.springframework.core.SpringVersion"); + springVersion = (String) springVersionClass.getDeclaredMethod("getVersion", new Class[0]).invoke(null, + new Object[0]); + spring = true; + if (springVersion.indexOf(".") > 0) { + int MajorVersion = Integer.parseInt(springVersion.substring(0, springVersion.indexOf("."))); + if (MajorVersion > 3) { + spring4 = true; + } else { + spring4 = false; + } + } + } catch (Exception e) { + spring = false; + spring4 = false; + } + return spring && spring4; + } + + /** + * 通过通用Mapper接口获取对应的MapperTemplate + * + * @param mapperClass + */ + private MapperEnhancer fromMapperClass(Class mapperClass) { + Method[] methods = mapperClass.getDeclaredMethods(); + Class templateClass = null; + Class tempClass = null; + Set methodSet = new HashSet(); + for (Method method : methods) { + if (method.isAnnotationPresent(ShardingExtensionMethod.class)) { + ShardingExtensionMethod annotation = method.getAnnotation(ShardingExtensionMethod.class); + tempClass = annotation.type(); + methodSet.add(method.getName()); + } + if (templateClass == null) { + templateClass = tempClass; + } else if (templateClass != tempClass) { + throw new RuntimeException("一个通用Mapper中只允许存在一个MapperTemplate子类!"); + } + } + if (templateClass == null || !MapperEnhancer.class.isAssignableFrom(templateClass)) { + throw new RuntimeException("接口中不存在包含type为MapperTemplate的Provider注解,这不是一个合法的通用Mapper接口类!"); + } + MapperEnhancer mapperEnhancer = null; + try { + mapperEnhancer = (MapperEnhancer) templateClass.getConstructor(Class.class).newInstance(mapperClass); + } catch (Exception e) { + throw new RuntimeException("实例化MapperTemplate对象失败:" + e.getMessage(), e); + } + // 注册方法 + for (String methodName : methodSet) { + try { + mapperEnhancer.addMethodMap(methodName, templateClass.getMethod("enhancedShardingSQL", MappedStatement.class, Configuration.class, Long.class)); + } catch (NoSuchMethodException e) { + throw new RuntimeException(templateClass.getCanonicalName() + "中缺少enhancedShardingSQL方法!"); + } + } + return mapperEnhancer; + } + + /** + * 注册通用Mapper接口 + * + * @param mapperClass + * @throws Exception + */ + public void registerMapper(Class mapperClass) { + if (registerMapper.get(mapperClass) == null) { + MapperEnhancer enhancer = fromMapperClass(mapperClass); + registerMapper.put(mapperClass, enhancer); + } else { + throw new RuntimeException("已经注册过的通用Mapper[" + mapperClass.getCanonicalName() + "]不能多次注册!"); + } + } + + /** + * 注册通用Mapper接口 + * + * @param mapperClass + * @throws Exception + */ + public void registerMapper(String mapperClass) { + try { + registerMapper(Class.forName(mapperClass)); + } catch (ClassNotFoundException e) { + throw new RuntimeException("注册通用Mapper[" + mapperClass + "]失败,找不到该通用Mapper!"); + } + } + + /** + * 方便Spring注入 + * + * @param mappers + */ + public void setMappers(String[] mappers) { + if (mappers != null && mappers.length > 0) { + for (String mapper : mappers) { + registerMapper(mapper); + } + } + } + + /** + * 判断当前的接口方法是否需要进行拦截 + * + * @param msId + * @return + */ + public boolean isMapperMethod(String msId) { + if (msIdSkip.get(msId) != null) { + return msIdSkip.get(msId); + } + for (Map.Entry, MapperEnhancer> entry : registerMapper.entrySet()) { + if (entry.getValue().supportMethod(msId)) { + msIdSkip.put(msId, true); + return true; + } + } + msIdSkip.put(msId, false); + return false; + } + + /** + * 获取MapperTemplate + * + * @param msId + * @return + */ + private MapperEnhancer getMapperTemplate(String msId) { + MapperEnhancer mapperEnhancer = null; + if (msIdCache.get(msId) != null) { + mapperEnhancer = msIdCache.get(msId); + } else { + for (Map.Entry, MapperEnhancer> entry : registerMapper.entrySet()) { + if (entry.getValue().supportMethod(msId)) { + mapperEnhancer = entry.getValue(); + break; + } + } + msIdCache.put(msId, mapperEnhancer); + } + return mapperEnhancer; + } + + /** + * 重新设置SqlSource + * + * @param ms + */ + public void setSqlSource(MappedStatement ms, Configuration configuration) { + MapperEnhancer mapperEnhancer = getMapperTemplate(ms.getId()); + try { + if (mapperEnhancer != null) { + mapperEnhancer.setSqlSource(ms, configuration); + } + } catch (Exception e) { + throw new RuntimeException("调用方法异常:" + e.getMessage(), e); + } + } + + /** + * 处理configuration中全部的MappedStatement + * + * @param configuration + */ + public void processConfiguration(Configuration configuration) { + Collection collection = configuration.getMappedStatements(); + // 防止反复处理一个 + if (collectionSet.contains(collection)) { + return; + } else { + collectionSet.add(collection); + } + + Collection tmpCollection = new HashSet<>(); + tmpCollection.addAll(collection); + + Iterator iterator = tmpCollection.iterator(); + while (iterator.hasNext()) { + Object object = iterator.next(); + if (object instanceof MappedStatement) { + MappedStatement ms = (MappedStatement) object; + if (isMapperMethod(ms.getId())) { + setSqlSource(ms, configuration); + } + } + } + } +} \ No newline at end of file diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperResourceEnhancer.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperResourceEnhancer.java new file mode 100644 index 0000000..49ba0e0 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperResourceEnhancer.java @@ -0,0 +1,123 @@ +package com.mogujie.trade.tsharding.route.orm; + +import com.mogujie.trade.tsharding.client.ShardingCaculator; +import org.apache.ibatis.builder.StaticSqlSource; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.ParameterMapping; +import org.apache.ibatis.mapping.SqlSource; +import org.apache.ibatis.scripting.defaults.RawSqlSource; +import org.apache.ibatis.scripting.xmltags.*; +import org.apache.ibatis.session.Configuration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/** + * Mappper sql增强 + * + * @author qigong on 5/1/15 + */ +public class MapperResourceEnhancer extends MapperEnhancer{ + + Logger logger = LoggerFactory.getLogger(MapperResourceEnhancer.class); + + public MapperResourceEnhancer(Class mapperClass) { + super(mapperClass); + } + + public SqlSource enhancedShardingSQL(MappedStatement ms, Configuration configuration, Long shardingPara) { + + String tableName = ShardingCaculator.caculateTableName(shardingPara); + SqlSource result = null; + + try { + if (ms.getSqlSource() instanceof DynamicSqlSource) { + + DynamicSqlSource sqlSource = (DynamicSqlSource) ms.getSqlSource(); + + Class sqlSourceClass = sqlSource.getClass(); + + Field sqlNodeField = sqlSourceClass.getDeclaredField("rootSqlNode"); + sqlNodeField.setAccessible(true); + + MixedSqlNode rootSqlNode = (MixedSqlNode) sqlNodeField.get(sqlSource); + + Class mixedSqlNodeClass = rootSqlNode.getClass(); + Field contentsField = mixedSqlNodeClass.getDeclaredField("contents"); + contentsField.setAccessible(true); + List textSqlNodes = (List) contentsField.get(rootSqlNode); + List newSqlNodesList = new ArrayList(); + + //StaticTextSqlNode + Class textSqlNodeClass = textSqlNodes.get(0).getClass(); + Field textField = textSqlNodeClass.getDeclaredField("text"); + textField.setAccessible(true); + for (SqlNode node : textSqlNodes) { + if (node instanceof StaticTextSqlNode) { + StaticTextSqlNode textSqlNode = (StaticTextSqlNode) node; + String text = (String) textField.get(textSqlNode); + if(!text.contains("TestTable") && !text.contains("TestTable1")){ + newSqlNodesList.add(node); + }else { + newSqlNodesList.add(new StaticTextSqlNode(replaceWithShardingTableName(text, tableName, shardingPara))); + } + }else{ + newSqlNodesList.add(node); + } + } + + MixedSqlNode newrootSqlNode = new MixedSqlNode(newSqlNodesList); + result = new DynamicSqlSource(configuration, newrootSqlNode); + return result; + + } else if (ms.getSqlSource() instanceof RawSqlSource) { + + RawSqlSource sqlSource = (RawSqlSource) ms.getSqlSource(); + Class sqlSourceClass = sqlSource.getClass(); + Field sqlSourceField = sqlSourceClass.getDeclaredField("sqlSource"); + sqlSourceField.setAccessible(true); + StaticSqlSource staticSqlSource = (StaticSqlSource) sqlSourceField.get(sqlSource); + Field sqlField = staticSqlSource.getClass().getDeclaredField("sql"); + Field parameterMappingsField = staticSqlSource.getClass().getDeclaredField("parameterMappings"); + sqlField.setAccessible(true); + parameterMappingsField.setAccessible(true); + + //sql处理 + String sql = (String) sqlField.get(staticSqlSource); + + if(!sql.contains("TestTable") && !sql.contains("TestTable1")){ + result = sqlSource; + }else { + sql = replaceWithShardingTableName(sql, tableName, shardingPara); + result = new RawSqlSource(configuration, sql, null); + //为sqlSource对象设置mappering参数 + StaticSqlSource newStaticSqlSource = (StaticSqlSource) sqlSourceField.get(result); + List parameterMappings = (List)parameterMappingsField.get(staticSqlSource); + parameterMappingsField.set(newStaticSqlSource, parameterMappings); + } + return result; + } else { + throw new RuntimeException("wrong sqlSource type!" + ms.getResource()); + } + + } catch (Exception e) { + logger.error("reflect error!, ms resources:" + ms.getResource(), e); + } + return result; + } + + + private String replaceWithShardingTableName(String text, String tableName, Long shardingPara){ + if(text.contains(" TestTablePressureTest")){ + return text.replace(" TestTablePressureTest", " TestTablePressureTest" + ShardingCaculator.getNumberWithZeroSuffix(shardingPara)); + }else if(text.contains(" TestTable1PressureTest")){ + return text.replace(" TestTable1PressureTest", " TestTable1PressureTest" + ShardingCaculator.getNumberWithZeroSuffix(shardingPara)); + }else if(text.contains(" TestTable1")){ + return text.replace(" TestTable1", " TestTable1" + ShardingCaculator.getNumberWithZeroSuffix(shardingPara)); + } + return text.replace(" TestTable", " " + tableName); + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperScannerWithSharding.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperScannerWithSharding.java new file mode 100644 index 0000000..778f7bc --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperScannerWithSharding.java @@ -0,0 +1,162 @@ +package com.mogujie.trade.tsharding.route.orm; + +import com.mogujie.tesla.db.DataSourceLookup; +import com.mogujie.tesla.db.ReadWriteSplittingDataSource; +import com.mogujie.trade.tsharding.route.orm.base.*; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.session.Configuration; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionFactoryBean; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.lang.reflect.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Tsharding MybatisMapper的扫描类,负责将Mapper接口与对应的xml配置文件整合,绑定设定的数据源,注入到Spring Context中。 + * + * @author qigong + */ +public class MapperScannerWithSharding implements BeanFactoryPostProcessor, InitializingBean { + + public static DataSourceLookup dataSourceLookup; + + private String packageName; + + private Resource[] mapperLocations; + + private String[] mapperPacakages; + + private SqlSessionFactoryLookup sqlSessionFactoryLookup; + + public static DataSourceLookup getDataSourceLookup() { + return dataSourceLookup; + } + + @Override + public void afterPropertiesSet() throws Exception { + this.initMapperPackage(); + } + + private void initMapperPackage() throws IOException { + this.mapperPacakages = packageName.split(","); + } + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + this.dataSourceLookup = beanFactory.getBean(DataSourceLookup.class); + + try { + this.initSqlSessionFactories(beanFactory); + } catch (Exception e) { + throw new RuntimeException(e); + } + ClassPathScanHandler scanner = new ClassPathScanHandler(); + Set> mapperClasses = new HashSet<>(); + for (String mapperPackage : this.mapperPacakages) { + Set> classes = scanner.getPackageAllClasses(mapperPackage.trim(), false); + mapperClasses.addAll(classes); + } + for (Class clazz : mapperClasses) { + if (isMapper(clazz)) { + Object mapper = this.newMapper(clazz); + beanFactory.registerSingleton(Character.toLowerCase(clazz.getSimpleName().charAt(0)) + + clazz.getSimpleName().substring(1), mapper); + } + } + + } + + private void initSqlSessionFactories(ConfigurableListableBeanFactory beanFactory) throws Exception { + Map sqlSessionFactories = new HashMap<>(this.dataSourceLookup.getMapping().size()); + + ReadWriteSplittingDataSource defaultDataSource = null; + SqlSessionFactory defaultSqlSessionFactory = null; + for (ReadWriteSplittingDataSource dataSource : this.dataSourceLookup.getMapping().values()) { + + SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); + sessionFactoryBean.setMapperLocations(mapperLocations); + sessionFactoryBean.setDataSource(dataSource); + sessionFactoryBean.setTypeAliasesPackage(this.packageName + ".domain.entity"); + + // init 初始化所有sql对应的元数据、资源(sqlNode, sqlSource, mappedStatement)等 + sessionFactoryBean.afterPropertiesSet(); + + if (defaultDataSource == null) { + //第一个 + defaultDataSource = dataSource; + defaultSqlSessionFactory = sessionFactoryBean.getObject(); + } else { + SqlSessionFactory newSqlSessionFactory = sessionFactoryBean.getObject(); + Field conf = newSqlSessionFactory.getClass().getDeclaredField("configuration"); + conf.setAccessible(true); + Configuration newConfiguration = (Configuration) conf.get(newSqlSessionFactory); + Field mappedStatementField = newConfiguration.getClass().getDeclaredField("mappedStatements"); + + //去掉final修饰符 + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt( mappedStatementField, mappedStatementField.getModifiers() & ~Modifier.FINAL); + mappedStatementField.setAccessible(true); + + //后续的元数据复用 + Configuration defaultConfiguration = defaultSqlSessionFactory.getConfiguration(); + Map reUsedMappedStatement = (Map) mappedStatementField.get(defaultConfiguration); + mappedStatementField.set(newConfiguration, reUsedMappedStatement); + } + beanFactory.registerSingleton(dataSource.getName() + "SqlSessionFactory", sessionFactoryBean); + sqlSessionFactories.put(dataSource.getName(), sessionFactoryBean.getObject()); + defaultSqlSessionFactory = sessionFactoryBean.getObject(); + } + + this.sqlSessionFactoryLookup = new SqlSessionFactoryLookup(sqlSessionFactories); + } + + private boolean isMapper(Class clazz) { + if (clazz.isInterface()) { + return true; + } + return false; + } + + private Object newMapper(final Class clazz) { + + final Invoker invoker = new TShardingRoutingInvokeFactory(sqlSessionFactoryLookup).newInvoker(clazz); + + return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { + return invoker.invoke(new DefaultInvocation(method, args)); + } + }); + } + + /** + * 注入packageName配置 + * + * @param packageName + */ + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + /** + * 注入mapperLocations配置 + * + * @param mapperLocations + */ + public void setMapperLocations(Resource[] mapperLocations) { + this.mapperLocations = mapperLocations; + } +} + + diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperShardingInitializer.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperShardingInitializer.java new file mode 100644 index 0000000..11e8530 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperShardingInitializer.java @@ -0,0 +1,67 @@ +package com.mogujie.trade.tsharding.route.orm; + +import com.mogujie.service.xiaodian.trade.base.utils.SentryUtil; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.SqlSessionTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 增强Mapper处理总入口:Mapper被mybatis初始化后,在这里做进一步的处理和增强 + * + * @author qigong on 5/1/15 + */ +public class MapperShardingInitializer implements ApplicationContextAware { + + + Logger logger = LoggerFactory.getLogger(getClass()); + + private String needEnhancedClasses; + private String[] needEnhancedClassesArray; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + + //初始化jvm监控信息 + SentryUtil.getTradeOrderInstance().openAutoCollector(); + + Map sqlSessionFactories = applicationContext.getBeansOfType(SqlSessionFactory.class); + if (sqlSessionFactories.isEmpty()) { + return; + } + MapperHelperForSharding mapperHelperForSharding = new MapperHelperForSharding(); + List sqlSessions = new ArrayList<>(sqlSessionFactories.size()); + for (SqlSessionFactory sqlSessionFactory : sqlSessionFactories.values()) { + SqlSession sqlSession = new SqlSessionTemplate(sqlSessionFactory); + sqlSessions.add(sqlSession); + } + //Mapper代码增强 每个方法扩展出一个ShardingMapper类,增强为512个方法。 + this.needEnhancedClassesArray = needEnhancedClasses.split(","); + this.enhanceMapperClass(); + mapperHelperForSharding.setMappers(needEnhancedClassesArray); + mapperHelperForSharding.setSqlSessions(sqlSessions.toArray(new SqlSession[0])); + mapperHelperForSharding.initMapper(); + } + + private void enhanceMapperClass() { + for (String mapperClass : needEnhancedClassesArray) { + try { + MapperEnhancer.enhanceMapperClass(mapperClass); + } catch (Exception e) { + logger.error("Enhance {} class error", mapperClass, e); + } + } + } + + public void setNeedEnhancedClasses(String needEnhancedClasses) { + this.needEnhancedClasses = needEnhancedClasses; + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ClassPathScanHandler.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ClassPathScanHandler.java new file mode 100644 index 0000000..0115641 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ClassPathScanHandler.java @@ -0,0 +1,273 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.regex.Pattern; + +/** + * 扫描指定包(包括jar)下的class文件
+ * http://www.micmiu.com + * + * @author michael + */ +public class ClassPathScanHandler { + + /** + * logger + */ + private static final Logger logger = LoggerFactory.getLogger(ClassPathScanHandler.class); + + /** + * 是否排除内部类 true->是 false->否 + */ + private boolean excludeInner = true; + /** + * 过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的 + */ + private boolean checkInOrEx = true; + + /** + * 过滤规则列表 如果是null或者空,即全部符合不过滤 + */ + private List classFilters = null; + + /** + * 无参构造器,默认是排除内部类、并搜索符合规则 + */ + public ClassPathScanHandler() { + } + + /** + * excludeInner:是否排除内部类 true->是 false->否
+ * checkInOrEx:过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的
+ * classFilters:自定义过滤规则,如果是null或者空,即全部符合不过滤 + * + * @param excludeInner + * @param checkInOrEx + * @param classFilters + */ + public ClassPathScanHandler(Boolean excludeInner, Boolean checkInOrEx, List classFilters) { + this.excludeInner = excludeInner; + this.checkInOrEx = checkInOrEx; + this.classFilters = classFilters; + + } + + /** + * 扫描包 + * + * @param basePackage + * 基础包 + * @param recursive + * 是否递归搜索子包 + * @return Set + */ + public Set> getPackageAllClasses(String basePackage, boolean recursive) { + Set> classes = new LinkedHashSet>(); + String packageName = basePackage; + if (packageName.endsWith(".")) { + packageName = packageName.substring(0, packageName.lastIndexOf('.')); + } + String package2Path = packageName.replace('.', '/'); + + Enumeration dirs; + try { + dirs = Thread.currentThread().getContextClassLoader().getResources(package2Path); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + logger.info("扫描file类型的class文件...."); + String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); + doScanPackageClassesByFile(classes, packageName, filePath, recursive); + } else if ("jar".equals(protocol)) { + logger.info("扫描jar文件中的类...."); + doScanPackageClassesByJar(packageName, url, recursive, classes); + } + } + } catch (IOException e) { + logger.error("IOException error:", e); + } + + return classes; + } + + /** + * 以jar的方式扫描包下的所有Class文件
+ * + * @param basePackage + * eg:michael.utils. + * @param url + * @param recursive + * @param classes + */ + private void doScanPackageClassesByJar(String basePackage, URL url, final boolean recursive, Set> classes) { + String packageName = basePackage; + String package2Path = packageName.replace('.', '/'); + JarFile jar; + try { + jar = ((JarURLConnection) url.openConnection()).getJarFile(); + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (!name.startsWith(package2Path) || entry.isDirectory()) { + continue; + } + + // 判断是否递归搜索子包 + if (!recursive && name.lastIndexOf('/') != package2Path.length()) { + continue; + } + // 判断是否过滤 inner class + if (this.excludeInner && name.indexOf('$') != -1) { + logger.info("exclude inner class with name:" + name); + continue; + } + String classSimpleName = name.substring(name.lastIndexOf('/') + 1); + // 判定是否符合过滤条件 + if (this.filterClassName(classSimpleName)) { + String className = name.replace('/', '.'); + className = className.substring(0, className.length() - 6); + try { + classes.add(Thread.currentThread().getContextClassLoader().loadClass(className)); + } catch (ClassNotFoundException e) { + logger.error("Class.forName error:", e); + } + } + } + } catch (IOException e) { + logger.error("IOException error:", e); + } + } + + /** + * 以文件的方式扫描包下的所有Class文件 + * + * @param packageName + * @param packagePath + * @param recursive + * @param classes + */ + private void doScanPackageClassesByFile(Set> classes, String packageName, String packagePath, + boolean recursive) { + File dir = new File(packagePath); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + final boolean fileRecursive = recursive; + File[] dirfiles = dir.listFiles(new FileFilter() { + // 自定义文件过滤规则 + @Override + public boolean accept(File file) { + if (file.isDirectory()) { + return fileRecursive; + } + String filename = file.getName(); + if (excludeInner && filename.indexOf('$') != -1) { + logger.info("exclude inner class with name:" + filename); + return false; + } + return filterClassName(filename); + } + }); + for (File file : dirfiles) { + if (file.isDirectory()) { + doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath(), + recursive); + } else { + String className = file.getName().substring(0, file.getName().length() - 6); + try { + classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className)); + + } catch (ClassNotFoundException e) { + logger.error("IOException error:", e); + } + } + } + } + + /** + * 根据过滤规则判断类名 + * + * @param className + * @return + */ + private boolean filterClassName(String className) { + if (!className.endsWith(".class")) { + return false; + } + if (null == this.classFilters || this.classFilters.isEmpty()) { + return true; + } + String tmpName = className.substring(0, className.length() - 6); + boolean flag = false; + for (String str : classFilters) { + String tmpreg = "^" + str.replace("*", ".*") + "$"; + Pattern p = Pattern.compile(tmpreg); + if (p.matcher(tmpName).find()) { + flag = true; + break; + } + } + return checkInOrEx && flag || !checkInOrEx && !flag; + } + + /** + * @return the excludeInner + */ + public boolean isExcludeInner() { + return excludeInner; + } + + /** + * @return the checkInOrEx + */ + public boolean isCheckInOrEx() { + return checkInOrEx; + } + + /** + * @return the classFilters + */ + public List getClassFilters() { + return classFilters; + } + + /** + * @param pExcludeInner + * the excludeInner to set + */ + public void setExcludeInner(boolean pExcludeInner) { + excludeInner = pExcludeInner; + } + + /** + * @param pCheckInOrEx + * the checkInOrEx to set + */ + public void setCheckInOrEx(boolean pCheckInOrEx) { + checkInOrEx = pCheckInOrEx; + } + + /** + * @param pClassFilters + * the classFilters to set + */ + public void setClassFilters(List pClassFilters) { + classFilters = pClassFilters; + } +} \ No newline at end of file diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/DefaultInvocation.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/DefaultInvocation.java new file mode 100644 index 0000000..e58985f --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/DefaultInvocation.java @@ -0,0 +1,41 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import java.lang.reflect.Method; +import java.util.Arrays; + +public class DefaultInvocation implements Invocation { + + private final Method method; + + private final Object[] args; + + public DefaultInvocation(Method method, Object[] args) { + this.method = method; + this.args = args; + } + + @Override + public Method getMethod() { + return this.method; + } + + @Override + public Object[] getArgs() { + return this.args; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append("DefaultInvocation ["); + if (method != null) { + builder.append("method=").append(method).append(", "); + } + if (args != null) { + builder.append("args=").append(Arrays.toString(args)); + } + builder.append("]"); + return builder.toString(); + } + +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invocation.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invocation.java new file mode 100644 index 0000000..c77026c --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invocation.java @@ -0,0 +1,14 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import java.lang.reflect.Method; + +/** + * @author qigong + * + */ +public interface Invocation { + + Method getMethod(); + + Object[] getArgs(); +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invoker.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invoker.java new file mode 100644 index 0000000..32285f4 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invoker.java @@ -0,0 +1,10 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +/** + * @author qigong + * + */ +public interface Invoker { + + Object invoke(Invocation invocation) throws Throwable; +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/InvokerFactory.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/InvokerFactory.java new file mode 100644 index 0000000..21327b2 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/InvokerFactory.java @@ -0,0 +1,10 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +/** + * @author qigong + * + * @param + */ +public interface InvokerFactory { + Invoker newInvoker(T config); +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperBasicConfig.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperBasicConfig.java new file mode 100644 index 0000000..63c29ea --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperBasicConfig.java @@ -0,0 +1,28 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +/** + * Mapper管控基础 类-数据源 + * + * @author qigong + * + */ +public class MapperBasicConfig { + + private final Class mapperInterface; + + private final String dataSourceName; + + public MapperBasicConfig(Class mapperInterface, String dataSourceName) { + this.mapperInterface = mapperInterface; + this.dataSourceName = dataSourceName; + } + + public Class getMapperInterface() { + return mapperInterface; + } + + public String getDataSourceName() { + return dataSourceName; + } + +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperInitializeException.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperInitializeException.java new file mode 100644 index 0000000..66e1492 --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperInitializeException.java @@ -0,0 +1,24 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +public class MapperInitializeException extends RuntimeException { + + private static final long serialVersionUID = -5010183715049161425L; + + public MapperInitializeException(String message) { + super(message); + } + + public MapperInitializeException(Throwable cause) { + super(cause); + } + + public MapperInitializeException(String message, Throwable cause) { + super(message, cause); + } + + public MapperInitializeException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReadWriteSplittingContextInitializer.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReadWriteSplittingContextInitializer.java new file mode 100644 index 0000000..5e8efca --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReadWriteSplittingContextInitializer.java @@ -0,0 +1,79 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import com.mogujie.tesla.db.DataSourceType; +import com.mogujie.tesla.db.ReadWriteSplitting; +import com.mogujie.tesla.db.ReadWriteSplittingContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 读写分离的上下文初始化和清空 + * + * @author qigong + */ +public class ReadWriteSplittingContextInitializer { + + private final static Logger logger = LoggerFactory.getLogger(ReadWriteSplittingContextInitializer.class); + + private final static String[] DEFAULT_WRITE_METHOD_NAMES = {"update", "save", "insert", "delete", "add", + "batchInsert", "batchUpdate", "batchSave", "batchAdd"}; + + private final static Map cache = new ConcurrentHashMap<>(); + + public static void initReadWriteSplittingContext(Method method) { + // 忽略object的方法,只关注Mapper的方法 + if (method.getDeclaringClass() != Object.class) { + } + DataSourceType dataSourceType = getDataSourceType(method); + logger.debug("ReadWriteSplitting {} using dataSource of {}", method, dataSourceType); + + ReadWriteSplittingContext.set(dataSourceType); + } + + public static void clearReadWriteSplittingContext() { + ReadWriteSplittingContext.clear(); + } + + /** + * 获取方法对应的数据眼类型 + * + * @param method + * @return + */ + private static DataSourceType getDataSourceType(Method method) { + DataSourceType dataSourceType = cache.get(method); + if (dataSourceType == null) { + synchronized (method) { + dataSourceType = cache.get(method); + if (dataSourceType == null) { + dataSourceType = determineDataSourceType(method); + cache.put(method, dataSourceType); + } + } + } + return dataSourceType; + } + + private static DataSourceType determineDataSourceType(Method method) { + DataSourceType dataSourceType = DataSourceType.slave; + + ReadWriteSplitting readWriteSplitting = method.getAnnotation(ReadWriteSplitting.class); + if (readWriteSplitting != null) { + dataSourceType = readWriteSplitting.value(); + dataSourceType = dataSourceType == null ? DataSourceType.master : dataSourceType; + } else { + for (String writeMethodName : DEFAULT_WRITE_METHOD_NAMES) { + if (method.getName().startsWith(writeMethodName)) { + dataSourceType = DataSourceType.master; + break; + } + } + } + return dataSourceType; + } + +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReflectUtil.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReflectUtil.java new file mode 100644 index 0000000..3737afa --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReflectUtil.java @@ -0,0 +1,29 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import java.lang.reflect.Field; + +/** + * @author qigong on 15/9/17 下午8:05. + */ +public class ReflectUtil { + + /** + * 循环向上转型, 获取对象的 DeclaredField + * @param object : 子类对象 + * @param fieldName : 父类中的属性名 + * @return 父类中的属性对象 + */ + + public static Field getDeclaredField(Object object, String fieldName){ + Field field = null ; + Class clazz = object.getClass() ; + for(; clazz != Object.class ; clazz = clazz.getSuperclass()) { + try { + field = clazz.getDeclaredField(fieldName) ; + return field ; + } catch (Exception e) { + } + } + return null; + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/SqlSessionFactoryLookup.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/SqlSessionFactoryLookup.java new file mode 100644 index 0000000..91155ff --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/SqlSessionFactoryLookup.java @@ -0,0 +1,27 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import org.apache.ibatis.session.SqlSessionFactory; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class SqlSessionFactoryLookup { + + private Map mapping; + + public SqlSessionFactoryLookup(Map mapping) { + + Map tmpMap = new HashMap<>(mapping.size()); + tmpMap.putAll(mapping); + this.mapping = Collections.unmodifiableMap(tmpMap); + } + + public Map getMapping() { + return this.mapping; + } + + public SqlSessionFactory get(String name) { + return this.mapping.get(name); + } +} diff --git a/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/TShardingRoutingInvokeFactory.java b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/TShardingRoutingInvokeFactory.java new file mode 100644 index 0000000..b42ae7a --- /dev/null +++ b/tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/TShardingRoutingInvokeFactory.java @@ -0,0 +1,213 @@ +package com.mogujie.trade.tsharding.route.orm.base; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.fastjson.JSON; +import com.mogujie.tesla.db.DataSourceLookup; +import com.mogujie.tesla.db.DataSourceRouting; +import com.mogujie.tesla.db.DataSourceRoutingException; +import com.mogujie.tesla.db.ReadWriteSplittingDataSource; +import com.mogujie.trade.moguswitch.util.MoguStableSwitch; +import com.mogujie.trade.tsharding.annotation.parameter.ShardingBuyerPara; +import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; +import com.mogujie.trade.tsharding.annotation.parameter.ShardingSellerPara; +import com.mogujie.trade.tsharding.client.ShardingCaculator; +import com.mogujie.trade.tsharding.route.TShardingRoutingHandler; +import com.mogujie.trade.tsharding.route.orm.MapperScannerWithSharding; +import org.apache.ibatis.session.SqlSessionFactory; +import org.mybatis.spring.mapper.MapperFactoryBean; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.sql.SQLException; +import java.util.List; + +public class TShardingRoutingInvokeFactory implements InvokerFactory> { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private SqlSessionFactoryLookup sqlSessionFactoryLookup; + + public TShardingRoutingInvokeFactory(SqlSessionFactoryLookup sqlSessionFactoryLookup) { + this.sqlSessionFactoryLookup = sqlSessionFactoryLookup; + } + + @Override + public Invoker newInvoker(Class mapperInterface) { + + final DataSourceRouting dataSourceRouting = mapperInterface.getAnnotation(DataSourceRouting.class); + final Class clazz = mapperInterface; + + if (dataSourceRouting != null && !StringUtils.isEmpty(dataSourceRouting.value())) { //使用配置的数据源 + logger.debug("TShardingRoutingInvokeFactory routing: emptyHandler and dataSourceRouting.value:" + dataSourceRouting.value()); + return new Invoker() { + @Override + public Object invoke(Invocation invocation) throws Throwable { + + MapperBasicConfig config = new MapperBasicConfig(clazz, dataSourceRouting.value()); + final Object mapper = newMyBatisMapper(config); + try { + ReadWriteSplittingContextInitializer.initReadWriteSplittingContext(invocation.getMethod()); + return invocation.getMethod().invoke(mapper, invocation.getArgs()); + } finally { + ReadWriteSplittingContextInitializer.clearReadWriteSplittingContext(); + } + } + }; + } else if (dataSourceRouting != null && dataSourceRouting.handler() == TShardingRoutingHandler.class) { //使用Sharding数据源 + logger.debug("TShardingRoutingInvokeFactory routing: dynamic handler: " + dataSourceRouting.handler().getName()); + return new Invoker() { + @Override + public Object invoke(Invocation invocation) throws Throwable { + + Method method = invocation.getMethod(); + ShardingMetadata shardingMetadata = getShardingKey(method, invocation.getArgs()); + + if (shardingMetadata == null) { + throw new DataSourceRoutingException("dataSourceRouting error! Method Name:" + method.getName() + " shardingMetadata is null!"); + } + + //走分库分表环境 + logger.debug("TShardingRoutingInvokeFactory routing to sharding db. Method Name:" + method.getName() + ". ShardingKey:" + shardingMetadata.getShardingKey()); + + Class newClass = clazz; + if (!"".equals(shardingMetadata.getSchemaName())) { + newClass = Class.forName(clazz.getCanonicalName() + "Sharding" + method.getName()); + } + Method newMethod = newClass.getMethod(method.getName() + shardingMetadata.getTableSuffix(), method.getParameterTypes()); + MapperBasicConfig config = new MapperBasicConfig(newClass, shardingMetadata.getSchemaName()); + final Object mapper = newMyBatisMapper(config); + try { + ReadWriteSplittingContextInitializer.initReadWriteSplittingContext(invocation.getMethod()); + return newMethod.invoke(mapper, invocation.getArgs()); + } finally { + ReadWriteSplittingContextInitializer.clearReadWriteSplittingContext(); + } + } + }; + } else { + throw new DataSourceRoutingException("dataSourceRouting error! cannot find datasource"); + } + } + + + private ShardingMetadata getShardingKey(Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { + Annotation[][] an = method.getParameterAnnotations(); + if (an.length > 0) { + for (int i = 0; i < an.length; i++) { + for (int j = 0; j < an[i].length; j++) { + if (an[i][j] instanceof ShardingOrderPara || an[i][j] instanceof ShardingBuyerPara || an[i][j] instanceof ShardingSellerPara) { + Long shardingKey = 0L; + if (args[i] instanceof Long) { + shardingKey = (Long) args[i]; + } else if (args[i] instanceof List) { + shardingKey = (Long) ((List) args[i]).get(0); + } else if (an[i][j] instanceof ShardingOrderPara && args[i] instanceof Object) { + Field field = ReflectUtil.getDeclaredField(args[i], "orderId"); + field.setAccessible(true); + shardingKey = (Long) field.get(args[i]); + if (shardingKey == null) { + field = ReflectUtil.getDeclaredField(args[i], "parentOrderId"); + field.setAccessible(true); + shardingKey = (Long) field.get(args[i]); + } + } else if (an[i][j] instanceof ShardingBuyerPara && args[i] instanceof Object) { + Field field = ReflectUtil.getDeclaredField(args[i], "buyerUserId"); + field.setAccessible(true); + shardingKey = (Long) field.get(args[i]); + } else if (an[i][j] instanceof ShardingSellerPara && args[i] instanceof Object) { + Field field = ReflectUtil.getDeclaredField(args[i], "sellerUserId"); + field.setAccessible(true); + shardingKey = (Long) field.get(args[i]); + } + + String schemaName = null; + if (an[i][j] instanceof ShardingOrderPara) { + schemaName = ShardingCaculator.caculateSchemaName("orderId", shardingKey); + } else if (an[i][j] instanceof ShardingBuyerPara) { + schemaName = ShardingCaculator.caculateSchemaName("buyerUserId", shardingKey); + } else if (an[i][j] instanceof ShardingSellerPara) { + schemaName = ShardingCaculator.caculateSchemaName("sellerUserId", shardingKey); + } + ShardingMetadata shardingMetadata = new ShardingMetadata(); + shardingMetadata.setShardingKey(shardingKey); + shardingMetadata.setTableSuffix(ShardingCaculator.getNumberWithZeroSuffix((shardingKey % 10000) % 512)); + shardingMetadata.setSchemaName(schemaName); + return shardingMetadata; + } + } + } + } + return null; + } + + @SuppressWarnings("unchecked") + private Object newMyBatisMapper(MapperBasicConfig config) { + MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(); + mapperFactoryBean.setMapperInterface(config.getMapperInterface()); + mapperFactoryBean.setSqlSessionFactory(this.getSqlSessionFactory(config.getDataSourceName(), + config.getMapperInterface())); + mapperFactoryBean.afterPropertiesSet(); + Object mapper = null; + try { + mapper = mapperFactoryBean.getObject(); + } catch (Exception e) { + throw new MapperInitializeException(e); + } + return mapper; + } + + private SqlSessionFactory getSqlSessionFactory(String dataSourceName, Class mapperInterface) { + if (StringUtils.isEmpty(dataSourceName)) { + if (sqlSessionFactoryLookup.getMapping().size() == 1) { + return sqlSessionFactoryLookup.getMapping().values().iterator().next(); + } else { + throw new DataSourceRoutingException("can't decided the datasource of " + + mapperInterface.getCanonicalName() + ",please add config by using @DataSourceRouting"); + } + } else { + SqlSessionFactory sqlSessionFactory = sqlSessionFactoryLookup.get(dataSourceName); + if (sqlSessionFactory == null) { + throw new DataSourceRoutingException("can't find datasource named " + dataSourceName + + " while init!"); + } + return sqlSessionFactory; + } + } + + private class ShardingMetadata { + + private Long shardingKey; + + private String schemaName; + + private String tableSuffix; + + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public String getTableSuffix() { + return tableSuffix; + } + + public void setTableSuffix(String tableSuffix) { + this.tableSuffix = tableSuffix; + } + + public Long getShardingKey() { + return shardingKey; + } + + public void setShardingKey(Long shardingKey) { + this.shardingKey = shardingKey; + } + } +} diff --git a/tsharding-client/src/main/resources/tesla/support/service-loader.xml b/tsharding-client/src/main/resources/tesla/support/service-loader.xml new file mode 100644 index 0000000..976ba0b --- /dev/null +++ b/tsharding-client/src/main/resources/tesla/support/service-loader.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDao.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDao.java new file mode 100644 index 0000000..5c14abe --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDao.java @@ -0,0 +1,13 @@ +package com.mogujie.service.tsharding.annotation; + +import com.mogujie.trade.tsharding.annotation.parameter.ShardingBuyerPara; +import org.apache.ibatis.annotations.Param; + +/** + * @auther qigong on 5/29/15 11:13 AM. + */ +public interface SimpleDao { + + public void abc(String a,String b,@ShardingBuyerPara @Param("test") Long id); + +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoImpl.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoImpl.java new file mode 100644 index 0000000..10e2196 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoImpl.java @@ -0,0 +1,16 @@ +package com.mogujie.service.tsharding.annotation; + +import com.mogujie.trade.tsharding.annotation.parameter.ShardingBuyerPara; +import org.apache.ibatis.annotations.Param; + +/** + * @auther qigong on 5/29/15 2:24 PM. + */ +public class SimpleDaoImpl implements SimpleDao { + + + public void abc(String a,String b,@ShardingBuyerPara @Param("xxx") Long id){ + + System.out.println("real method"); + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoWithAnotationTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoWithAnotationTest.java new file mode 100644 index 0000000..9eb9985 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/annotation/SimpleDaoWithAnotationTest.java @@ -0,0 +1,71 @@ +package com.mogujie.service.tsharding.annotation; + +import com.alibaba.fastjson.JSON; +import com.mogujie.trade.tsharding.annotation.parameter.ShardingBuyerPara; +import org.apache.ibatis.annotations.Param; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @auther qigong on 5/29/15 12:46 PM. + */ +public class SimpleDaoWithAnotationTest { + + private static ShardingBuyerPara a; + + public static void main(String[] args) { + Class z = SimpleDao.class; + try { + + InvocationHandler handler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("proxy method"); + + + System.out.println(JSON.toJSONString(method.getParameterTypes())); + + Annotation[][] an = method.getParameterAnnotations(); + + if (an.length > 0) { + for (int i = 0; i < an.length; i++) { + System.out.print("i::" + i + ";"); + System.out.println(JSON.toJSONString(an[i].length)); + for (int j = 0; j < an[i].length; j++) { + System.out.println("j::" + j); + if(j==0) { + a = (ShardingBuyerPara) an[i][j]; + System.out.println("sharding参数"+args[i]); + }else { + Param b = (Param) an[i][j]; + System.out.println(b.value()); + } + } + } + } + + + return method.invoke(SimpleDaoImpl.class.newInstance(), new Object[]{"abc", "cde", 4191574207234L}); + } + }; + Proxy proxy = (Proxy) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{SimpleDao.class}, handler); + + + Method m = z.getMethod("abc", new Class[]{String.class, String.class, Long.class}); + + try { + handler.invoke(proxy, m, new Object[]{"abc", "cde", 4191574207234L}); + } catch (Throwable throwable) { + throwable.printStackTrace(); + } + + + } catch (Exception e) { + e.printStackTrace(); + } + + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/client/ShardingCaculatorTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/client/ShardingCaculatorTest.java new file mode 100644 index 0000000..394c19f --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/client/ShardingCaculatorTest.java @@ -0,0 +1,80 @@ +package com.mogujie.service.tsharding.client; + +import com.mogujie.trade.tsharding.client.ShardingCaculator; +import org.junit.Assert; +import org.junit.Test; + +/** + * @auther qigong on 5/29/15 8:28 AM. + */ +public class ShardingCaculatorTest { + + + @Test + public void testCaculateTableName() { + + ShardingparaObj para = new ShardingparaObj(); + para.setName("buyerId"); + para.setValue(100000000L); + Assert.assertEquals("TestTable0000", ShardingCaculator.caculateTableName(para.getValue())); + para.setValue(100000128L); + Assert.assertEquals("TestTable0128", ShardingCaculator.caculateTableName(para.getValue())); + para.setValue(100000512L); + Assert.assertEquals("TestTable0000", ShardingCaculator.caculateTableName(para.getValue())); + } + + @Test + public void testCaculateSchemaName() { + + ShardingparaObj para = new ShardingparaObj(); + para.setName("sellerUserId"); + para.setValue(100000000L); + Assert.assertEquals("sellertestschema0000", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); + para.setValue(100000128L); + Assert.assertEquals("sellertestschema0002", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); + para.setName("buyerUserId"); + para.setValue(100000512L); + Assert.assertEquals("testschema0000", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); + } + + @Test + public void testCaculateDatasourceName() { + + ShardingparaObj para = new ShardingparaObj(); + para.setName("sellerUserId"); + para.setValue(100000000L); + Assert.assertEquals("seller_ds_0", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); + para.setValue(100000128L); + Assert.assertEquals("seller_ds_0", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); + para.setName("buyerUserId"); + para.setValue(100000511L); + Assert.assertEquals("buyer_ds_1", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); + } + + @Test + public void testgetNumberWithZeroSuffix(){ + Assert.assertEquals("0100", ShardingCaculator.getNumberWithZeroSuffix(100L)); + } + + private class ShardingparaObj { + private String name; + private Long value; + + public void setName(String name) { + this.name = name; + } + + public void setValue(Long value) { + this.value = value; + } + + public String getName() { + return name; + } + + public Long getValue() { + return value; + } + + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/InvokerTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/InvokerTest.java new file mode 100644 index 0000000..969f386 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/InvokerTest.java @@ -0,0 +1,13 @@ +package com.mogujie.service.tsharding.route; + +import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; +import com.mogujie.trade.tsharding.route.orm.base.Invocation; + +/** + * @author qigong + * + */ +public interface InvokerTest { + + Object invoke(@ShardingOrderPara("test") Invocation invocation) throws Throwable; +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/SchemaPropertiesTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/SchemaPropertiesTest.java new file mode 100644 index 0000000..8295299 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/SchemaPropertiesTest.java @@ -0,0 +1,31 @@ +package com.mogujie.service.tsharding.route; + +import com.mogujie.trade.tsharding.client.ShardingCaculator; + +/** + * @author qigong on 15/9/7 下午6:16. + */ +public class SchemaPropertiesTest { + + public static void main(String[] args) { + + String[] initConfs = new String[]{ + "# testschemapressuretest master", + "testschemapressuretest.master.url=jdbc:mysql://10.11.4.110/testschemapressuretest", + "testschemapressuretest.master.port=3306", + "testschemapressuretest.master.username=pigeontest", + "testschemapressuretest.master.password=3ff143dd2bbd399a3af6349f1b46f2e2", + "testschemapressuretest.master.minPoolSize=1", + "testschemapressuretest.master.maxPoolSize=24", + "testschemapressuretest.master.initialPoolSize=1" + }; + + for (long i = 0; i < 8; i++) { + for (String conf : initConfs) { + conf = conf.replace("testschemapressuretest", "testschemapressuretest" + ShardingCaculator.getNumberWithZeroSuffix(i)); + conf = conf.replace("sharding0", "sharding" + i/4); + System.out.println(conf); + } + } + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderDaoTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderDaoTest.java new file mode 100644 index 0000000..cb4d363 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderDaoTest.java @@ -0,0 +1,30 @@ +package com.mogujie.service.tsharding.route; + +import com.mogujie.service.tsharding.route.dal.ShopOrder; +import com.mogujie.service.tsharding.route.dal.ShopOrderDao; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.ArrayList; +import java.util.List; + +/** + * @auther qigong on 6/5/15 8:55 PM. + */ +public class ShopOrderDaoTest { + + @Autowired + private ShopOrderDao shopOrderDao; + + @Test + public void testGetShopOrderByShopOrderIdsOfOneBuyer() { + + List listShopOrderIds = new ArrayList<>(); + listShopOrderIds.add(3968484880824L); + listShopOrderIds.add(3968484880824L); + + List shopOrders = shopOrderDao.getShopOrderByShopOrderIdsOfOneBuyer(listShopOrderIds); + Assert.assertTrue(shopOrders.size() == 2); + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderMapperTest.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderMapperTest.java new file mode 100644 index 0000000..e83c004 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/ShopOrderMapperTest.java @@ -0,0 +1,110 @@ +package com.mogujie.service.tsharding.route; + +import com.mogujie.service.tsharding.route.dal.ShopOrder; +import com.mogujie.service.tsharding.route.dal.ShopOrderMapper; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.Arrays; +import java.util.List; + +public class ShopOrderMapperTest { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private ConfigurableApplicationContext applicationContext; + + @Autowired + private ShopOrderMapper shopOrderMapper; + + @Before + public void setUp() throws Exception { + this.applicationContext = new ClassPathXmlApplicationContext("META-INF/tesla/support/service-loader.xml", + "META-INF/tesla/support/datasource.xml"); + this.shopOrderMapper = applicationContext.getBean(ShopOrderMapper.class); + } + + @After + public void tearDown() throws Exception { + this.applicationContext.close(); + } + + + @Test + public void testXdOrderSharding() { + + ShopOrder shopOrder = null; + try { + shopOrder = shopOrderMapper.getShopOrderByShopOrderId(3950627820824L); + logger.debug("{}", shopOrder); + Assert.assertNotNull(shopOrder); + + shopOrder = shopOrderMapper.getShopOrderByShopOrderId(3950627820002L); + logger.debug("{}", shopOrder); + Assert.assertNull(shopOrder); + +// List shopOrders = shopOrderMapper.getShopOrderByShopOrderIdsOfOneBuyer(Arrays.asList(new Long[]{3950627820824L,3950631130824L})); +// logger.debug("{}", shopOrders); +// Assert.assertEquals(2, shopOrders.size()); + + +// Assert.assertEquals(new Integer(0), xdShopOrder.getVisible()); +// xdShopOrder.setVisible(1); +// xdShopOrderMapperPart1.updateShopOrderByShopOrderIdAndStatus(xdShopOrder.getOrderId(),xdShopOrder, xdShopOrder.getStatus()); +// xdShopOrder = xdShopOrderMapper.getShopOrderByShopOrderId(3950627820824L); +// logger.debug("after update visible:{}", xdShopOrders); +// Assert.assertEquals(new Integer(1), xdShopOrder.getVisible()); +// +// xdShopOrder.setVisible(0); +// xdShopOrderMapperPart1.updateShopOrderByShopOrderIdAndStatus(xdShopOrder.getOrderId(),xdShopOrder, xdShopOrder.getStatus()); +// xdShopOrder = xdShopOrderMapper.getShopOrderByShopOrderId(3950627820824L); +// Assert.assertEquals(new Integer(0), xdShopOrder.getVisible()); + +// xdShopOrders = xdShopOrderMapperPart1.getShopOrdersByPayOrderIdsOfOneBuyer(Arrays.asList(new Long[]{3950627810824L, 3950631120824L})); +// logger.debug("{}", xdShopOrders); +// Assert.assertEquals(2, xdShopOrders.size()); +// +// Long payOrderId = 3971291360824L; +// XdShopOrder record = new XdShopOrder(); +// record.setParentOrderId(payOrderId); +// record.setStatus(1 /*BusinessConstants.ORDER_STATUS_CANCELED*/); +// Long now = (System.currentTimeMillis() / 1000); +// record.setCancelTime(now); +// record.setCancelReason("ddd"); +// record.setExpiredTime(1L); +// record.setUpdated(1L); +// xdShopOrderMapper.batchUpdateShopOrderByShopOrderIdsAndStatusOfOneBuyer(Arrays.asList(new Long[]{3971291380824L, 3971291370824L}), record, record.getStatus()); +// xdShopOrder = xdShopOrderMapper.getShopOrderByShopOrderId(3971291380824L); +// logger.debug("{}", xdShopOrder); +// Assert.assertEquals(now, xdShopOrder.getCancelTime()); +// +// +// //updateShipExpenseByShopOrderId +// xdShopOrder.setOrderId(3981357789949L); +// xdShopOrder.setShipExpense(1200L); +// xdShopOrderMapper.updateShopOrderByShopOrderId(xdShopOrder.getOrderId(), xdShopOrder); +// xdShopOrder = xdShopOrderMapper.getShopOrderByShopOrderId(3981357789949L); +// logger.debug("{}", xdShopOrder); +// Assert.assertEquals(new Long(1200), xdShopOrder.getShipExpense()); +// +// +// //扫单表 +// xdShopOrders = xdShopOrderMapper.getOrdersByUpdatedTime(181,1430740105L, 1430833654L); +// logger.debug("{}", xdShopOrders); +// Assert.assertTrue(xdShopOrders.size() > 1); + + } catch (Exception e) { + logger.error("test error", e); + } + + } + + +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/Test.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/Test.java new file mode 100644 index 0000000..16a376e --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/Test.java @@ -0,0 +1,61 @@ +package com.mogujie.service.tsharding.route; + +import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; +import com.mogujie.trade.tsharding.route.orm.MapperAnnotationEnhancer; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; + +import java.io.IOException; + +/** + * @author qigong on 15/9/5 下午12:14. + */ +public class Test { + + public static void main(String[] args) { + + ClassPool cp = ClassPool.getDefault(); + try { + CtClass cc = cp.makeInterface("com.mogujie.service.tsharding.route.Tester"); + + ClassPool pool = ClassPool.getDefault(); + + Class originClass = Class.forName("com.mogujie.service.tsharding.route.InvokerTest"); + + + CtClass c = pool.get("com.mogujie.service.tsharding.route.InvokerTest"); + CtMethod ctMethod = c.getDeclaredMethods()[0]; + CtMethod cm = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(), ctMethod.getParameterTypes(), cc); + ClassFile ccFile = cc.getClassFile(); + ConstPool constPool = ccFile.getConstPool(); + cm.getMethodInfo().addAttribute(MapperAnnotationEnhancer.duplicateParameterAnnotationsAttribute(constPool, originClass.getDeclaredMethods()[0])); + + cc.addMethod(cm); + Class newClass = cc.toClass(); + + java.lang.annotation.Annotation[][] annotations = newClass.getDeclaredMethods()[0].getParameterAnnotations(); + if (annotations.length > 0) { + for (int i = 0; i < annotations.length; i++) { + for (int j = 0; j < annotations[i].length; j++) { + if (annotations[i][j] instanceof ShardingOrderPara) { + Long shardingPara = 0L; + System.out.println("comin again!"); + break; + } + } + } + } + + try { + cc.writeFile("."); + } catch (IOException e) { + e.printStackTrace(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/TestJavaAssit.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/TestJavaAssit.java new file mode 100644 index 0000000..f8b567a --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/TestJavaAssit.java @@ -0,0 +1,61 @@ +package com.mogujie.service.tsharding.route; + +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.bytecode.AnnotationsAttribute; +import javassist.bytecode.ClassFile; +import javassist.bytecode.ConstPool; +import javassist.bytecode.annotation.Annotation; +import javassist.bytecode.annotation.IntegerMemberValue; + +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * @author qigong on 15/9/5 下午12:14. + */ +public class TestJavaAssit { + + public static void main(String[] args){ + + ClassPool cp = ClassPool.getDefault(); + try { + CtClass cc = cp.makeClass("com.mogujie.service.tsharding.route.Tester1"); + CtMethod cm = new CtMethod(CtClass.intType, "add", new CtClass[] { + CtClass.intType, CtClass.intType }, cc); + + + ClassFile ccFile = cc.getClassFile(); + ConstPool constpool = ccFile.getConstPool(); + + AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag); + Annotation annot = new Annotation("com.mogujie.trade.tsharding.annotation.parameter.ShardingTablePara", constpool); + annot.addMemberValue("value", new IntegerMemberValue(ccFile.getConstPool(), 0)); + + cm.getMethodInfo().addAttribute(attr); + +// + cm.setBody("return $1 + $2;"); + cm.insertAfter("for(int i=0;i<$args.length;i++)" + + "{System.out.println(\"args[\"+i+\"]=\"+$args[i]);}"); + cc.addMethod(cm); + Class newClass = cc.toClass(); + Object o = newClass.newInstance(); + Method m = newClass.getDeclaredMethod("add", int.class, int.class); + System.out.println("Result:" + m.invoke(o, 1, 2)); + try { + cc.writeFile("."); + } catch (IOException e) { + e.printStackTrace(); + } + } catch (CannotCompileException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/BaseOrder.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/BaseOrder.java new file mode 100644 index 0000000..1f4c877 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/BaseOrder.java @@ -0,0 +1,222 @@ +package com.mogujie.service.tsharding.route.dal; + +/** + * @author xueyan on 15/1/19. + */ +public abstract class BaseOrder { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 买家的userId + */ + private Long buyerUserId; + + /** + * 订单类型 + */ + private Integer type; + + /** + * 商品级订单: 商品数量; 店铺级订单: 子商品级订单所有商品数量总和 + */ + private Integer number; + + /** + * 商品级订单: 订单价格(计算商品打折), 店铺级订单: 子商品级订单价格总和(计算店铺优惠) + */ + private Long price; + + /** + * 促销详情:例如促销单号... + */ + private String promotion; + + /** + * 支付时间, 未支付时为空或NULL + */ + private Long payTime; + + /** + * 支付ID + */ + private Long payId; + /** + * 订单当前状态的超时时刻 + */ + private Long expiredTime; + + /** + * 下单平台类型, 0: PC; 1000000: APP + */ + private Integer platformType; + + /** + * 来源类型,用于来源跟踪 + */ + private String source; + + /** + * 自定义扩展信息 + */ + private String extra; + + /** + * 自定义扩展信息 + */ + private Long extraInt; + + /** + * 创建时间 + */ + private Long created; + + /** + * 更新时间 + */ + private Long updated; + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public Long getBuyerUserId() { + return buyerUserId; + } + + public void setBuyerUserId(Long buyerUserId) { + this.buyerUserId = buyerUserId; + } + + public Integer getType() { + return type; + } + + public void setType(Integer type) { + this.type = type; + } + + public Integer getNumber() { + return number; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public Long getPrice() { + return price; + } + + public void setPrice(Long price) { + this.price = price; + } + + public String getPromotion() { + return promotion; + } + + public void setPromotion(String promotion) { + this.promotion = promotion; + } + + public Long getPayTime() { + return payTime; + } + + public void setPayTime(Long payTime) { + this.payTime = payTime; + } + + public Long getPayId() { + return payId; + } + + public void setPayId(Long payId) { + this.payId = payId; + } + + public Long getExpiredTime() { + return expiredTime; + } + + public void setExpiredTime(Long expiredTime) { + this.expiredTime = expiredTime; + } + + public Integer getPlatformType() { + return platformType; + } + + public void setPlatformType(Integer platformType) { + this.platformType = platformType; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getExtra() { + return extra; + } + + public void setExtra(String extra) { + this.extra = extra; + } + + public Long getExtraInt() { + return extraInt; + } + + public void setExtraInt(Long extraInt) { + this.extraInt = extraInt; + } + + public Long getCreated() { + return created; + } + + public void setCreated(Long created) { + this.created = created; + } + + public Long getUpdated() { + return updated; + } + + public void setUpdated(Long updated) { + this.updated = updated; + } + + @Override + public String toString() { + return "XdBaseOrder{" + + "orderId=" + orderId + + ", buyerUserId=" + buyerUserId + + ", type=" + type + + ", number=" + number + + ", price=" + price + + ", promotion='" + promotion + '\'' + + ", payTime=" + payTime + + ", payId=" + payId + + ", expiredTime=" + expiredTime + + ", platformType=" + platformType + + ", source='" + source + '\'' + + ", extra='" + extra + '\'' + + ", extraInt=" + extraInt + + ", created=" + created + + ", updated=" + updated + + '}'; + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrder.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrder.java new file mode 100644 index 0000000..6e5ba45 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrder.java @@ -0,0 +1,280 @@ +package com.mogujie.service.tsharding.route.dal; + +/** + * @author xueyan on 15/1/19. + */ +public class ShopOrder extends BaseOrder { + + /** + * 父订单ID + */ + private Long parentOrderId; + + /** + * 订单级别, 0: 商品级; 1: 店铺级 + */ + private final Integer level = 1; + + /** + * 卖家的userId + */ + private Long sellerUserId; + + /** + * 交易流程类型, 0: 全款交易,1: 二阶段预售 + */ + private Integer processType; + + /** + * 快递费用 + */ + private Long shipExpense; + + /** + * 店铺级订单:店铺优惠; 支付级订单: 平台优惠 + */ + private Long promotionAmount; + + /** + * 订单状态 + */ + private Integer status; + + /** + * 订单扩展状态 + */ + private Integer statusEx; + + /** + * 是否可见, 0: 可见 1: 不可见 + */ + private Integer visible; + + /** + * 发货时间 + */ + private Long shipTime; + + /** + * 确认收货时间 + */ + private Long receiveTime; + + /** + * 确认收货触发类型, 0: 买家触发,1: 快递签收触发(自动), 2: 超时触发(自动) + */ + private Integer receiveType; + + /** + * 评论状态 + */ + private Integer rateFromBuyerVisible; + + /** + * 取消订单时间, 如果取消与支付回调同时发生则优先考虑支付相关状态(取消时间为0) + */ + private Long cancelTime; + + /** + * 结算时间 + */ + private Long settlementTime; + + /** + * 取消原因 + */ + private String cancelReason; + + /** + * 买家备注 + */ + private String buyerComment; + + /** + * 卖家备注 + */ + private String sellerComment; + + /** + * 订单逆向流程状态 + */ + private Integer reverseStatus; + + + public static ShopOrder getInstance() { + return new ShopOrder(); + } + + public Long getParentOrderId() { + return this.parentOrderId; + } + + public void setParentOrderId(Long parentOrderId) { + this.parentOrderId = parentOrderId; + } + + public Integer getLevel() { + return level; + } + + public Long getSellerUserId() { + return sellerUserId; + } + + public void setSellerUserId(Long sellerUserId) { + this.sellerUserId = sellerUserId; + } + + public Integer getProcessType() { + return processType; + } + + public void setProcessType(Integer processType) { + this.processType = processType; + } + + public Long getShipExpense() { + return shipExpense; + } + + public void setShipExpense(Long shipExpense) { + this.shipExpense = shipExpense; + } + + public Long getPromotionAmount() { + return promotionAmount; + } + + public void setPromotionAmount(Long promotionAmount) { + this.promotionAmount = promotionAmount; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public Integer getStatusEx() { + return statusEx; + } + + public void setStatusEx(Integer statusEx) { + this.statusEx = statusEx; + } + + public Integer getVisible() { + return visible; + } + + public void setVisible(Integer visible) { + this.visible = visible; + } + + public Long getShipTime() { + return shipTime; + } + + public void setShipTime(Long shipTime) { + this.shipTime = shipTime; + } + + public Long getReceiveTime() { + return receiveTime; + } + + public void setReceiveTime(Long receiveTime) { + this.receiveTime = receiveTime; + } + + public Integer getReceiveType() { + return receiveType; + } + + public void setReceiveType(Integer receiveType) { + this.receiveType = receiveType; + } + + public Integer getRateFromBuyerVisible() { + return rateFromBuyerVisible; + } + + public void setRateFromBuyerVisible(Integer rateFromBuyerVisible) { + this.rateFromBuyerVisible = rateFromBuyerVisible; + } + + public Long getCancelTime() { + return cancelTime; + } + + public void setCancelTime(Long cancelTime) { + this.cancelTime = cancelTime; + } + + public Long getSettlementTime() { + return settlementTime; + } + + public void setSettlementTime(Long settlementTime) { + this.settlementTime = settlementTime; + } + + public String getCancelReason() { + return cancelReason; + } + + public void setCancelReason(String cancelReason) { + this.cancelReason = cancelReason; + } + + public String getBuyerComment() { + return buyerComment; + } + + public void setBuyerComment(String buyerComment) { + this.buyerComment = buyerComment; + } + + public String getSellerComment() { + return sellerComment; + } + + public void setSellerComment(String sellerComment) { + this.sellerComment = sellerComment; + } + + public Integer getReverseStatus() { + return reverseStatus; + } + + public void setReverseStatus(Integer reverseStatus) { + this.reverseStatus = reverseStatus; + } + + @Override + public String toString() { + return "XdShopOrder{" + + "parentOrderId=" + parentOrderId + + ", level=" + level + + ", sellerUserId=" + sellerUserId + + ", processType=" + processType + + ", shipExpense=" + shipExpense + + ", promotionAmount=" + promotionAmount + + ", status=" + status + + ", statusEx=" + statusEx + + ", visible=" + visible + + ", shipTime=" + shipTime + + ", receiveTime=" + receiveTime + + ", receiveType=" + receiveType + + ", rateFromBuyerVisible=" + rateFromBuyerVisible + + ", cancelTime=" + cancelTime + + ", settlementTime=" + settlementTime + + ", cancelReason='" + cancelReason + '\'' + + ", buyerComment='" + buyerComment + '\'' + + ", sellerComment='" + sellerComment + '\'' + + ", reverseStatus=" + reverseStatus + + "} " + super.toString(); + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDao.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDao.java new file mode 100644 index 0000000..9a0e066 --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDao.java @@ -0,0 +1,31 @@ +package com.mogujie.service.tsharding.route.dal; + +import com.mogujie.tesla.db.DataSourceRouting; +import com.mogujie.trade.tsharding.route.TShardingRoutingHandler; + +import java.util.List; + +/** + * @auther qigong on 6/5/15 8:50 PM. + */ +@DataSourceRouting(handler = TShardingRoutingHandler.class) +public interface ShopOrderDao { + + /** + * 根据店铺级订单ID获取订单信息(同一个买家) + * + * @param listShopOrderIds 店铺级订单ID集合 + * @return List + */ + List getShopOrderByShopOrderIdsOfOneBuyer(List listShopOrderIds); + + /** + * 根据店铺级订单ID获取订单信息(多个买家) + * + * @param listShopOrderIds 店铺级订单ID集合 + * @return List + */ + List getShopOrderByShopOrderIdsOfMultiBuyer(List listShopOrderIds); + + +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDaoImpl.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDaoImpl.java new file mode 100644 index 0000000..8cbcdcf --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderDaoImpl.java @@ -0,0 +1,53 @@ +package com.mogujie.service.tsharding.route.dal; + +import com.mogujie.trade.tsharding.client.ShardingCaculator; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.*; + +/** + * @auther qigong on 6/5/15 8:52 PM. + */ +public class ShopOrderDaoImpl implements ShopOrderDao { + + @Autowired + private ShopOrderMapper shopOrderMapper; + + @Override + public List getShopOrderByShopOrderIdsOfOneBuyer(List listShopOrderIds) { + if (listShopOrderIds == null || listShopOrderIds.size() == 0) { + return null; + } + Set setShopOrderIds = new HashSet(); + for (Long iShopOrderId : listShopOrderIds) { + if (iShopOrderId > 0) { + setShopOrderIds.add(iShopOrderId); + } + } + return shopOrderMapper.getShopOrderByShopOrderIdsOfOneBuyer(new ArrayList(setShopOrderIds)); + } + + + @Override + public List getShopOrderByShopOrderIdsOfMultiBuyer(List listShopOrderIds) { + if (listShopOrderIds == null || listShopOrderIds.size() == 0) { + return null; + } + HashMap> shopOrderIdsMap = new HashMap(); + for (Long shopOrderId : listShopOrderIds) { + Integer tableIndex = ShardingCaculator.caculateTableIndex(shopOrderId); + List orderIds = shopOrderIdsMap.get(tableIndex); + if (orderIds == null) { + orderIds = new ArrayList<>(); + } + orderIds.add(shopOrderId); + shopOrderIdsMap.put(tableIndex, orderIds); + } + List result = new ArrayList<>(); + + for (Integer tableIndex : shopOrderIdsMap.keySet()) { + result.addAll(shopOrderMapper.getShopOrderByShopOrderIdsOfOneBuyer(shopOrderIdsMap.get(tableIndex))); + } + return result; + } +} diff --git a/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderMapper.java b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderMapper.java new file mode 100644 index 0000000..e29e84f --- /dev/null +++ b/tsharding-client/src/test/java/com/mogujie/service/tsharding/route/dal/ShopOrderMapper.java @@ -0,0 +1,20 @@ +package com.mogujie.service.tsharding.route.dal; + +import com.mogujie.tesla.db.DataSourceRouting; +import com.mogujie.trade.tsharding.annotation.ShardingExtensionMethod; +import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; +import com.mogujie.trade.tsharding.route.TShardingRoutingHandler; +import com.mogujie.trade.tsharding.route.orm.MapperResourceEnhancer; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@DataSourceRouting(handler=TShardingRoutingHandler.class) +public interface ShopOrderMapper { + @ShardingExtensionMethod(type = MapperResourceEnhancer.class, method = "enhancedShardingSQL") + public ShopOrder getShopOrderByShopOrderId(@ShardingOrderPara("orderId") Long shopOrderId); + + @ShardingExtensionMethod(type = MapperResourceEnhancer.class, method = "enhancedShardingSQL") + public List getShopOrderByShopOrderIdsOfOneBuyer(@ShardingOrderPara("orderId") List xdShopOrderIds); + +} \ No newline at end of file diff --git a/tsharding-client/src/test/resources/META-INF/tesla/support/service-loader.xml b/tsharding-client/src/test/resources/META-INF/tesla/support/service-loader.xml new file mode 100644 index 0000000..976ba0b --- /dev/null +++ b/tsharding-client/src/test/resources/META-INF/tesla/support/service-loader.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tsharding-client/src/test/resources/jdbc.properties b/tsharding-client/src/test/resources/jdbc.properties new file mode 100644 index 0000000..7c08398 --- /dev/null +++ b/tsharding-client/src/test/resources/jdbc.properties @@ -0,0 +1,137 @@ +# testschema0000 master +testschema0000.master.url=jdbc:mysql://127.0.0.1/testschema0000 +testschema0000.master.username=root +testschema0000.master.password=root123 +testschema0000.master.minPoolSize=0 +testschema0000.master.maxPoolSize=10 +testschema0000.master.initialPoolSize=0 +# testschema0000 slave +testschema0000.slave.url=jdbc:mysql://127.0.0.1/testschema0000 +testschema0000.slave.username=root +testschema0000.slave.password=root123 +testschema0000.slave.minPoolSize=1 +testschema0000.slave.maxPoolSize=24 +testschema0000.slave.initialPoolSize=1 + +# testschema0001 master +testschema0001.master.url=jdbc:mysql://127.0.0.1/testschema0001 +testschema0001.master.username=root +testschema0001.master.password=root123 +testschema0001.master.minPoolSize=0 +testschema0001.master.maxPoolSize=10 +testschema0001.master.initialPoolSize=0 +# testschema0001 slave +testschema0001.slave.url=jdbc:mysql://127.0.0.1/testschema0001 +testschema0001.slave.username=root +testschema0001.slave.password=root123 +testschema0001.slave.minPoolSize=1 +testschema0001.slave.maxPoolSize=24 +testschema0001.slave.initialPoolSize=1 + +# testschema0002 master +testschema0002.master.url=jdbc:mysql://127.0.0.1/testschema0002 +testschema0002.master.username=root +testschema0002.master.password=root123 +testschema0002.master.minPoolSize=0 +testschema0002.master.maxPoolSize=10 +testschema0002.master.initialPoolSize=0 +# testschema0002 slave +testschema0002.slave.url=jdbc:mysql://127.0.0.1/testschema0002 +testschema0002.slave.username=root +testschema0002.slave.password=root123 +testschema0002.slave.minPoolSize=1 +testschema0002.slave.maxPoolSize=24 +testschema0002.slave.initialPoolSize=1 + +# testschema0003 master +testschema0003.master.url=jdbc:mysql://127.0.0.1/testschema0003 + +testschema0003.master.username=root +testschema0003.master.password=root123 +testschema0003.master.minPoolSize=0 +testschema0003.master.maxPoolSize=10 +testschema0003.master.initialPoolSize=0 +# testschema0003 slave +testschema0003.slave.url=jdbc:mysql://127.0.0.1/testschema0003 +testschema0003.slave.username=root +testschema0003.slave.password=root123 +testschema0003.slave.minPoolSize=1 +testschema0003.slave.maxPoolSize=24 +testschema0003.slave.initialPoolSize=1 + +# testschema0004 master +testschema0004.master.url=jdbc:mysql://127.0.0.1/testschema0004 +testschema0004.master.username=root +testschema0004.master.password=root123 +testschema0004.master.minPoolSize=0 +testschema0004.master.maxPoolSize=10 +testschema0004.master.initialPoolSize=0 +# testschema0004 slave +testschema0004.slave.url=jdbc:mysql://127.0.0.1/testschema0004 +testschema0004.slave.username=root +testschema0004.slave.password=root123 +testschema0004.slave.minPoolSize=1 +testschema0004.slave.maxPoolSize=24 +testschema0004.slave.initialPoolSize=1 + +# testschema0005 master +testschema0005.master.url=jdbc:mysql://127.0.0.1/testschema0005 +testschema0005.master.username=root +testschema0005.master.password=root123 +testschema0005.master.minPoolSize=0 +testschema0005.master.maxPoolSize=10 +testschema0005.master.initialPoolSize=0 +# testschema0005 slave +testschema0005.slave.url=jdbc:mysql://127.0.0.1/testschema0005 +testschema0005.slave.username=root +testschema0005.slave.password=root123 +testschema0005.slave.minPoolSize=1 +testschema0005.slave.maxPoolSize=24 +testschema0005.slave.initialPoolSize=1 + +# testschema0006 master +testschema0006.master.url=jdbc:mysql://127.0.0.1/testschema0006 +testschema0006.master.username=root +testschema0006.master.password=root123 +testschema0006.master.minPoolSize=0 +testschema0006.master.maxPoolSize=10 +testschema0006.master.initialPoolSize=0 +# testschema0006 slave +testschema0006.slave.url=jdbc:mysql://127.0.0.1/testschema0006 +testschema0006.slave.username=root +testschema0006.slave.password=root123 +testschema0006.slave.minPoolSize=1 +testschema0006.slave.maxPoolSize=24 +testschema0006.slave.initialPoolSize=1 + +# testschema0007 master +testschema0007.master.url=jdbc:mysql://127.0.0.1/testschema0007 +testschema0007.master.username=root +testschema0007.master.password=root123 +testschema0007.master.minPoolSize=0 +testschema0007.master.maxPoolSize=10 +testschema0007.master.initialPoolSize=0 +# testschema0007 slave +testschema0007.slave.url=jdbc:mysql://127.0.0.1/testschema0007 +testschema0007.slave.username=root +testschema0007.slave.password=root123 +testschema0007.slave.minPoolSize=1 +testschema0007.slave.maxPoolSize=24 +testschema0007.slave.initialPoolSize=1 + +# testschema单库 master +testschema.master.url=jdbc:mysql://127.0.0.1/trade +testschema.master.username=root +testschema.master.password=root123 +testschema.master.minPoolSize=0 +testschema.master.maxPoolSize=10 +testschema.master.initialPoolSize=0 +# rootjie slave +testschema.slave.url=jdbc:mysql://127.0.0.1/trade +testschema.slave.username=root +testschema.slave.password=root123 +testschema.slave.minPoolSize=1 +testschema.slave.maxPoolSize=24 +testschema.slave.initialPoolSize=1 + + diff --git a/tsharding-client/src/test/resources/logback-test.xml b/tsharding-client/src/test/resources/logback-test.xml new file mode 100644 index 0000000..ef8fb9f --- /dev/null +++ b/tsharding-client/src/test/resources/logback-test.xml @@ -0,0 +1,22 @@ + + + + + + + + + %d [%t] %5p \(%F:%L\) %M\(\) - %m%n + + + + + + + + + + + + + \ No newline at end of file diff --git a/tsharding-client/src/test/resources/sqlmap/shoporder-mapper.xml b/tsharding-client/src/test/resources/sqlmap/shoporder-mapper.xml new file mode 100644 index 0000000..14256f4 --- /dev/null +++ b/tsharding-client/src/test/resources/sqlmap/shoporder-mapper.xml @@ -0,0 +1,4 @@ + + diff --git a/tsharding-client/src/test/resources/tesla.properties b/tsharding-client/src/test/resources/tesla.properties new file mode 100644 index 0000000..670a3d2 --- /dev/null +++ b/tsharding-client/src/test/resources/tesla.properties @@ -0,0 +1,19 @@ +tesla.application.name = test-service +tesla.owner = tesla +tesla.port = 20019 + +tesla.invokerExecutor.corePoolSize = 24 +tesla.invokerExecutor.maxPoolSize = 96 +tesla.invokerExecutor.keepAliveSeconds = 60 +tesla.invokerExecutor.queueCapacity = 5120 + +tesla.worker.threads=0 + +tesla.registry.ignoreError = true +tesla.monitorTask.timeSpan = 60000 + +tesla.network.readIdleTimeout = 0 +tesla.network.compressThreshold = 1024 + +tesla.orm.mapperPacakge=com.mogujie.service.tsharding.route.dal +tsharding.needEnhancedClasses=com.mogujie.service.tsharding.route.dal.ShopOrderMapper \ No newline at end of file