├── .gitignore ├── LICENSE ├── README.md └── tsharding-client ├── .gitignore ├── README.md ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── mogujie │ │ └── trade │ │ ├── db │ │ ├── DataSourceConfig.java │ │ ├── DataSourceFactory.java │ │ ├── DataSourceLookup.java │ │ ├── DataSourceRouting.java │ │ ├── DataSourceRoutingException.java │ │ ├── DataSourceRoutingHandler.java │ │ ├── DataSourceScanner.java │ │ ├── DataSourceType.java │ │ ├── DruidDataSourceFactory.java │ │ ├── EmptyDataSourceRoutingHandler.java │ │ ├── ReadWriteSplitting.java │ │ ├── ReadWriteSplittingAdvice.java │ │ ├── ReadWriteSplittingContext.java │ │ ├── ReadWriteSplittingDataSource.java │ │ ├── ReadWriteSplittingException.java │ │ ├── RoutingDataSourceTransactionContext.java │ │ └── RoutingDataSourceTransactionManager.java │ │ └── tsharding │ │ ├── annotation │ │ ├── ShardingExtensionMethod.java │ │ └── parameter │ │ │ ├── ShardingBuyerPara.java │ │ │ ├── ShardingOrderPara.java │ │ │ └── ShardingSellerPara.java │ │ ├── client │ │ └── ShardingCaculator.java │ │ └── route │ │ ├── TShardingRoutingHandler.java │ │ ├── TShardingRoutingHandlerForPressureTest.java │ │ └── orm │ │ ├── MapperAnnotationEnhancer.java │ │ ├── MapperEnhancer.java │ │ ├── MapperHelperForSharding.java │ │ ├── MapperResourceEnhancer.java │ │ ├── MapperScannerWithSharding.java │ │ ├── MapperShardingInitializer.java │ │ └── base │ │ ├── ClassPathScanHandler.java │ │ ├── DefaultInvocation.java │ │ ├── Invocation.java │ │ ├── Invoker.java │ │ ├── InvokerFactory.java │ │ ├── MapperBasicConfig.java │ │ ├── MapperInitializeException.java │ │ ├── ReadWriteSplittingContextInitializer.java │ │ ├── ReflectUtil.java │ │ ├── SqlSessionFactoryLookup.java │ │ └── TShardingRoutingInvokeFactory.java └── resources │ └── tesla │ └── support │ └── service-loader.xml └── test ├── java └── com │ └── mogujie │ └── service │ └── tsharding │ ├── bean │ ├── BaseOrder.java │ └── ShopOrder.java │ ├── dao │ ├── ShopOrderDao.java │ └── ShopOrderDaoImpl.java │ ├── mapper │ └── ShopOrderMapper.java │ └── test │ ├── BaseTest.java │ ├── TShardingTest.java │ └── client │ └── ShardingCaculatorTest.java └── resources ├── META-INF └── support │ ├── datasource.xml │ └── service-loader.xml ├── app.properties ├── jdbc.properties ├── logback.xml ├── spring-test.xml └── sqlmap └── shoporder-mapper.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Harvey Bai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tsharding 2 | ### TSharding is the simple sharding component used in mogujie trade platform. 3 | ### 分库分表业界方案 4 | ![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-select.png) 5 | 6 | ### 分库分表TSharding 7 | ##### TSharding组件目标 8 | * 很少的资源投入即可开发完成 9 | * 支持交易订单表的Sharding需求,分库又分表 10 | * 支持数据源路由 11 | * 支持事务 12 | * 支持结果集合并 13 | * 支持读写分离 14 | 15 | ##### TSharding Resources Abstract 16 | ![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-abstract.png) 17 | 18 | ##### TSharding Resources Classes 19 | ![alt text](https://github.com/baihui212/intro/raw/master/pics/tsharding-classes.png) 20 | 21 | ##### TSharding组件接入过程: 22 | * 引入TSharding JAR包 23 | * 配置所有分库的JDBC连接信息 24 | * Mybatis Mapper方法参数增加ShardingOrderPara/ShardingBuyerPara/ShardingSellerPara注解 25 | * 批量查询增加结果集合并逻辑 26 | 27 | ##### 28 | TSharding遵循GPL V2协议。 29 | -------------------------------------------------------------------------------- /tsharding-client/.gitignore: -------------------------------------------------------------------------------- 1 | # maven ignore 2 | target/ 3 | *.jar 4 | *.war 5 | *.zip 6 | *.tar 7 | *.tar.gz 8 | 9 | # eclipse ignore 10 | .metadata/ 11 | .settings/ 12 | .project 13 | .classpath 14 | .springBeans 15 | 16 | # idea ignore 17 | .idea/ 18 | *.ipr 19 | *.iml 20 | *.iws 21 | 22 | # temp ignore 23 | logs/ 24 | *.log 25 | *.cache 26 | *.diff 27 | *.patch 28 | *.tmp 29 | *.swp 30 | 31 | # system ignore 32 | .DS_Store 33 | Thumbs.db 34 | 35 | /bin/ 36 | -------------------------------------------------------------------------------- /tsharding-client/README.md: -------------------------------------------------------------------------------- 1 | ## 交易分库分表组件TSharding 2 | 3 | 4 | ### 关键类 5 | * 1.测试用例入口 com.mogujie.service.tsharding.test#TShardingTest 6 | 7 | * 2.默认走Master库的前缀命名 com.mogujie.trade.tsharding.route.orm.base.ReadWriteSplittingContextInitializer.DEFAULT_WRITE_METHOD_NAMES 8 | 9 | * 3.SQL增强 com.mogujie.trade.tsharding.route.orm.MapperResourceEnhancer.enhancedShardingSQL 10 | 11 | 12 | ### 测试用例 13 | 14 | 跑测试用例之前先建库建表结构; 15 | 理论上是8个库,512张表,每个库64张表. 16 | 17 | 如果仅仅是跑测试用例,执行下面的sql就可以跑通: 18 | 19 | create database trade0000; 20 | create database trade0001; 21 | create database trade0002; 22 | create database trade0003; 23 | create database trade0004; 24 | create database trade0005; 25 | create database trade0006; 26 | create database trade0007; 27 | create database trade; 28 | use trade0001; 29 | 30 | CREATE TABLE `TradeOrder0064` ( 31 | `orderId` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '订单ID', 32 | `buyerUserId` bigint(20) unsigned NOT NULL COMMENT '买家的userId', 33 | `sellerUserId` bigint(20) unsigned NOT NULL COMMENT '卖家的userId', 34 | `shipTime` int(11) unsigned DEFAULT '0' COMMENT '发货时间', 35 | PRIMARY KEY (`orderId`) 36 | ) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 COMMENT='订单信息表'; 37 | 38 | INSERT INTO `TradeOrder0064` (`orderId`, `buyerUserId`, `sellerUserId`, `shipTime`) 39 | VALUES 40 | (50000280834672, 1234567, 2345678, 12345678); 41 | -------------------------------------------------------------------------------- /tsharding-client/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.mogujie.trade 6 | tsharding-client 7 | 1.0.0 8 | tsharding client 9 | 10 | 11 | 3.2.8 12 | 1.2.2 13 | 4.0.6.RELEASE 14 | 4.10 15 | 3.19.0-GA 16 | 1.2.7 17 | 1.7.21 18 | 1.0.18 19 | 5.1.38 20 | 1.8.8 21 | 22 | 23 | 24 | 25 | org.springframework 26 | spring-jdbc 27 | ${spring.version} 28 | 29 | 30 | 31 | org.mybatis 32 | mybatis 33 | ${mybatis.version} 34 | 35 | 36 | 37 | org.mybatis 38 | mybatis-spring 39 | ${mybatis-spring.version} 40 | 41 | 42 | 43 | javax.persistence 44 | persistence-api 45 | 1.0 46 | 47 | 48 | 49 | org.springframework 50 | spring-test 51 | ${spring.version} 52 | 53 | 54 | 55 | org.springframework 56 | spring-core 57 | ${spring.version} 58 | 59 | 60 | 61 | org.springframework 62 | spring-context 63 | ${spring.version} 64 | provided 65 | 66 | 67 | 68 | org.javassist 69 | javassist 70 | ${javassist.version} 71 | 72 | 73 | 74 | junit 75 | junit 76 | ${junit.version} 77 | 78 | 79 | 80 | com.alibaba 81 | fastjson 82 | ${fastjson.version} 83 | 84 | 85 | 86 | org.slf4j 87 | slf4j-api 88 | ${slf4j-api.version} 89 | 90 | 91 | com.alibaba 92 | druid 93 | ${druid.version} 94 | 95 | 96 | 97 | mysql 98 | mysql-connector-java 99 | ${mysql.version} 100 | 101 | 102 | 103 | org.aspectj 104 | aspectjweaver 105 | ${aspectj.version} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-surefire-plugin 115 | 2.17 116 | 117 | false 118 | 119 | 120 | 121 | maven-compiler-plugin 122 | 123 | 1.7 124 | 1.7 125 | UTF-8 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-surefire-plugin 131 | 132 | true 133 | 134 | 135 | 136 | maven-source-plugin 137 | 2.4 138 | 139 | 140 | package 141 | 142 | jar-no-fork 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | /** 4 | * @author by jiuru on 16/7/14. 5 | */ 6 | public class DataSourceConfig { 7 | 8 | static final int DEFAULT_MIN_POOL_SIZE = 0; 9 | static final int DEFAULT_MAX_POOL_SIZE = 10; 10 | static final int DEFAULT_INI_POOL_SIZE = 0; 11 | 12 | private String url; 13 | 14 | private String username; 15 | 16 | private String password; 17 | 18 | private int minPoolSize; 19 | 20 | private int maxPoolSize; 21 | 22 | private int initialPoolSize; 23 | 24 | public String getUrl() { 25 | return url; 26 | } 27 | 28 | public void setUrl(String url) { 29 | this.url = url; 30 | } 31 | 32 | public String getUsername() { 33 | return username; 34 | } 35 | 36 | public void setUsername(String username) { 37 | this.username = username; 38 | } 39 | 40 | public String getPassword() { 41 | return password; 42 | } 43 | 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | public int getMinPoolSize() { 49 | return minPoolSize; 50 | } 51 | 52 | public void setMinPoolSize(int minPoolSize) { 53 | this.minPoolSize = minPoolSize; 54 | } 55 | 56 | public int getMaxPoolSize() { 57 | return maxPoolSize; 58 | } 59 | 60 | public void setMaxPoolSize(int maxPoolSize) { 61 | this.maxPoolSize = maxPoolSize; 62 | } 63 | 64 | public int getInitialPoolSize() { 65 | return initialPoolSize; 66 | } 67 | 68 | public void setInitialPoolSize(int initialPoolSize) { 69 | this.initialPoolSize = initialPoolSize; 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceFactory.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import javax.sql.DataSource; 4 | import java.sql.SQLException; 5 | 6 | /** 7 | * @author by jiuru on 16/7/14. 8 | */ 9 | public interface DataSourceFactory { 10 | 11 | T getDataSource(DataSourceConfig config) throws SQLException; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceLookup.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import java.io.Closeable; 4 | import java.io.IOException; 5 | import java.util.Collections; 6 | import java.util.Map; 7 | 8 | /** 9 | * @author by jiuru on 16/7/14. 10 | */ 11 | public class DataSourceLookup implements Closeable { 12 | 13 | private final Map dataSources; 14 | 15 | public DataSourceLookup(Map dataSources) { 16 | this.dataSources = Collections.unmodifiableMap(dataSources); 17 | } 18 | 19 | /** 20 | * @param name 21 | * @return 22 | */ 23 | public ReadWriteSplittingDataSource get(String name) { 24 | return this.dataSources.get(name); 25 | } 26 | 27 | public Map getMapping() { 28 | return this.dataSources; 29 | } 30 | 31 | @Override 32 | public void close() throws IOException { 33 | for (ReadWriteSplittingDataSource dataSource : dataSources.values()) { 34 | dataSource.close(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceRouting.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author by jiuru on 16/7/14. 10 | */ 11 | @Target(ElementType.TYPE) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface DataSourceRouting { 14 | /** 15 | * 静态绑定该Mapper对应的数据源 16 | * 17 | * @return 绑定的数据源的名称 18 | */ 19 | String value() default ""; 20 | 21 | /** 22 | * 指定运行时路由处理的Handler,动态指定对应的数据源。注意:此方式不能支持事务! 23 | * 24 | * @return 25 | */ 26 | Class handler() default EmptyDataSourceRoutingHandler.class; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceRoutingException.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | /** 4 | * @author by jiuru on 16/7/14. 5 | */ 6 | public class DataSourceRoutingException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 8678631953659495594L; 9 | 10 | public DataSourceRoutingException() { 11 | } 12 | 13 | public DataSourceRoutingException(String message, Throwable cause, boolean enableSuppression, 14 | boolean writableStackTrace) { 15 | super(message, cause, enableSuppression, writableStackTrace); 16 | } 17 | 18 | public DataSourceRoutingException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | 22 | public DataSourceRoutingException(String message) { 23 | super(message); 24 | } 25 | 26 | public DataSourceRoutingException(Throwable cause) { 27 | super(cause); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceRoutingHandler.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | 6 | /** 7 | * @author by jiuru on 16/7/14. 8 | */ 9 | public interface DataSourceRoutingHandler { 10 | 11 | /** 12 | * 根据方法及参数动态路由 13 | * 14 | * @param method 15 | * 方法 16 | * @param args 17 | * 参数列表 18 | * @return 数据源的名称 19 | */ 20 | String dynamicRoute(Method method, Object[] args); 21 | 22 | /** 23 | * 返回所有可能数据库名称。注:此方法可能在将来版本中废弃,无须做此指定。 24 | * 25 | * @return 所有可能返回的数据库名称 26 | */ 27 | Collection values(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceScanner.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.MutablePropertyValues; 7 | import org.springframework.beans.factory.BeanCreationException; 8 | import org.springframework.beans.factory.BeanInitializationException; 9 | import org.springframework.beans.factory.FactoryBean; 10 | import org.springframework.beans.factory.annotation.Qualifier; 11 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 12 | import org.springframework.beans.factory.config.ConstructorArgumentValues; 13 | import org.springframework.beans.factory.support.AutowireCandidateQualifier; 14 | import org.springframework.beans.factory.support.BeanDefinitionRegistry; 15 | import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; 16 | import org.springframework.beans.factory.support.GenericBeanDefinition; 17 | import org.springframework.context.ApplicationContext; 18 | import org.springframework.context.ApplicationContextAware; 19 | import org.springframework.transaction.PlatformTransactionManager; 20 | import org.springframework.transaction.TransactionDefinition; 21 | import org.springframework.transaction.TransactionException; 22 | import org.springframework.transaction.TransactionStatus; 23 | import org.springframework.util.Assert; 24 | 25 | import javax.sql.DataSource; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.sql.SQLException; 29 | import java.util.EnumMap; 30 | import java.util.HashMap; 31 | import java.util.Map; 32 | import java.util.Properties; 33 | 34 | /** 35 | * @author by jiuru on 16/7/14. 36 | */ 37 | public class DataSourceScanner implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware { 38 | 39 | private static final String PROPERTY_FILE_NAME = "jdbc.properties"; 40 | 41 | private static final String KEY_SEPARATOR = "."; 42 | 43 | private final Logger logger = LoggerFactory.getLogger(getClass()); 44 | 45 | private DataSourceFactory dataSourceFactory; 46 | 47 | private ApplicationContext applicationContext; 48 | 49 | @Override 50 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 51 | // do nothing 52 | } 53 | 54 | @Override 55 | public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 56 | 57 | final Map dataSources = new HashMap<>(); 58 | 59 | InputStream in = this.getClass().getClassLoader().getResourceAsStream(PROPERTY_FILE_NAME); 60 | if (in != null) { 61 | Properties properties = new Properties(); 62 | try { 63 | properties.load(in); 64 | } catch (IOException e) { 65 | throw new BeanInitializationException("read property file error!", e); 66 | } 67 | try { 68 | Map> dataSourcesMapping = this.getDataSources(properties); 69 | this.registerDataSources(registry, dataSourcesMapping); 70 | int transcactionManagerCount = 0; 71 | String transactionManagerBeanName = null; 72 | for (Map.Entry> entry : dataSourcesMapping.entrySet()) { 73 | final String name = entry.getKey(); 74 | 75 | final DataSource masterDataSource = entry.getValue().get(DataSourceType.master); 76 | 77 | final ReadWriteSplittingDataSource readWriteSplittingDataSource = new ReadWriteSplittingDataSource( 78 | entry.getKey(), entry.getValue().get(DataSourceType.master), entry.getValue().get( 79 | DataSourceType.slave)); 80 | logger.info("init dataSource {}", readWriteSplittingDataSource); 81 | dataSources.put(name, readWriteSplittingDataSource); 82 | 83 | // 若无可写的数据源则跳过创建事务管理器 84 | if (masterDataSource == null) { 85 | continue; 86 | } 87 | 88 | transactionManagerBeanName = name + "TransactionManager"; 89 | 90 | GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 91 | beanDefinition.setBeanClass(RoutingDataSourceTransactionManager.class); 92 | 93 | MutablePropertyValues propertyValues = new MutablePropertyValues(); 94 | propertyValues.add("dataSource", readWriteSplittingDataSource); 95 | propertyValues.add("name", name); 96 | beanDefinition.setPropertyValues(propertyValues); 97 | AutowireCandidateQualifier qualifier = new AutowireCandidateQualifier(Qualifier.class); 98 | qualifier.setAttribute(AutowireCandidateQualifier.VALUE_KEY, name); 99 | beanDefinition.addQualifier(qualifier); 100 | registry.registerBeanDefinition(transactionManagerBeanName, beanDefinition); 101 | 102 | PlatformTransactionManager transactionManager = this.applicationContext.getBean( 103 | transactionManagerBeanName, PlatformTransactionManager.class); 104 | Assert.notNull(transactionManager, "register BeanDefinition of " + transactionManagerBeanName 105 | + " error!"); 106 | transcactionManagerCount++; 107 | } 108 | 109 | // 兼容只有一个或无TransactionManager的情况 110 | if (transcactionManagerCount == 1) {// 若只有一个则添加别名,兼容默认情况 111 | registry.registerAlias(transactionManagerBeanName, "transcationManager"); 112 | } else if (transcactionManagerCount == 0) { 113 | // add an empty transcationManager 114 | GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); 115 | beanDefinition.setBeanClass(EmptyTransactionManager.class); 116 | 117 | registry.registerBeanDefinition("transactionManager", beanDefinition); 118 | } 119 | } catch (SQLException e) { 120 | throw new BeanCreationException("initial dataSources error!", e); 121 | } 122 | } 123 | 124 | // register dataSourceLookup 125 | GenericBeanDefinition dataSourceLookupBeanDefinition = new GenericBeanDefinition(); 126 | dataSourceLookupBeanDefinition.setBeanClass(DataSourceLookup.class); 127 | ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); 128 | constructorArgumentValues.addIndexedArgumentValue(0, dataSources); 129 | dataSourceLookupBeanDefinition.setConstructorArgumentValues(constructorArgumentValues); 130 | registry.registerBeanDefinition("dataSourceLookup", dataSourceLookupBeanDefinition); 131 | } 132 | 133 | /** 134 | * 根据Properties配置解析得到数据源 135 | * 136 | * @param properties 137 | * @return 138 | * @throws SQLException 139 | */ 140 | private Map> getDataSources(Properties properties) throws SQLException { 141 | Map> dataSourcesMapping = new HashMap<>(2); 142 | for (Map.Entry entry : properties.entrySet()) { 143 | String[] parts = entry.getKey().toString().trim().split("\\" + KEY_SEPARATOR); 144 | if (parts.length == 3) { 145 | String name = parts[0]; 146 | if (!dataSourcesMapping.containsKey(name)) { 147 | for (DataSourceType dataSourceType : DataSourceType.values()) { 148 | 149 | DataSource ds = this.dataSourceFactory.getDataSource(this.parseDataSourceConfig(name, 150 | dataSourceType, properties)); 151 | Map map = dataSourcesMapping.get(name); 152 | if (map == null) { 153 | map = new EnumMap(DataSourceType.class); 154 | dataSourcesMapping.put(name, map); 155 | } 156 | DataSource preValue = map.put(dataSourceType, ds); 157 | if (preValue != null) { 158 | throw new IllegalArgumentException("dupilicated DataSource of" + name + " " 159 | + dataSourceType); 160 | } 161 | } 162 | 163 | } 164 | } else { 165 | // It's illegal, ignore. 166 | } 167 | } 168 | return dataSourcesMapping; 169 | } 170 | 171 | private DataSourceConfig parseDataSourceConfig(String name, DataSourceType dataSourceType, Properties properties) { 172 | String keyPrefix = name + KEY_SEPARATOR + dataSourceType + KEY_SEPARATOR; 173 | 174 | DataSourceConfig dataSourceConfig = new DataSourceConfig(); 175 | String url = properties.getProperty(keyPrefix + "url"); 176 | Assert.hasText(url, keyPrefix + "url is empty!"); 177 | dataSourceConfig.setUrl(url); 178 | 179 | String username = properties.getProperty(keyPrefix + "username"); 180 | Assert.hasText(username, keyPrefix + "username is empty!"); 181 | dataSourceConfig.setUsername(username); 182 | 183 | String password = properties.getProperty(keyPrefix + "password"); 184 | Assert.hasText(password, keyPrefix + "password is empty!"); 185 | dataSourceConfig.setPassword(password); 186 | 187 | String initialPoolSizeStr = properties.getProperty(keyPrefix + "initialPoolSize"); 188 | int initialPoolSize = initialPoolSizeStr == null ? DataSourceConfig.DEFAULT_INI_POOL_SIZE : Integer 189 | .parseInt(initialPoolSizeStr); 190 | dataSourceConfig.setInitialPoolSize(initialPoolSize); 191 | 192 | String minPoolSizeStr = properties.getProperty(keyPrefix + "minPoolSize"); 193 | int minPoolSize = minPoolSizeStr == null ? DataSourceConfig.DEFAULT_MIN_POOL_SIZE : Integer 194 | .parseInt(minPoolSizeStr); 195 | dataSourceConfig.setMinPoolSize(minPoolSize); 196 | 197 | String maxPoolSizeStr = properties.getProperty(keyPrefix + "maxPoolSize"); 198 | int maxPoolSize = maxPoolSizeStr == null ? DataSourceConfig.DEFAULT_MAX_POOL_SIZE : Integer 199 | .parseInt(maxPoolSizeStr); 200 | dataSourceConfig.setMaxPoolSize(maxPoolSize); 201 | 202 | return dataSourceConfig; 203 | } 204 | 205 | /** 206 | * 将数据源注入到Spring中 207 | * 208 | * @param registry 209 | * @param dataSourcesMapping 210 | */ 211 | private void registerDataSources(BeanDefinitionRegistry registry, 212 | Map> dataSourcesMapping) { 213 | 214 | for (Map.Entry> entry : dataSourcesMapping.entrySet()) { 215 | final String name = entry.getKey(); 216 | for (Map.Entry subEntry : entry.getValue().entrySet()) { 217 | GenericBeanDefinition dataSourceBeanDefinition = new GenericBeanDefinition(); 218 | dataSourceBeanDefinition.setBeanClass(DataSourceFactoryBean.class); 219 | ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues(); 220 | constructorArgumentValues.addIndexedArgumentValue(0, subEntry.getValue()); 221 | dataSourceBeanDefinition.setConstructorArgumentValues(constructorArgumentValues); 222 | String beanName = name + Character.toUpperCase(subEntry.getKey().name().charAt(0)) 223 | + subEntry.getKey().name().substring(1) + "DataSource"; 224 | registry.registerBeanDefinition(beanName, dataSourceBeanDefinition); 225 | } 226 | } 227 | } 228 | 229 | // --------------------Setters--------------- 230 | 231 | public void setDataSourceFactory(DataSourceFactory dataSourceFactory) { 232 | this.dataSourceFactory = dataSourceFactory; 233 | } 234 | 235 | @Override 236 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 237 | this.applicationContext = applicationContext; 238 | } 239 | 240 | public static class EmptyTransactionManager implements PlatformTransactionManager { 241 | 242 | @Override 243 | public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { 244 | throw new UnsupportedOperationException(); 245 | } 246 | 247 | @Override 248 | public void commit(TransactionStatus status) throws TransactionException { 249 | throw new UnsupportedOperationException(); 250 | } 251 | 252 | @Override 253 | public void rollback(TransactionStatus status) throws TransactionException { 254 | throw new UnsupportedOperationException(); 255 | } 256 | 257 | } 258 | 259 | public static class DataSourceFactoryBean implements FactoryBean { 260 | 261 | private final DataSource dataSource; 262 | 263 | public DataSourceFactoryBean(DataSource dataSource) { 264 | Assert.notNull(dataSource); 265 | this.dataSource = dataSource; 266 | } 267 | 268 | @Override 269 | public DataSource getObject() throws Exception { 270 | return this.dataSource; 271 | } 272 | 273 | @Override 274 | public Class getObjectType() { 275 | return this.dataSource.getClass(); 276 | } 277 | 278 | @Override 279 | public boolean isSingleton() { 280 | return true; 281 | } 282 | 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DataSourceType.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | /** 4 | * @author by jiuru on 16/7/14. 5 | */ 6 | public enum DataSourceType { 7 | master, slave 8 | } 9 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/DruidDataSourceFactory.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import com.alibaba.druid.filter.Filter; 4 | import com.alibaba.druid.pool.DruidDataSource; 5 | 6 | import java.sql.SQLException; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * @author by jiuru on 16/7/14. 12 | */ 13 | public class DruidDataSourceFactory implements DataSourceFactory { 14 | 15 | private List filters = Collections.emptyList(); 16 | 17 | @Override 18 | public DruidDataSource getDataSource(DataSourceConfig config) throws SQLException { 19 | DruidDataSource dataSource = new DruidDataSource(); 20 | dataSource.setUrl(config.getUrl()); 21 | dataSource.setUsername(config.getUsername()); 22 | dataSource.setPassword(config.getPassword()); 23 | // pool config 24 | dataSource.setInitialSize(config.getInitialPoolSize()); 25 | dataSource.setMinIdle(config.getMinPoolSize()); 26 | dataSource.setMaxActive(config.getMaxPoolSize()); 27 | 28 | // common config 29 | dataSource.setFilters("stat"); 30 | dataSource.setMaxWait(1000); 31 | dataSource.setValidationQuery("SELECT 'x'"); 32 | dataSource.setTestWhileIdle(true); 33 | dataSource.setTestOnBorrow(false); 34 | dataSource.setTestOnReturn(false); 35 | dataSource.setTimeBetweenEvictionRunsMillis(60000); 36 | dataSource.setMinEvictableIdleTimeMillis(120000); 37 | dataSource.setTimeBetweenLogStatsMillis(0); 38 | 39 | dataSource.setProxyFilters(filters); 40 | 41 | dataSource.init(); 42 | return dataSource; 43 | } 44 | 45 | public void setFilters(List filters) { 46 | this.filters = filters; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/EmptyDataSourceRoutingHandler.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Collection; 5 | 6 | /** 7 | * @author by jiuru on 16/7/14. 8 | */ 9 | public class EmptyDataSourceRoutingHandler implements DataSourceRoutingHandler { 10 | 11 | @Override 12 | public String dynamicRoute(Method method, Object[] args) { 13 | // this will never be called. 14 | throw new InternalError(); 15 | } 16 | 17 | @Override 18 | public Collection values() { 19 | // this will never be called. 20 | throw new InternalError(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/ReadWriteSplitting.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @author by jiuru on 16/7/14. 10 | */ 11 | @Target(ElementType.METHOD) 12 | @Retention(RetentionPolicy.RUNTIME) 13 | public @interface ReadWriteSplitting { 14 | DataSourceType value() default DataSourceType.master; 15 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/ReadWriteSplittingAdvice.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.Signature; 5 | import org.aspectj.lang.annotation.Around; 6 | import org.aspectj.lang.reflect.MethodSignature; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.Map; 13 | import java.util.concurrent.ConcurrentHashMap; 14 | 15 | /** 16 | * @author by jiuru on 16/7/14. 17 | */ 18 | public class ReadWriteSplittingAdvice { 19 | 20 | private final Logger logger = LoggerFactory.getLogger(getClass()); 21 | 22 | private final Map cache = new ConcurrentHashMap<>(); 23 | 24 | @Around("@annotation(com.mogujie.trade.db.ReadWriteSplitting)") 25 | public Object intercept(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { 26 | Signature signature = proceedingJoinPoint.getSignature(); 27 | DataSourceType dataSourceType = null; 28 | if (signature instanceof MethodSignature) { 29 | // 若已经在事务中 则不做处理 30 | if (RoutingDataSourceTransactionContext.getCurTransactionDataSource() != null) { 31 | return proceedingJoinPoint.proceed(); 32 | } 33 | 34 | // 若已经设置为Master 则忽略 35 | if (ReadWriteSplittingContext.isMaster()) { 36 | return proceedingJoinPoint.proceed(); 37 | } 38 | MethodSignature methodSignature = (MethodSignature) signature; 39 | Method method = methodSignature.getMethod(); 40 | dataSourceType = this.getDataSourceType(method); 41 | } else { 42 | // this may not happend. 43 | throw new ReadWriteSplittingException("ReadWriteSplitting annotation should only used on method. "); 44 | } 45 | ReadWriteSplittingContext.set(dataSourceType); 46 | logger.debug("{} {} using dataSourceOf {} ", proceedingJoinPoint.getTarget(), 47 | proceedingJoinPoint.getSignature(), dataSourceType); 48 | try { 49 | return proceedingJoinPoint.proceed(); 50 | } finally { 51 | ReadWriteSplittingContext.clear(); 52 | logger.debug("{} release dataSource of {}", proceedingJoinPoint.getTarget(), dataSourceType); 53 | } 54 | } 55 | 56 | /** 57 | * 获取方法对应的数据源类型 58 | * 59 | * @param method 60 | * @return 61 | */ 62 | private DataSourceType getDataSourceType(Method method) { 63 | DataSourceType dataSourceType = this.cache.get(method); 64 | if (dataSourceType == null) { 65 | synchronized (method) { 66 | dataSourceType = this.cache.get(method); 67 | if (dataSourceType == null) { 68 | dataSourceType = this.determineDataSourceType(method); 69 | this.cache.put(method, dataSourceType); 70 | } 71 | } 72 | } 73 | return dataSourceType; 74 | } 75 | 76 | private DataSourceType determineDataSourceType(Method method) { 77 | DataSourceType dataSourceType = DataSourceType.slave; 78 | 79 | ReadWriteSplitting readWriteSplitting = method.getAnnotation(ReadWriteSplitting.class); 80 | if (readWriteSplitting != null) { 81 | dataSourceType = readWriteSplitting.value(); 82 | dataSourceType = dataSourceType == null ? DataSourceType.master : dataSourceType; 83 | } else { 84 | // this will not happen 85 | throw new InternalError("method must have the annotation of ReadWriteSplitting! "); 86 | } 87 | 88 | Transactional transcational = method.getAnnotation(Transactional.class); 89 | if (transcational != null) { 90 | throw new ReadWriteSplittingException("ReadWriteSplitting and Transactional can't be used on method " 91 | + method + " at the same time!"); 92 | } 93 | return dataSourceType; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/ReadWriteSplittingContext.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | /** 4 | * @author by jiuru on 16/7/14. 5 | */ 6 | public class ReadWriteSplittingContext { 7 | 8 | private static final ThreadLocal curDataSourceType = new ThreadLocal(); 9 | 10 | public static void set(DataSourceType dataSourceType) { 11 | curDataSourceType.set(dataSourceType); 12 | } 13 | 14 | public static void setMaster() { 15 | curDataSourceType.set(DataSourceType.master); 16 | } 17 | 18 | public static void clear() { 19 | curDataSourceType.remove(); 20 | } 21 | 22 | public static boolean isMaster() { 23 | return DataSourceType.master == curDataSourceType.get(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/ReadWriteSplittingDataSource.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import org.springframework.jdbc.datasource.AbstractDataSource; 4 | import org.springframework.util.Assert; 5 | 6 | import javax.sql.DataSource; 7 | import java.io.Closeable; 8 | import java.io.IOException; 9 | import java.sql.Connection; 10 | import java.sql.SQLException; 11 | 12 | /** 13 | * @author by jiuru on 16/7/14. 14 | */ 15 | public class ReadWriteSplittingDataSource extends AbstractDataSource implements DataSource, Closeable { 16 | 17 | private final String name; 18 | 19 | private final DataSource masterDataSource; 20 | 21 | private final DataSource slaveDataSource; 22 | 23 | public ReadWriteSplittingDataSource(String name, DataSource masterDataSource, DataSource slaveDataSource) { 24 | this.name = name; 25 | this.masterDataSource = masterDataSource; 26 | this.slaveDataSource = slaveDataSource; 27 | Assert.isTrue(masterDataSource != slaveDataSource || masterDataSource != null, 28 | "masterDataSource and slaveDataSource can't be both null!"); 29 | } 30 | 31 | @Override 32 | public Connection getConnection() throws SQLException { 33 | return this.determineTargetDataSource().getConnection(); 34 | } 35 | 36 | public DataSource getSlaveDataSource() { 37 | return slaveDataSource; 38 | } 39 | 40 | private DataSource determineTargetDataSource() { 41 | if (slaveDataSource == null) { 42 | return masterDataSource; 43 | } 44 | 45 | if (this.isInTransaction()) { 46 | return masterDataSource; 47 | } 48 | 49 | return ReadWriteSplittingContext.isMaster() ? masterDataSource : slaveDataSource; 50 | } 51 | 52 | private boolean isInTransaction() { 53 | return RoutingDataSourceTransactionContext.getCurTransactionDataSource() != null; 54 | } 55 | 56 | @Override 57 | public Connection getConnection(String username, String password) throws SQLException { 58 | return this.determineTargetDataSource().getConnection(username, password); 59 | } 60 | 61 | public String getName() { 62 | return this.name; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | StringBuilder builder = new StringBuilder(); 68 | builder.append("ReadWriteSplittingDataSource ["); 69 | if (name != null) { 70 | builder.append("name=").append(name).append(", "); 71 | } 72 | if (masterDataSource != null) { 73 | builder.append("masterDataSource=").append(masterDataSource).append(", "); 74 | } 75 | if (slaveDataSource != null) { 76 | builder.append("slaveDataSource=").append(slaveDataSource); 77 | } 78 | builder.append("]"); 79 | return builder.toString(); 80 | } 81 | 82 | @Override 83 | public void close() throws IOException { 84 | if (this.masterDataSource != null) { 85 | if (masterDataSource instanceof AutoCloseable) { 86 | try { 87 | ((AutoCloseable) masterDataSource).close(); 88 | } catch (Exception ignore) { 89 | } 90 | } 91 | } 92 | 93 | if (this.slaveDataSource != null) { 94 | if (slaveDataSource instanceof AutoCloseable) { 95 | try { 96 | ((AutoCloseable) slaveDataSource).close(); 97 | } catch (Exception ignore) { 98 | } 99 | } 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/ReadWriteSplittingException.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | /** 4 | * @author by jiuru on 16/7/14. 5 | */ 6 | public class ReadWriteSplittingException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 3118672534738018794L; 9 | 10 | public ReadWriteSplittingException() { 11 | } 12 | 13 | public ReadWriteSplittingException(String message) { 14 | super(message); 15 | } 16 | 17 | public ReadWriteSplittingException(Throwable cause) { 18 | super(cause); 19 | } 20 | 21 | public ReadWriteSplittingException(String message, Throwable cause) { 22 | super(message, cause); 23 | } 24 | 25 | public ReadWriteSplittingException(String message, Throwable cause, boolean enableSuppression, 26 | boolean writableStackTrace) { 27 | super(message, cause, enableSuppression, writableStackTrace); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/RoutingDataSourceTransactionContext.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import javax.sql.DataSource; 4 | 5 | /** 6 | * @author by jiuru on 16/7/14. 7 | */ 8 | public class RoutingDataSourceTransactionContext { 9 | 10 | private static final ThreadLocal curDataSource = new ThreadLocal(); 11 | 12 | public static DataSource getCurTransactionDataSource() { 13 | return curDataSource.get(); 14 | } 15 | 16 | public static void setCurDataSource(DataSource dataSource) { 17 | curDataSource.set(dataSource); 18 | } 19 | 20 | public static void clear() { 21 | curDataSource.remove(); 22 | } 23 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/db/RoutingDataSourceTransactionManager.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.db; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.jdbc.datasource.DataSourceTransactionManager; 6 | 7 | import javax.sql.DataSource; 8 | 9 | /** 10 | * @author by jiuru on 16/7/14. 11 | */ 12 | public class RoutingDataSourceTransactionManager extends DataSourceTransactionManager { 13 | 14 | private final Logger logger = LoggerFactory.getLogger(getClass()); 15 | 16 | private String name; 17 | /** 18 | * 19 | */ 20 | private static final long serialVersionUID = 2532966497797909623L; 21 | 22 | public RoutingDataSourceTransactionManager() { 23 | } 24 | 25 | public RoutingDataSourceTransactionManager(DataSource dataSource) { 26 | super(dataSource); 27 | } 28 | 29 | @Override 30 | protected Object doGetTransaction() { 31 | RoutingDataSourceTransactionContext.setCurDataSource(getDataSource()); 32 | logger.debug("Transaction of {} begin", this.name); 33 | return super.doGetTransaction(); 34 | } 35 | 36 | @Override 37 | protected void doCleanupAfterCompletion(Object transaction) { 38 | super.doCleanupAfterCompletion(transaction); 39 | RoutingDataSourceTransactionContext.clear(); 40 | logger.debug("Transaction of {} ends", this.name); 41 | } 42 | 43 | public String getName() { 44 | return name; 45 | } 46 | 47 | public void setName(String name) { 48 | this.name = name; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/ShardingExtensionMethod.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.annotation; 2 | 3 | import com.mogujie.trade.tsharding.route.orm.MapperResourceEnhancer; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * 需要sharding扩展的dao层方法 12 | * @auther qigong on 6/4/15 11:02 AM. 13 | */ 14 | @Target(ElementType.METHOD) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface ShardingExtensionMethod { 17 | Class type() default MapperResourceEnhancer.class; 18 | 19 | String method() default "enhancedShardingSQL"; 20 | } 21 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingBuyerPara.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.annotation.parameter; 2 | 3 | /** 4 | * sharding参数注解 5 | * @auther qigong on 5/28/15 1:00 PM. 6 | */ 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | /** 17 | * sharding计算用的Para buyerUserId 18 | */ 19 | public @interface ShardingBuyerPara { 20 | String value() default "buyerUserId"; 21 | } 22 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingOrderPara.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.annotation.parameter; 2 | 3 | /** 4 | * sharding参数注解 5 | * @auther qigong on 5/28/15 1:00 PM. 6 | */ 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | /** 17 | * sharding计算用的Para orderId或parentOrderId或itemOrderId或shopOrderId或payOrderId 18 | */ 19 | public @interface ShardingOrderPara { 20 | String value() default "orderId"; 21 | } 22 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/annotation/parameter/ShardingSellerPara.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.annotation.parameter; 2 | 3 | /** 4 | * sharding参数注解 5 | * @auther qigong on 5/28/15 1:00 PM. 6 | */ 7 | 8 | import java.lang.annotation.ElementType; 9 | import java.lang.annotation.Retention; 10 | import java.lang.annotation.RetentionPolicy; 11 | import java.lang.annotation.Target; 12 | 13 | 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | /** 17 | * sharding计算用的Para sellerUserId 18 | */ 19 | public @interface ShardingSellerPara { 20 | String value() default "sellerUserId"; 21 | } 22 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/client/ShardingCaculator.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.client; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * 分片计算器 10 | * 11 | * @auther qigong on 5/28/15 1:06 PM. 12 | */ 13 | public class ShardingCaculator { 14 | 15 | /** 16 | * 根据分片参数值计算分表名 17 | * 18 | * @param shardingPara 19 | * @return 分表名0xxx 20 | */ 21 | public static String caculateTableName(Long shardingPara) { 22 | if (shardingPara >= 0) { 23 | return "TradeOrder" + getNumberWithZeroSuffix((shardingPara % 10000) % 512); 24 | } 25 | return null; 26 | } 27 | 28 | /** 29 | * 根据分片参数值计算分表名 30 | * 31 | * @param shardingPara 32 | * @return 分表名0xxx 33 | */ 34 | public static Integer caculateTableIndex(Long shardingPara) { 35 | if (shardingPara >= 0) { 36 | return new Long(shardingPara % 10000 % 512).intValue(); 37 | } 38 | return null; 39 | } 40 | 41 | 42 | /** 43 | * 根据分片参数值计算分库名(逻辑库) 44 | * 45 | * @param shardingPara 46 | * @return 分库名000x 47 | */ 48 | public static String caculateSchemaName(String fieldName, Long shardingPara) { 49 | if (shardingPara >= 0) { 50 | 51 | if ("sellerUserId".equals(fieldName)) { 52 | return "sellertrade" + getNumberWithZeroSuffix(((shardingPara % 10000) % 512) / 64); 53 | } else { 54 | return "trade" + getNumberWithZeroSuffix(((shardingPara % 10000) % 512) / 64); 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | /** 61 | * 根据分片参数值计算数据源名 62 | * 63 | * @param shardingPara 64 | * @return DatasourceName 见数据源配置文件 65 | */ 66 | public static String caculateDatasourceName(String fieldName, Long shardingPara) { 67 | if (shardingPara >= 0) { 68 | if ("sellerUserId".equals(fieldName)) { 69 | return "seller_ds_" + ((shardingPara % 10000) % 512) / 256; 70 | } else { 71 | return "buyer_ds_" + ((shardingPara % 10000) % 512) / 256; 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | public static String getNumberWithZeroSuffix(long number) { 78 | if (number >= 100) { 79 | return "0" + number; 80 | } else if (number >= 10) { 81 | return "00" + number; 82 | } else if (number >= 0) { 83 | return "000" + number; 84 | } 85 | return null; 86 | } 87 | 88 | /** 89 | * 按订单号批量查询:跨表查,先按分表做分组 90 | * 91 | * @param listShopOrderIds 92 | * @return tableNo -> orderIds 93 | */ 94 | public static Map> getTableNoAndOrderIdsMap(List listShopOrderIds) { 95 | 96 | HashMap> shopOrderIdsMap = new HashMap(); 97 | if (listShopOrderIds == null || listShopOrderIds.size() == 0) { 98 | return shopOrderIdsMap; 99 | } 100 | for (Long shopOrderId : listShopOrderIds) { 101 | Integer tableNo = ShardingCaculator.caculateTableIndex(shopOrderId); 102 | List orderIds = shopOrderIdsMap.get(tableNo); 103 | if (orderIds == null) { 104 | orderIds = new ArrayList<>(); 105 | } 106 | orderIds.add(shopOrderId); 107 | shopOrderIdsMap.put(tableNo, orderIds); 108 | } 109 | return shopOrderIdsMap; 110 | } 111 | 112 | public static void main(String args[]) { 113 | System.out.println(caculateTableName(6000004386417L)); 114 | System.out.println(caculateSchemaName("buyerUserId", 6000004386417L)); 115 | 116 | System.out.println(caculateTableName(35586213L)); 117 | System.out.println(caculateSchemaName("sellerUserId", 35586213L)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandler.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route; 2 | 3 | 4 | import com.mogujie.trade.db.DataSourceRoutingHandler; 5 | 6 | import java.lang.reflect.Method; 7 | import java.util.Collection; 8 | 9 | 10 | /** 11 | * @author qigong 06/05/2015 12 | */ 13 | public class TShardingRoutingHandler implements DataSourceRoutingHandler { 14 | 15 | @Override 16 | public String dynamicRoute(Method method, Object[] args) { 17 | //route逻辑见TShardingRoutingInvokeFactory 18 | return "testschema"; 19 | } 20 | 21 | @Override 22 | public Collection values() { 23 | //暂未使用 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/TShardingRoutingHandlerForPressureTest.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route; 2 | 3 | 4 | import com.mogujie.trade.db.DataSourceRoutingHandler; 5 | import java.lang.reflect.Method; 6 | import java.util.Collection; 7 | 8 | 9 | /** 10 | * @author qigong 06/05/2015 11 | */ 12 | public class TShardingRoutingHandlerForPressureTest implements DataSourceRoutingHandler { 13 | 14 | @Override 15 | public String dynamicRoute(Method method, Object[] args) { 16 | //route逻辑见TShardingRoutingInvokeFactory 17 | return "testschema"; 18 | } 19 | 20 | @Override 21 | public Collection values() { 22 | //暂未使用 23 | return null; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperAnnotationEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import javassist.CtMethod; 4 | import javassist.bytecode.ConstPool; 5 | import javassist.bytecode.ParameterAnnotationsAttribute; 6 | import javassist.bytecode.annotation.Annotation; 7 | import javassist.bytecode.annotation.MemberValue; 8 | import javassist.bytecode.annotation.StringMemberValue; 9 | 10 | import java.lang.reflect.Method; 11 | 12 | /** 13 | * @author qigong on 15/9/8 下午9:13. 14 | */ 15 | public class MapperAnnotationEnhancer { 16 | 17 | public static ParameterAnnotationsAttribute duplicateParameterAnnotationsAttribute(ConstPool cp, Method method) { 18 | ParameterAnnotationsAttribute oldAns = new ParameterAnnotationsAttribute(cp, ParameterAnnotationsAttribute.visibleTag); 19 | javassist.bytecode.annotation.Annotation[][] anAr = new javassist.bytecode.annotation.Annotation[method.getParameterAnnotations().length][]; 20 | for (int i = 0; i < anAr.length; ++i) { 21 | anAr[i] = new javassist.bytecode.annotation.Annotation[method.getParameterAnnotations()[i].length]; 22 | for (int j = 0; j < anAr[i].length; ++j) { 23 | anAr[i][j] = createJavassistAnnotation(method.getParameterAnnotations()[i][j], cp); 24 | } 25 | } 26 | oldAns.setAnnotations(anAr); 27 | return oldAns; 28 | } 29 | 30 | public static javassist.bytecode.annotation.Annotation createJavassistAnnotation(java.lang.annotation.Annotation annotation, ConstPool cp) { 31 | try { 32 | javassist.bytecode.annotation.Annotation newAnnotation = new Annotation(annotation.annotationType().getName(), cp); 33 | for (Method m : annotation.annotationType().getDeclaredMethods()) { 34 | Object val = m.invoke(annotation); 35 | newAnnotation.addMemberValue(m.getName(), createMemberValue(m.getReturnType(), val, cp)); 36 | } 37 | return newAnnotation; 38 | } catch (Exception e) { 39 | throw new RuntimeException("createJavassistAnnotation error!", e); 40 | } 41 | } 42 | 43 | private static MemberValue createMemberValue(Class type, Object val, ConstPool cp) { 44 | if (type == String.class) { 45 | return new StringMemberValue((String) val, cp); 46 | } else { 47 | throw new RuntimeException("Only support string param value! Invalid param value type:" + type + " and value: " + val); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import com.mogujie.trade.tsharding.client.ShardingCaculator; 4 | import javassist.ClassPool; 5 | import javassist.CtClass; 6 | import javassist.CtMethod; 7 | import javassist.bytecode.ClassFile; 8 | import javassist.bytecode.ConstPool; 9 | import org.apache.ibatis.mapping.MappedStatement; 10 | import org.apache.ibatis.mapping.ResultMap; 11 | import org.apache.ibatis.mapping.SqlSource; 12 | import org.apache.ibatis.reflection.MetaObject; 13 | import org.apache.ibatis.reflection.factory.DefaultObjectFactory; 14 | import org.apache.ibatis.reflection.factory.ObjectFactory; 15 | import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory; 16 | import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory; 17 | import org.apache.ibatis.session.Configuration; 18 | 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | /** 26 | * 通用Mapper增强基类,扩展Mapper sql时需要继承该类 27 | * 28 | * @author qigong on 5/1/15 29 | */ 30 | public abstract class MapperEnhancer { 31 | 32 | private static ClassPool pool = ClassPool.getDefault(); 33 | 34 | private Map methodMap = new HashMap(); 35 | private Class mapperClass; 36 | 37 | public MapperEnhancer(Class mapperClass) { 38 | this.mapperClass = mapperClass; 39 | } 40 | 41 | /** 42 | * 代码增加方法标记 43 | * 44 | * @param record 45 | */ 46 | public String enhancedShardingSQL(Object record) { 47 | return "enhancedShardingSQL"; 48 | } 49 | 50 | public MapperEnhancer() { 51 | super(); 52 | } 53 | 54 | /** 55 | * 对mapper进行增强,生成新的mapper,并主动加载新mapper类到classloader 56 | * 57 | * @param mapperClassName 58 | */ 59 | public static void enhanceMapperClass(String mapperClassName) throws Exception { 60 | 61 | Class originClass = Class.forName(mapperClassName); 62 | Method[] originMethods = originClass.getDeclaredMethods(); 63 | 64 | CtClass cc = pool.get(mapperClassName); 65 | 66 | for (CtMethod ctMethod : cc.getDeclaredMethods()) { 67 | CtClass enhanceClass = pool.makeInterface(mapperClassName + "Sharding" + ctMethod.getName()); 68 | for (long i = 0L; i < 512; i++) { 69 | CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName() + ShardingCaculator.getNumberWithZeroSuffix(i), ctMethod.getParameterTypes(), enhanceClass); 70 | 71 | Method method = getOriginMethod(newMethod, originMethods); 72 | if(method.getParameterAnnotations()[0].length > 0) { 73 | ClassFile ccFile = enhanceClass.getClassFile(); 74 | ConstPool constPool = ccFile.getConstPool(); 75 | 76 | //拷贝注解信息和注解内容,以支持mybatis mapper类的动态绑定 77 | newMethod.getMethodInfo().addAttribute(MapperAnnotationEnhancer.duplicateParameterAnnotationsAttribute(constPool, method)); 78 | } 79 | enhanceClass.addMethod(newMethod); 80 | } 81 | Class loadThisClass = enhanceClass.toClass(); 82 | 83 | //2015.09.22后不再输出类到本地 84 | // enhanceClass.writeFile("."); 85 | } 86 | } 87 | 88 | private static Method getOriginMethod(CtMethod ctMethod, Method[] originMethods) { 89 | for (Method method : originMethods) { 90 | int len = ctMethod.getName().length(); 91 | if (ctMethod.getName().substring(0, len-4).equals(method.getName())) { 92 | return method; 93 | } 94 | } 95 | throw new RuntimeException("enhanceMapperClass find method error!"); 96 | } 97 | 98 | /** 99 | * 添加映射方法 100 | * 101 | * @param methodName 102 | * @param method 103 | */ 104 | public void addMethodMap(String methodName, Method method) { 105 | methodMap.put(methodName, method); 106 | } 107 | 108 | 109 | private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory(); 110 | private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory(); 111 | 112 | /** 113 | * 反射对象,增加对低版本Mybatis的支持 114 | * 115 | * @param object 反射对象 116 | * @return 117 | */ 118 | public static MetaObject forObject(Object object) { 119 | return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); 120 | } 121 | 122 | /** 123 | * 是否支持该通用方法 124 | * 125 | * @param msId 126 | * @return 127 | */ 128 | public boolean supportMethod(String msId) { 129 | Class mapperClass = getMapperClass(msId); 130 | if (this.mapperClass.isAssignableFrom(mapperClass)) { 131 | String methodName = getMethodName(msId); 132 | return methodMap.get(methodName) != null; 133 | } 134 | return false; 135 | } 136 | 137 | /** 138 | * 重新设置SqlSource 139 | * 140 | * @param ms 141 | * @param sqlSource 142 | */ 143 | protected void setSqlSource(MappedStatement ms, SqlSource sqlSource) { 144 | MetaObject msObject = forObject(ms); 145 | msObject.setValue("sqlSource", sqlSource); 146 | } 147 | 148 | /** 149 | * 重新设置SqlSource 150 | * 151 | * @param ms 152 | * @throws java.lang.reflect.InvocationTargetException 153 | * @throws IllegalAccessException 154 | */ 155 | public void setSqlSource(MappedStatement ms, Configuration configuration) throws Exception { 156 | Method method = methodMap.get(getMethodName(ms)); 157 | try { 158 | if (method.getReturnType() == Void.TYPE) { 159 | method.invoke(this, ms); 160 | } else if (SqlSource.class.isAssignableFrom(method.getReturnType())) { 161 | //代码增强 扩充为512个方法。 162 | for (long i = 0; i < 512; i++) { 163 | 164 | //新的带sharding的sql 165 | SqlSource sqlSource = (SqlSource) method.invoke(this, ms, configuration, i); 166 | 167 | String newMsId = ms.getId() + ShardingCaculator.getNumberWithZeroSuffix(i); 168 | newMsId = newMsId.replace("Mapper.", "MapperSharding" + getMethodName(ms) + "."); 169 | 170 | //添加到ms库中 171 | MappedStatement newMs = copyFromMappedStatement(ms, sqlSource, newMsId); 172 | configuration.addMappedStatement(newMs); 173 | setSqlSource(newMs, sqlSource); 174 | } 175 | } else { 176 | throw new RuntimeException("自定义Mapper方法返回类型错误,可选的返回类型为void和SqlNode!"); 177 | } 178 | } catch (IllegalAccessException e) { 179 | throw new RuntimeException(e); 180 | } catch (InvocationTargetException e) { 181 | throw new RuntimeException(e.getTargetException() != null ? e.getTargetException() : e); 182 | } 183 | } 184 | 185 | protected MappedStatement copyFromMappedStatement(MappedStatement ms, 186 | SqlSource newSqlSource, String newMsId) { 187 | MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, newSqlSource, ms.getSqlCommandType()); 188 | builder.resource(ms.getResource()); 189 | builder.fetchSize(ms.getFetchSize()); 190 | builder.statementType(ms.getStatementType()); 191 | builder.keyGenerator(ms.getKeyGenerator()); 192 | // setStatementTimeout() 193 | builder.timeout(ms.getTimeout()); 194 | // setParameterMap() 195 | builder.parameterMap(ms.getParameterMap()); 196 | // setStatementResultMap() 197 | List resultMaps = ms.getResultMaps(); 198 | builder.resultMaps(resultMaps); 199 | builder.resultSetType(ms.getResultSetType()); 200 | // setStatementCache() 201 | builder.cache(ms.getCache()); 202 | builder.flushCacheRequired(ms.isFlushCacheRequired()); 203 | builder.useCache(ms.isUseCache()); 204 | return builder.build(); 205 | } 206 | 207 | /** 208 | * 根据msId获取接口类 209 | * 210 | * @param msId 211 | * @return 212 | * @throws ClassNotFoundException 213 | */ 214 | public static Class getMapperClass(String msId) { 215 | String mapperClassStr = msId.substring(0, msId.lastIndexOf(".")); 216 | try { 217 | return Class.forName(mapperClassStr); 218 | } catch (ClassNotFoundException e) { 219 | throw new RuntimeException("无法获取Mapper接口信息:" + msId); 220 | } 221 | } 222 | 223 | /** 224 | * 获取执行的方法名 225 | * 226 | * @param ms 227 | * @return 228 | */ 229 | public static String getMethodName(MappedStatement ms) { 230 | return getMethodName(ms.getId()); 231 | } 232 | 233 | /** 234 | * 获取执行的方法名 235 | * 236 | * @param msId 237 | * @return 238 | */ 239 | public static String getMethodName(String msId) { 240 | return msId.substring(msId.lastIndexOf(".") + 1); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperHelperForSharding.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import com.mogujie.trade.tsharding.annotation.ShardingExtensionMethod; 4 | import org.apache.ibatis.mapping.MappedStatement; 5 | import org.apache.ibatis.session.Configuration; 6 | import org.apache.ibatis.session.SqlSession; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.*; 10 | 11 | /** 12 | * Mapper处理主要逻辑,最关键的一个类 13 | *

14 | *

15 | * 参考项目地址 : https://github.com/abel533/Mapper 17 | *

18 | * 19 | * @author qigong on 5/1/15 20 | */ 21 | public class MapperHelperForSharding { 22 | 23 | /** 24 | * 注册的通用Mapper接口 25 | */ 26 | private Map, MapperEnhancer> registerMapper = new HashMap, MapperEnhancer>(); 27 | 28 | /** 29 | * 缓存msid和MapperTemplate 30 | */ 31 | private Map msIdCache = new HashMap(); 32 | /** 33 | * 缓存skip结果 34 | */ 35 | private final Map msIdSkip = new HashMap(); 36 | 37 | /** 38 | * 缓存已经处理过的Collection 39 | */ 40 | private Set> collectionSet = new HashSet>(); 41 | 42 | /** 43 | * 是否使用的Spring 44 | */ 45 | private boolean spring = false; 46 | 47 | /** 48 | * 是否为Spring4.x以上版本 49 | */ 50 | private boolean spring4 = false; 51 | 52 | /** 53 | * Spring版本号 54 | */ 55 | private String springVersion; 56 | 57 | /** 58 | * 缓存初始化时的SqlSession 59 | */ 60 | private List sqlSessions = new ArrayList(); 61 | 62 | /** 63 | * 针对Spring注入需要处理的SqlSession 64 | * 65 | * @param sqlSessions 66 | */ 67 | public void setSqlSessions(SqlSession[] sqlSessions) { 68 | if (sqlSessions != null && sqlSessions.length > 0) { 69 | this.sqlSessions.addAll(Arrays.asList(sqlSessions)); 70 | } 71 | } 72 | 73 | /** 74 | * Spring初始化方法,使用Spring时需要配置init-method="initMapper" 75 | */ 76 | public void initMapper() { 77 | // 只有Spring会执行这个方法,所以Spring配置的时候,从这儿可以尝试获取Spring的版本 78 | // 先判断Spring版本,对下面的操作有影响 79 | // Spring4以上支持泛型注入,因此可以扫描通用Mapper 80 | if (!initSpringVersion()) { 81 | throw new RuntimeException("Error! Spring4 is necessary!"); 82 | } 83 | 84 | for (SqlSession sqlSession : sqlSessions) { 85 | processConfiguration(sqlSession.getConfiguration()); 86 | } 87 | } 88 | 89 | /** 90 | * 检测Spring版本号,Spring4.x以上支持泛型注入 91 | */ 92 | private boolean initSpringVersion() { 93 | try { 94 | // 反射获取SpringVersion 95 | Class springVersionClass = Class.forName("org.springframework.core.SpringVersion"); 96 | springVersion = (String) springVersionClass.getDeclaredMethod("getVersion", new Class[0]).invoke(null, 97 | new Object[0]); 98 | spring = true; 99 | if (springVersion.indexOf(".") > 0) { 100 | int MajorVersion = Integer.parseInt(springVersion.substring(0, springVersion.indexOf("."))); 101 | if (MajorVersion > 3) { 102 | spring4 = true; 103 | } else { 104 | spring4 = false; 105 | } 106 | } 107 | } catch (Exception e) { 108 | spring = false; 109 | spring4 = false; 110 | } 111 | return spring && spring4; 112 | } 113 | 114 | /** 115 | * 通过通用Mapper接口获取对应的MapperTemplate 116 | * 117 | * @param mapperClass 118 | */ 119 | private MapperEnhancer fromMapperClass(Class mapperClass) { 120 | Method[] methods = mapperClass.getDeclaredMethods(); 121 | Class templateClass = null; 122 | Class tempClass = null; 123 | Set methodSet = new HashSet(); 124 | for (Method method : methods) { 125 | if (method.isAnnotationPresent(ShardingExtensionMethod.class)) { 126 | ShardingExtensionMethod annotation = method.getAnnotation(ShardingExtensionMethod.class); 127 | tempClass = annotation.type(); 128 | methodSet.add(method.getName()); 129 | } 130 | if (templateClass == null) { 131 | templateClass = tempClass; 132 | } else if (templateClass != tempClass) { 133 | throw new RuntimeException("一个通用Mapper中只允许存在一个MapperTemplate子类!"); 134 | } 135 | } 136 | if (templateClass == null || !MapperEnhancer.class.isAssignableFrom(templateClass)) { 137 | throw new RuntimeException("接口中不存在包含type为MapperTemplate的Provider注解,这不是一个合法的通用Mapper接口类!"); 138 | } 139 | MapperEnhancer mapperEnhancer = null; 140 | try { 141 | mapperEnhancer = (MapperEnhancer) templateClass.getConstructor(Class.class).newInstance(mapperClass); 142 | } catch (Exception e) { 143 | throw new RuntimeException("实例化MapperTemplate对象失败:" + e.getMessage(), e); 144 | } 145 | // 注册方法 146 | for (String methodName : methodSet) { 147 | try { 148 | mapperEnhancer.addMethodMap(methodName, templateClass.getMethod("enhancedShardingSQL", MappedStatement.class, Configuration.class, Long.class)); 149 | } catch (NoSuchMethodException e) { 150 | throw new RuntimeException(templateClass.getCanonicalName() + "中缺少enhancedShardingSQL方法!"); 151 | } 152 | } 153 | return mapperEnhancer; 154 | } 155 | 156 | /** 157 | * 注册通用Mapper接口 158 | * 159 | * @param mapperClass 160 | * @throws Exception 161 | */ 162 | public void registerMapper(Class mapperClass) { 163 | if (registerMapper.get(mapperClass) == null) { 164 | MapperEnhancer enhancer = fromMapperClass(mapperClass); 165 | registerMapper.put(mapperClass, enhancer); 166 | } else { 167 | throw new RuntimeException("已经注册过的通用Mapper[" + mapperClass.getCanonicalName() + "]不能多次注册!"); 168 | } 169 | } 170 | 171 | /** 172 | * 注册通用Mapper接口 173 | * 174 | * @param mapperClass 175 | * @throws Exception 176 | */ 177 | public void registerMapper(String mapperClass) { 178 | try { 179 | registerMapper(Class.forName(mapperClass)); 180 | } catch (ClassNotFoundException e) { 181 | throw new RuntimeException("注册通用Mapper[" + mapperClass + "]失败,找不到该通用Mapper!"); 182 | } 183 | } 184 | 185 | /** 186 | * 方便Spring注入 187 | * 188 | * @param mappers 189 | */ 190 | public void setMappers(String[] mappers) { 191 | if (mappers != null && mappers.length > 0) { 192 | for (String mapper : mappers) { 193 | registerMapper(mapper); 194 | } 195 | } 196 | } 197 | 198 | /** 199 | * 判断当前的接口方法是否需要进行拦截 200 | * 201 | * @param msId 202 | * @return 203 | */ 204 | public boolean isMapperMethod(String msId) { 205 | if (msIdSkip.get(msId) != null) { 206 | return msIdSkip.get(msId); 207 | } 208 | for (Map.Entry, MapperEnhancer> entry : registerMapper.entrySet()) { 209 | if (entry.getValue().supportMethod(msId)) { 210 | msIdSkip.put(msId, true); 211 | return true; 212 | } 213 | } 214 | msIdSkip.put(msId, false); 215 | return false; 216 | } 217 | 218 | /** 219 | * 获取MapperTemplate 220 | * 221 | * @param msId 222 | * @return 223 | */ 224 | private MapperEnhancer getMapperTemplate(String msId) { 225 | MapperEnhancer mapperEnhancer = null; 226 | if (msIdCache.get(msId) != null) { 227 | mapperEnhancer = msIdCache.get(msId); 228 | } else { 229 | for (Map.Entry, MapperEnhancer> entry : registerMapper.entrySet()) { 230 | if (entry.getValue().supportMethod(msId)) { 231 | mapperEnhancer = entry.getValue(); 232 | break; 233 | } 234 | } 235 | msIdCache.put(msId, mapperEnhancer); 236 | } 237 | return mapperEnhancer; 238 | } 239 | 240 | /** 241 | * 重新设置SqlSource 242 | * 243 | * @param ms 244 | */ 245 | public void setSqlSource(MappedStatement ms, Configuration configuration) { 246 | MapperEnhancer mapperEnhancer = getMapperTemplate(ms.getId()); 247 | try { 248 | if (mapperEnhancer != null) { 249 | mapperEnhancer.setSqlSource(ms, configuration); 250 | } 251 | } catch (Exception e) { 252 | throw new RuntimeException("调用方法异常:" + e.getMessage(), e); 253 | } 254 | } 255 | 256 | /** 257 | * 处理configuration中全部的MappedStatement 258 | * 259 | * @param configuration 260 | */ 261 | public void processConfiguration(Configuration configuration) { 262 | Collection collection = configuration.getMappedStatements(); 263 | // 防止反复处理一个 264 | if (collectionSet.contains(collection)) { 265 | return; 266 | } else { 267 | collectionSet.add(collection); 268 | } 269 | 270 | Collection tmpCollection = new HashSet<>(); 271 | tmpCollection.addAll(collection); 272 | 273 | Iterator iterator = tmpCollection.iterator(); 274 | while (iterator.hasNext()) { 275 | Object object = iterator.next(); 276 | if (object instanceof MappedStatement) { 277 | MappedStatement ms = (MappedStatement) object; 278 | if (isMapperMethod(ms.getId())) { 279 | setSqlSource(ms, configuration); 280 | } 281 | } 282 | } 283 | } 284 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperResourceEnhancer.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import com.mogujie.trade.tsharding.client.ShardingCaculator; 4 | import org.apache.ibatis.builder.StaticSqlSource; 5 | import org.apache.ibatis.mapping.MappedStatement; 6 | import org.apache.ibatis.mapping.ParameterMapping; 7 | import org.apache.ibatis.mapping.SqlSource; 8 | import org.apache.ibatis.scripting.defaults.RawSqlSource; 9 | import org.apache.ibatis.scripting.xmltags.*; 10 | import org.apache.ibatis.session.Configuration; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.lang.reflect.Field; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Mappper sql增强 20 | * 21 | * @author qigong on 5/1/15 22 | */ 23 | public class MapperResourceEnhancer extends MapperEnhancer{ 24 | 25 | Logger logger = LoggerFactory.getLogger(MapperResourceEnhancer.class); 26 | 27 | public MapperResourceEnhancer(Class mapperClass) { 28 | super(mapperClass); 29 | } 30 | 31 | public SqlSource enhancedShardingSQL(MappedStatement ms, Configuration configuration, Long shardingPara) { 32 | 33 | String tableName = ShardingCaculator.caculateTableName(shardingPara); 34 | SqlSource result = null; 35 | 36 | try { 37 | if (ms.getSqlSource() instanceof DynamicSqlSource) { 38 | 39 | DynamicSqlSource sqlSource = (DynamicSqlSource) ms.getSqlSource(); 40 | 41 | Class sqlSourceClass = sqlSource.getClass(); 42 | 43 | Field sqlNodeField = sqlSourceClass.getDeclaredField("rootSqlNode"); 44 | sqlNodeField.setAccessible(true); 45 | 46 | MixedSqlNode rootSqlNode = (MixedSqlNode) sqlNodeField.get(sqlSource); 47 | 48 | Class mixedSqlNodeClass = rootSqlNode.getClass(); 49 | Field contentsField = mixedSqlNodeClass.getDeclaredField("contents"); 50 | contentsField.setAccessible(true); 51 | List textSqlNodes = (List) contentsField.get(rootSqlNode); 52 | List newSqlNodesList = new ArrayList(); 53 | 54 | //StaticTextSqlNode 55 | Class textSqlNodeClass = textSqlNodes.get(0).getClass(); 56 | Field textField = textSqlNodeClass.getDeclaredField("text"); 57 | textField.setAccessible(true); 58 | for (SqlNode node : textSqlNodes) { 59 | if (node instanceof StaticTextSqlNode) { 60 | StaticTextSqlNode textSqlNode = (StaticTextSqlNode) node; 61 | String text = (String) textField.get(textSqlNode); 62 | if(!text.contains("TradeOrder")){ 63 | newSqlNodesList.add(node); 64 | }else { 65 | newSqlNodesList.add(new StaticTextSqlNode(replaceWithShardingTableName(text, tableName, shardingPara))); 66 | } 67 | }else{ 68 | newSqlNodesList.add(node); 69 | } 70 | } 71 | 72 | MixedSqlNode newrootSqlNode = new MixedSqlNode(newSqlNodesList); 73 | result = new DynamicSqlSource(configuration, newrootSqlNode); 74 | return result; 75 | 76 | } else if (ms.getSqlSource() instanceof RawSqlSource) { 77 | 78 | RawSqlSource sqlSource = (RawSqlSource) ms.getSqlSource(); 79 | Class sqlSourceClass = sqlSource.getClass(); 80 | Field sqlSourceField = sqlSourceClass.getDeclaredField("sqlSource"); 81 | sqlSourceField.setAccessible(true); 82 | StaticSqlSource staticSqlSource = (StaticSqlSource) sqlSourceField.get(sqlSource); 83 | Field sqlField = staticSqlSource.getClass().getDeclaredField("sql"); 84 | Field parameterMappingsField = staticSqlSource.getClass().getDeclaredField("parameterMappings"); 85 | sqlField.setAccessible(true); 86 | parameterMappingsField.setAccessible(true); 87 | 88 | //sql处理 89 | String sql = (String) sqlField.get(staticSqlSource); 90 | 91 | if(!sql.contains("TradeOrder")){ 92 | result = sqlSource; 93 | }else { 94 | sql = replaceWithShardingTableName(sql, tableName, shardingPara); 95 | result = new RawSqlSource(configuration, sql, null); 96 | //为sqlSource对象设置mappering参数 97 | StaticSqlSource newStaticSqlSource = (StaticSqlSource) sqlSourceField.get(result); 98 | List parameterMappings = (List)parameterMappingsField.get(staticSqlSource); 99 | parameterMappingsField.set(newStaticSqlSource, parameterMappings); 100 | } 101 | return result; 102 | } else { 103 | throw new RuntimeException("wrong sqlSource type!" + ms.getResource()); 104 | } 105 | 106 | } catch (Exception e) { 107 | logger.error("reflect error!, ms resources:" + ms.getResource(), e); 108 | } 109 | return result; 110 | } 111 | 112 | 113 | private String replaceWithShardingTableName(String text, String tableName, Long shardingPara){ 114 | if(text.contains(" TradeOrderPressureTest")){ 115 | return text.replace(" TradeOrderPressureTest", " TradeOrderPressureTest" + ShardingCaculator.getNumberWithZeroSuffix(shardingPara)); 116 | } 117 | return text.replace(" TradeOrder", " " + tableName); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperScannerWithSharding.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import com.mogujie.trade.db.DataSourceLookup; 4 | import com.mogujie.trade.db.ReadWriteSplittingDataSource; 5 | import com.mogujie.trade.tsharding.route.orm.base.*; 6 | import org.apache.ibatis.mapping.MappedStatement; 7 | import org.apache.ibatis.session.Configuration; 8 | import org.apache.ibatis.session.SqlSessionFactory; 9 | import org.mybatis.spring.SqlSessionFactoryBean; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.beans.factory.InitializingBean; 12 | import org.springframework.beans.factory.config.BeanFactoryPostProcessor; 13 | import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 14 | import org.springframework.core.io.Resource; 15 | 16 | import java.io.IOException; 17 | import java.lang.reflect.*; 18 | import java.util.HashMap; 19 | import java.util.HashSet; 20 | import java.util.Map; 21 | import java.util.Set; 22 | 23 | /** 24 | * Tsharding MybatisMapper的扫描类,负责将Mapper接口与对应的xml配置文件整合,绑定设定的数据源,注入到Spring Context中。 25 | * 26 | * @author qigong 27 | */ 28 | public class MapperScannerWithSharding implements BeanFactoryPostProcessor, InitializingBean { 29 | 30 | public static DataSourceLookup dataSourceLookup; 31 | 32 | private String packageName; 33 | 34 | private Resource[] mapperLocations; 35 | 36 | private String[] mapperPacakages; 37 | 38 | private SqlSessionFactoryLookup sqlSessionFactoryLookup; 39 | 40 | public static DataSourceLookup getDataSourceLookup() { 41 | return dataSourceLookup; 42 | } 43 | 44 | @Override 45 | public void afterPropertiesSet() throws Exception { 46 | this.initMapperPackage(); 47 | } 48 | 49 | private void initMapperPackage() throws IOException { 50 | this.mapperPacakages = packageName.split(","); 51 | } 52 | 53 | @Override 54 | public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 55 | this.dataSourceLookup = beanFactory.getBean(DataSourceLookup.class); 56 | 57 | try { 58 | this.initSqlSessionFactories(beanFactory); 59 | } catch (Exception e) { 60 | throw new RuntimeException(e); 61 | } 62 | ClassPathScanHandler scanner = new ClassPathScanHandler(); 63 | Set> mapperClasses = new HashSet<>(); 64 | for (String mapperPackage : this.mapperPacakages) { 65 | Set> classes = scanner.getPackageAllClasses(mapperPackage.trim(), false); 66 | mapperClasses.addAll(classes); 67 | } 68 | for (Class clazz : mapperClasses) { 69 | if (isMapper(clazz)) { 70 | Object mapper = this.newMapper(clazz); 71 | beanFactory.registerSingleton(Character.toLowerCase(clazz.getSimpleName().charAt(0)) 72 | + clazz.getSimpleName().substring(1), mapper); 73 | } 74 | } 75 | 76 | } 77 | 78 | private void initSqlSessionFactories(ConfigurableListableBeanFactory beanFactory) throws Exception { 79 | Map sqlSessionFactories = new HashMap<>(this.dataSourceLookup.getMapping().size()); 80 | 81 | ReadWriteSplittingDataSource defaultDataSource = null; 82 | SqlSessionFactory defaultSqlSessionFactory = null; 83 | for (ReadWriteSplittingDataSource dataSource : this.dataSourceLookup.getMapping().values()) { 84 | 85 | SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); 86 | sessionFactoryBean.setMapperLocations(mapperLocations); 87 | sessionFactoryBean.setDataSource(dataSource); 88 | sessionFactoryBean.setTypeAliasesPackage(this.packageName + ".domain.entity"); 89 | 90 | // init 初始化所有sql对应的元数据、资源(sqlNode, sqlSource, mappedStatement)等 91 | sessionFactoryBean.afterPropertiesSet(); 92 | 93 | if (defaultDataSource == null) { 94 | //第一个 95 | defaultDataSource = dataSource; 96 | defaultSqlSessionFactory = sessionFactoryBean.getObject(); 97 | } else { 98 | SqlSessionFactory newSqlSessionFactory = sessionFactoryBean.getObject(); 99 | Field conf = newSqlSessionFactory.getClass().getDeclaredField("configuration"); 100 | conf.setAccessible(true); 101 | Configuration newConfiguration = (Configuration) conf.get(newSqlSessionFactory); 102 | Field mappedStatementField = newConfiguration.getClass().getDeclaredField("mappedStatements"); 103 | 104 | //去掉final修饰符 105 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 106 | modifiersField.setAccessible(true); 107 | modifiersField.setInt( mappedStatementField, mappedStatementField.getModifiers() & ~Modifier.FINAL); 108 | mappedStatementField.setAccessible(true); 109 | 110 | //后续的元数据复用 111 | Configuration defaultConfiguration = defaultSqlSessionFactory.getConfiguration(); 112 | Map reUsedMappedStatement = (Map) mappedStatementField.get(defaultConfiguration); 113 | mappedStatementField.set(newConfiguration, reUsedMappedStatement); 114 | } 115 | beanFactory.registerSingleton(dataSource.getName() + "SqlSessionFactory", sessionFactoryBean); 116 | sqlSessionFactories.put(dataSource.getName(), sessionFactoryBean.getObject()); 117 | defaultSqlSessionFactory = sessionFactoryBean.getObject(); 118 | } 119 | 120 | this.sqlSessionFactoryLookup = new SqlSessionFactoryLookup(sqlSessionFactories); 121 | } 122 | 123 | private boolean isMapper(Class clazz) { 124 | if (clazz.isInterface()) { 125 | return true; 126 | } 127 | return false; 128 | } 129 | 130 | private Object newMapper(final Class clazz) { 131 | 132 | final Invoker invoker = new TShardingRoutingInvokeFactory(sqlSessionFactoryLookup).newInvoker(clazz); 133 | 134 | return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, 135 | new InvocationHandler() { 136 | @Override 137 | public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable { 138 | return invoker.invoke(new DefaultInvocation(method, args)); 139 | } 140 | }); 141 | } 142 | 143 | /** 144 | * 注入packageName配置 145 | * 146 | * @param packageName 147 | */ 148 | public void setPackageName(String packageName) { 149 | this.packageName = packageName; 150 | } 151 | 152 | /** 153 | * 注入mapperLocations配置 154 | * 155 | * @param mapperLocations 156 | */ 157 | public void setMapperLocations(Resource[] mapperLocations) { 158 | this.mapperLocations = mapperLocations; 159 | } 160 | } 161 | 162 | 163 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/MapperShardingInitializer.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm; 2 | 3 | import org.apache.ibatis.session.SqlSession; 4 | import org.apache.ibatis.session.SqlSessionFactory; 5 | import org.mybatis.spring.SqlSessionTemplate; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.BeansException; 9 | import org.springframework.context.ApplicationContext; 10 | import org.springframework.context.ApplicationContextAware; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 增强Mapper处理总入口:Mapper被mybatis初始化后,在这里做进一步的处理和增强 18 | * 19 | * @author qigong on 5/1/15 20 | */ 21 | public class MapperShardingInitializer implements ApplicationContextAware { 22 | 23 | 24 | Logger logger = LoggerFactory.getLogger(getClass()); 25 | 26 | private String needEnhancedClasses; 27 | private String[] needEnhancedClassesArray; 28 | 29 | @Override 30 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 31 | Map sqlSessionFactories = applicationContext.getBeansOfType(SqlSessionFactory.class); 32 | if (sqlSessionFactories.isEmpty()) { 33 | return; 34 | } 35 | MapperHelperForSharding mapperHelperForSharding = new MapperHelperForSharding(); 36 | List sqlSessions = new ArrayList<>(sqlSessionFactories.size()); 37 | for (SqlSessionFactory sqlSessionFactory : sqlSessionFactories.values()) { 38 | SqlSession sqlSession = new SqlSessionTemplate(sqlSessionFactory); 39 | sqlSessions.add(sqlSession); 40 | } 41 | //Mapper代码增强 每个方法扩展出一个ShardingMapper类,增强为512个方法。 42 | this.needEnhancedClassesArray = needEnhancedClasses.split(","); 43 | this.enhanceMapperClass(); 44 | mapperHelperForSharding.setMappers(needEnhancedClassesArray); 45 | mapperHelperForSharding.setSqlSessions(sqlSessions.toArray(new SqlSession[0])); 46 | mapperHelperForSharding.initMapper(); 47 | } 48 | 49 | private void enhanceMapperClass() { 50 | for (String mapperClass : needEnhancedClassesArray) { 51 | try { 52 | MapperEnhancer.enhanceMapperClass(mapperClass); 53 | } catch (Exception e) { 54 | logger.error("Enhance {} class error", mapperClass, e); 55 | } 56 | } 57 | } 58 | 59 | public void setNeedEnhancedClasses(String needEnhancedClasses) { 60 | this.needEnhancedClasses = needEnhancedClasses; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ClassPathScanHandler.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.File; 7 | import java.io.FileFilter; 8 | import java.io.IOException; 9 | import java.net.JarURLConnection; 10 | import java.net.URL; 11 | import java.net.URLDecoder; 12 | import java.util.Enumeration; 13 | import java.util.LinkedHashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | import java.util.regex.Pattern; 19 | 20 | /** 21 | * 扫描指定包(包括jar)下的class文件
22 | * http://www.micmiu.com 23 | * 24 | * @author michael 25 | */ 26 | public class ClassPathScanHandler { 27 | 28 | /** 29 | * logger 30 | */ 31 | private static final Logger logger = LoggerFactory.getLogger(ClassPathScanHandler.class); 32 | 33 | /** 34 | * 是否排除内部类 true->是 false->否 35 | */ 36 | private boolean excludeInner = true; 37 | /** 38 | * 过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的 39 | */ 40 | private boolean checkInOrEx = true; 41 | 42 | /** 43 | * 过滤规则列表 如果是null或者空,即全部符合不过滤 44 | */ 45 | private List classFilters = null; 46 | 47 | /** 48 | * 无参构造器,默认是排除内部类、并搜索符合规则 49 | */ 50 | public ClassPathScanHandler() { 51 | } 52 | 53 | /** 54 | * excludeInner:是否排除内部类 true->是 false->否
55 | * checkInOrEx:过滤规则适用情况 true—>搜索符合规则的 false->排除符合规则的
56 | * classFilters:自定义过滤规则,如果是null或者空,即全部符合不过滤 57 | * 58 | * @param excludeInner 59 | * @param checkInOrEx 60 | * @param classFilters 61 | */ 62 | public ClassPathScanHandler(Boolean excludeInner, Boolean checkInOrEx, List classFilters) { 63 | this.excludeInner = excludeInner; 64 | this.checkInOrEx = checkInOrEx; 65 | this.classFilters = classFilters; 66 | 67 | } 68 | 69 | /** 70 | * 扫描包 71 | * 72 | * @param basePackage 73 | * 基础包 74 | * @param recursive 75 | * 是否递归搜索子包 76 | * @return Set 77 | */ 78 | public Set> getPackageAllClasses(String basePackage, boolean recursive) { 79 | Set> classes = new LinkedHashSet>(); 80 | String packageName = basePackage; 81 | if (packageName.endsWith(".")) { 82 | packageName = packageName.substring(0, packageName.lastIndexOf('.')); 83 | } 84 | String package2Path = packageName.replace('.', '/'); 85 | 86 | Enumeration dirs; 87 | try { 88 | dirs = Thread.currentThread().getContextClassLoader().getResources(package2Path); 89 | while (dirs.hasMoreElements()) { 90 | URL url = dirs.nextElement(); 91 | String protocol = url.getProtocol(); 92 | if ("file".equals(protocol)) { 93 | logger.info("扫描file类型的class文件...."); 94 | String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); 95 | doScanPackageClassesByFile(classes, packageName, filePath, recursive); 96 | } else if ("jar".equals(protocol)) { 97 | logger.info("扫描jar文件中的类...."); 98 | doScanPackageClassesByJar(packageName, url, recursive, classes); 99 | } 100 | } 101 | } catch (IOException e) { 102 | logger.error("IOException error:", e); 103 | } 104 | 105 | return classes; 106 | } 107 | 108 | /** 109 | * 以jar的方式扫描包下的所有Class文件
110 | * 111 | * @param basePackage 112 | * eg:michael.utils. 113 | * @param url 114 | * @param recursive 115 | * @param classes 116 | */ 117 | private void doScanPackageClassesByJar(String basePackage, URL url, final boolean recursive, Set> classes) { 118 | String packageName = basePackage; 119 | String package2Path = packageName.replace('.', '/'); 120 | JarFile jar; 121 | try { 122 | jar = ((JarURLConnection) url.openConnection()).getJarFile(); 123 | Enumeration entries = jar.entries(); 124 | while (entries.hasMoreElements()) { 125 | JarEntry entry = entries.nextElement(); 126 | String name = entry.getName(); 127 | if (!name.startsWith(package2Path) || entry.isDirectory()) { 128 | continue; 129 | } 130 | 131 | // 判断是否递归搜索子包 132 | if (!recursive && name.lastIndexOf('/') != package2Path.length()) { 133 | continue; 134 | } 135 | // 判断是否过滤 inner class 136 | if (this.excludeInner && name.indexOf('$') != -1) { 137 | logger.info("exclude inner class with name:" + name); 138 | continue; 139 | } 140 | String classSimpleName = name.substring(name.lastIndexOf('/') + 1); 141 | // 判定是否符合过滤条件 142 | if (this.filterClassName(classSimpleName)) { 143 | String className = name.replace('/', '.'); 144 | className = className.substring(0, className.length() - 6); 145 | try { 146 | classes.add(Thread.currentThread().getContextClassLoader().loadClass(className)); 147 | } catch (ClassNotFoundException e) { 148 | logger.error("Class.forName error:", e); 149 | } 150 | } 151 | } 152 | } catch (IOException e) { 153 | logger.error("IOException error:", e); 154 | } 155 | } 156 | 157 | /** 158 | * 以文件的方式扫描包下的所有Class文件 159 | * 160 | * @param packageName 161 | * @param packagePath 162 | * @param recursive 163 | * @param classes 164 | */ 165 | private void doScanPackageClassesByFile(Set> classes, String packageName, String packagePath, 166 | boolean recursive) { 167 | File dir = new File(packagePath); 168 | if (!dir.exists() || !dir.isDirectory()) { 169 | return; 170 | } 171 | final boolean fileRecursive = recursive; 172 | File[] dirfiles = dir.listFiles(new FileFilter() { 173 | // 自定义文件过滤规则 174 | @Override 175 | public boolean accept(File file) { 176 | if (file.isDirectory()) { 177 | return fileRecursive; 178 | } 179 | String filename = file.getName(); 180 | if (excludeInner && filename.indexOf('$') != -1) { 181 | logger.info("exclude inner class with name:" + filename); 182 | return false; 183 | } 184 | return filterClassName(filename); 185 | } 186 | }); 187 | for (File file : dirfiles) { 188 | if (file.isDirectory()) { 189 | doScanPackageClassesByFile(classes, packageName + "." + file.getName(), file.getAbsolutePath(), 190 | recursive); 191 | } else { 192 | String className = file.getName().substring(0, file.getName().length() - 6); 193 | try { 194 | classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className)); 195 | 196 | } catch (ClassNotFoundException e) { 197 | logger.error("IOException error:", e); 198 | } 199 | } 200 | } 201 | } 202 | 203 | /** 204 | * 根据过滤规则判断类名 205 | * 206 | * @param className 207 | * @return 208 | */ 209 | private boolean filterClassName(String className) { 210 | if (!className.endsWith(".class")) { 211 | return false; 212 | } 213 | if (null == this.classFilters || this.classFilters.isEmpty()) { 214 | return true; 215 | } 216 | String tmpName = className.substring(0, className.length() - 6); 217 | boolean flag = false; 218 | for (String str : classFilters) { 219 | String tmpreg = "^" + str.replace("*", ".*") + "$"; 220 | Pattern p = Pattern.compile(tmpreg); 221 | if (p.matcher(tmpName).find()) { 222 | flag = true; 223 | break; 224 | } 225 | } 226 | return checkInOrEx && flag || !checkInOrEx && !flag; 227 | } 228 | 229 | /** 230 | * @return the excludeInner 231 | */ 232 | public boolean isExcludeInner() { 233 | return excludeInner; 234 | } 235 | 236 | /** 237 | * @return the checkInOrEx 238 | */ 239 | public boolean isCheckInOrEx() { 240 | return checkInOrEx; 241 | } 242 | 243 | /** 244 | * @return the classFilters 245 | */ 246 | public List getClassFilters() { 247 | return classFilters; 248 | } 249 | 250 | /** 251 | * @param pExcludeInner 252 | * the excludeInner to set 253 | */ 254 | public void setExcludeInner(boolean pExcludeInner) { 255 | excludeInner = pExcludeInner; 256 | } 257 | 258 | /** 259 | * @param pCheckInOrEx 260 | * the checkInOrEx to set 261 | */ 262 | public void setCheckInOrEx(boolean pCheckInOrEx) { 263 | checkInOrEx = pCheckInOrEx; 264 | } 265 | 266 | /** 267 | * @param pClassFilters 268 | * the classFilters to set 269 | */ 270 | public void setClassFilters(List pClassFilters) { 271 | classFilters = pClassFilters; 272 | } 273 | } -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/DefaultInvocation.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Arrays; 5 | 6 | public class DefaultInvocation implements Invocation { 7 | 8 | private final Method method; 9 | 10 | private final Object[] args; 11 | 12 | public DefaultInvocation(Method method, Object[] args) { 13 | this.method = method; 14 | this.args = args; 15 | } 16 | 17 | @Override 18 | public Method getMethod() { 19 | return this.method; 20 | } 21 | 22 | @Override 23 | public Object[] getArgs() { 24 | return this.args; 25 | } 26 | 27 | @Override 28 | public String toString() { 29 | StringBuilder builder = new StringBuilder(); 30 | builder.append("DefaultInvocation ["); 31 | if (method != null) { 32 | builder.append("method=").append(method).append(", "); 33 | } 34 | if (args != null) { 35 | builder.append("args=").append(Arrays.toString(args)); 36 | } 37 | builder.append("]"); 38 | return builder.toString(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invocation.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * @author qigong 7 | * 8 | */ 9 | public interface Invocation { 10 | 11 | Method getMethod(); 12 | 13 | Object[] getArgs(); 14 | } 15 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/Invoker.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | /** 4 | * @author qigong 5 | * 6 | */ 7 | public interface Invoker { 8 | 9 | Object invoke(Invocation invocation) throws Throwable; 10 | } 11 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/InvokerFactory.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | /** 4 | * @author qigong 5 | * 6 | * @param 7 | */ 8 | public interface InvokerFactory { 9 | Invoker newInvoker(T config); 10 | } 11 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperBasicConfig.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | /** 4 | * Mapper管控基础 类-数据源 5 | * 6 | * @author qigong 7 | * 8 | */ 9 | public class MapperBasicConfig { 10 | 11 | private final Class mapperInterface; 12 | 13 | private final String dataSourceName; 14 | 15 | public MapperBasicConfig(Class mapperInterface, String dataSourceName) { 16 | this.mapperInterface = mapperInterface; 17 | this.dataSourceName = dataSourceName; 18 | } 19 | 20 | public Class getMapperInterface() { 21 | return mapperInterface; 22 | } 23 | 24 | public String getDataSourceName() { 25 | return dataSourceName; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/MapperInitializeException.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | public class MapperInitializeException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = -5010183715049161425L; 6 | 7 | public MapperInitializeException(String message) { 8 | super(message); 9 | } 10 | 11 | public MapperInitializeException(Throwable cause) { 12 | super(cause); 13 | } 14 | 15 | public MapperInitializeException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public MapperInitializeException(String message, Throwable cause, boolean enableSuppression, 20 | boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReadWriteSplittingContextInitializer.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import com.mogujie.trade.db.DataSourceType; 4 | import com.mogujie.trade.db.ReadWriteSplitting; 5 | import com.mogujie.trade.db.ReadWriteSplittingContext; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.lang.reflect.Method; 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * 读写分离的上下文初始化和清空 15 | * 16 | * @author qigong 17 | */ 18 | public class ReadWriteSplittingContextInitializer { 19 | 20 | private final static Logger logger = LoggerFactory.getLogger(ReadWriteSplittingContextInitializer.class); 21 | 22 | private final static String[] DEFAULT_WRITE_METHOD_NAMES = {"update", "save", "insert", "delete", "add", 23 | "batchInsert", "batchUpdate", "batchSave", "batchAdd"}; 24 | 25 | private final static Map cache = new ConcurrentHashMap<>(); 26 | 27 | public static void initReadWriteSplittingContext(Method method) { 28 | // 忽略object的方法,只关注Mapper的方法 29 | if (method.getDeclaringClass() != Object.class) { 30 | } 31 | DataSourceType dataSourceType = getDataSourceType(method); 32 | logger.debug("ReadWriteSplitting {} using dataSource of {}", method, dataSourceType); 33 | 34 | ReadWriteSplittingContext.set(dataSourceType); 35 | } 36 | 37 | public static void clearReadWriteSplittingContext() { 38 | ReadWriteSplittingContext.clear(); 39 | } 40 | 41 | /** 42 | * 获取方法对应的数据眼类型 43 | * 44 | * @param method 45 | * @return 46 | */ 47 | private static DataSourceType getDataSourceType(Method method) { 48 | DataSourceType dataSourceType = cache.get(method); 49 | if (dataSourceType == null) { 50 | synchronized (method) { 51 | dataSourceType = cache.get(method); 52 | if (dataSourceType == null) { 53 | dataSourceType = determineDataSourceType(method); 54 | cache.put(method, dataSourceType); 55 | } 56 | } 57 | } 58 | return dataSourceType; 59 | } 60 | 61 | private static DataSourceType determineDataSourceType(Method method) { 62 | DataSourceType dataSourceType = DataSourceType.slave; 63 | 64 | ReadWriteSplitting readWriteSplitting = method.getAnnotation(ReadWriteSplitting.class); 65 | if (readWriteSplitting != null) { 66 | dataSourceType = readWriteSplitting.value(); 67 | dataSourceType = dataSourceType == null ? DataSourceType.master : dataSourceType; 68 | } else { 69 | for (String writeMethodName : DEFAULT_WRITE_METHOD_NAMES) { 70 | if (method.getName().startsWith(writeMethodName)) { 71 | dataSourceType = DataSourceType.master; 72 | break; 73 | } 74 | } 75 | } 76 | return dataSourceType; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/ReflectUtil.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | /** 6 | * @author qigong on 15/9/17 下午8:05. 7 | */ 8 | public class ReflectUtil { 9 | 10 | /** 11 | * 循环向上转型, 获取对象的 DeclaredField 12 | * @param object : 子类对象 13 | * @param fieldName : 父类中的属性名 14 | * @return 父类中的属性对象 15 | */ 16 | 17 | public static Field getDeclaredField(Object object, String fieldName){ 18 | Field field = null ; 19 | Class clazz = object.getClass() ; 20 | for(; clazz != Object.class ; clazz = clazz.getSuperclass()) { 21 | try { 22 | field = clazz.getDeclaredField(fieldName) ; 23 | return field ; 24 | } catch (Exception e) { 25 | } 26 | } 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/SqlSessionFactoryLookup.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import org.apache.ibatis.session.SqlSessionFactory; 4 | 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class SqlSessionFactoryLookup { 10 | 11 | private Map mapping; 12 | 13 | public SqlSessionFactoryLookup(Map mapping) { 14 | 15 | Map tmpMap = new HashMap<>(mapping.size()); 16 | tmpMap.putAll(mapping); 17 | this.mapping = Collections.unmodifiableMap(tmpMap); 18 | } 19 | 20 | public Map getMapping() { 21 | return this.mapping; 22 | } 23 | 24 | public SqlSessionFactory get(String name) { 25 | return this.mapping.get(name); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tsharding-client/src/main/java/com/mogujie/trade/tsharding/route/orm/base/TShardingRoutingInvokeFactory.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.trade.tsharding.route.orm.base; 2 | 3 | import com.mogujie.trade.db.DataSourceRouting; 4 | import com.mogujie.trade.db.DataSourceRoutingException; 5 | import com.mogujie.trade.tsharding.annotation.parameter.ShardingBuyerPara; 6 | import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; 7 | import com.mogujie.trade.tsharding.annotation.parameter.ShardingSellerPara; 8 | import com.mogujie.trade.tsharding.client.ShardingCaculator; 9 | import com.mogujie.trade.tsharding.route.TShardingRoutingHandler; 10 | import org.apache.ibatis.session.SqlSessionFactory; 11 | import org.mybatis.spring.mapper.MapperFactoryBean; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | import org.springframework.util.StringUtils; 15 | 16 | import java.lang.annotation.Annotation; 17 | import java.lang.reflect.Field; 18 | import java.lang.reflect.Method; 19 | import java.util.List; 20 | 21 | public class TShardingRoutingInvokeFactory implements InvokerFactory> { 22 | 23 | private final Logger logger = LoggerFactory.getLogger(getClass()); 24 | 25 | private SqlSessionFactoryLookup sqlSessionFactoryLookup; 26 | 27 | public TShardingRoutingInvokeFactory(SqlSessionFactoryLookup sqlSessionFactoryLookup) { 28 | this.sqlSessionFactoryLookup = sqlSessionFactoryLookup; 29 | } 30 | 31 | @Override 32 | public Invoker newInvoker(Class mapperInterface) { 33 | 34 | final DataSourceRouting dataSourceRouting = mapperInterface.getAnnotation(DataSourceRouting.class); 35 | final Class clazz = mapperInterface; 36 | 37 | if (dataSourceRouting != null && !StringUtils.isEmpty(dataSourceRouting.value())) { //使用配置的数据源 38 | logger.debug("TShardingRoutingInvokeFactory routing: emptyHandler and dataSourceRouting.value:" + dataSourceRouting.value()); 39 | return new Invoker() { 40 | @Override 41 | public Object invoke(Invocation invocation) throws Throwable { 42 | 43 | MapperBasicConfig config = new MapperBasicConfig(clazz, dataSourceRouting.value()); 44 | final Object mapper = newMyBatisMapper(config); 45 | try { 46 | ReadWriteSplittingContextInitializer.initReadWriteSplittingContext(invocation.getMethod()); 47 | return invocation.getMethod().invoke(mapper, invocation.getArgs()); 48 | } finally { 49 | ReadWriteSplittingContextInitializer.clearReadWriteSplittingContext(); 50 | } 51 | } 52 | }; 53 | } else if (dataSourceRouting != null && dataSourceRouting.handler() == TShardingRoutingHandler.class) { //使用Sharding数据源 54 | logger.debug("TShardingRoutingInvokeFactory routing: dynamic handler: " + dataSourceRouting.handler().getName()); 55 | return new Invoker() { 56 | @Override 57 | public Object invoke(Invocation invocation) throws Throwable { 58 | 59 | Method method = invocation.getMethod(); 60 | ShardingMetadata shardingMetadata = getShardingKey(method, invocation.getArgs()); 61 | 62 | if (shardingMetadata == null) { 63 | throw new DataSourceRoutingException("dataSourceRouting error! Method Name:" + method.getName() + " shardingMetadata is null!"); 64 | } 65 | 66 | //走分库分表环境 67 | logger.debug("TShardingRoutingInvokeFactory routing to sharding db. Method Name:" + method.getName() + ". ShardingKey:" + shardingMetadata.getShardingKey()); 68 | 69 | Class newClass = clazz; 70 | if (!"".equals(shardingMetadata.getSchemaName())) { 71 | newClass = Class.forName(clazz.getCanonicalName() + "Sharding" + method.getName()); 72 | } 73 | Method newMethod = newClass.getMethod(method.getName() + shardingMetadata.getTableSuffix(), method.getParameterTypes()); 74 | MapperBasicConfig config = new MapperBasicConfig(newClass, shardingMetadata.getSchemaName()); 75 | final Object mapper = newMyBatisMapper(config); 76 | try { 77 | ReadWriteSplittingContextInitializer.initReadWriteSplittingContext(invocation.getMethod()); 78 | return newMethod.invoke(mapper, invocation.getArgs()); 79 | } finally { 80 | ReadWriteSplittingContextInitializer.clearReadWriteSplittingContext(); 81 | } 82 | } 83 | }; 84 | } else { 85 | throw new DataSourceRoutingException("dataSourceRouting error! cannot find datasource"); 86 | } 87 | } 88 | 89 | 90 | private ShardingMetadata getShardingKey(Method method, Object[] args) throws NoSuchFieldException, IllegalAccessException { 91 | Annotation[][] an = method.getParameterAnnotations(); 92 | if (an.length > 0) { 93 | for (int i = 0; i < an.length; i++) { 94 | for (int j = 0; j < an[i].length; j++) { 95 | if (an[i][j] instanceof ShardingOrderPara || an[i][j] instanceof ShardingBuyerPara || an[i][j] instanceof ShardingSellerPara) { 96 | Long shardingKey = 0L; 97 | if (args[i] instanceof Long) { 98 | shardingKey = (Long) args[i]; 99 | } else if (args[i] instanceof List) { 100 | shardingKey = (Long) ((List) args[i]).get(0); 101 | } else if (an[i][j] instanceof ShardingOrderPara && args[i] instanceof Object) { 102 | Field field = ReflectUtil.getDeclaredField(args[i], "orderId"); 103 | field.setAccessible(true); 104 | shardingKey = (Long) field.get(args[i]); 105 | if (shardingKey == null) { 106 | field = ReflectUtil.getDeclaredField(args[i], "parentOrderId"); 107 | field.setAccessible(true); 108 | shardingKey = (Long) field.get(args[i]); 109 | } 110 | } else if (an[i][j] instanceof ShardingBuyerPara && args[i] instanceof Object) { 111 | Field field = ReflectUtil.getDeclaredField(args[i], "buyerUserId"); 112 | field.setAccessible(true); 113 | shardingKey = (Long) field.get(args[i]); 114 | } else if (an[i][j] instanceof ShardingSellerPara && args[i] instanceof Object) { 115 | Field field = ReflectUtil.getDeclaredField(args[i], "sellerUserId"); 116 | field.setAccessible(true); 117 | shardingKey = (Long) field.get(args[i]); 118 | } 119 | 120 | String schemaName = null; 121 | if (an[i][j] instanceof ShardingOrderPara) { 122 | schemaName = ShardingCaculator.caculateSchemaName("orderId", shardingKey); 123 | } else if (an[i][j] instanceof ShardingBuyerPara) { 124 | schemaName = ShardingCaculator.caculateSchemaName("buyerUserId", shardingKey); 125 | } else if (an[i][j] instanceof ShardingSellerPara) { 126 | schemaName = ShardingCaculator.caculateSchemaName("sellerUserId", shardingKey); 127 | } 128 | ShardingMetadata shardingMetadata = new ShardingMetadata(); 129 | shardingMetadata.setShardingKey(shardingKey); 130 | shardingMetadata.setTableSuffix(ShardingCaculator.getNumberWithZeroSuffix((shardingKey % 10000) % 512)); 131 | shardingMetadata.setSchemaName(schemaName); 132 | return shardingMetadata; 133 | } 134 | } 135 | } 136 | } 137 | return null; 138 | } 139 | 140 | @SuppressWarnings("unchecked") 141 | private Object newMyBatisMapper(MapperBasicConfig config) { 142 | MapperFactoryBean mapperFactoryBean = new MapperFactoryBean(); 143 | mapperFactoryBean.setMapperInterface(config.getMapperInterface()); 144 | mapperFactoryBean.setSqlSessionFactory(this.getSqlSessionFactory(config.getDataSourceName(), 145 | config.getMapperInterface())); 146 | mapperFactoryBean.afterPropertiesSet(); 147 | Object mapper = null; 148 | try { 149 | mapper = mapperFactoryBean.getObject(); 150 | } catch (Exception e) { 151 | throw new MapperInitializeException(e); 152 | } 153 | return mapper; 154 | } 155 | 156 | private SqlSessionFactory getSqlSessionFactory(String dataSourceName, Class mapperInterface) { 157 | if (StringUtils.isEmpty(dataSourceName)) { 158 | if (sqlSessionFactoryLookup.getMapping().size() == 1) { 159 | return sqlSessionFactoryLookup.getMapping().values().iterator().next(); 160 | } else { 161 | throw new DataSourceRoutingException("can't decided the datasource of " 162 | + mapperInterface.getCanonicalName() + ",please add config by using @DataSourceRouting"); 163 | } 164 | } else { 165 | SqlSessionFactory sqlSessionFactory = sqlSessionFactoryLookup.get(dataSourceName); 166 | if (sqlSessionFactory == null) { 167 | throw new DataSourceRoutingException("can't find datasource named " + dataSourceName 168 | + " while init!"); 169 | } 170 | return sqlSessionFactory; 171 | } 172 | } 173 | 174 | private class ShardingMetadata { 175 | 176 | private Long shardingKey; 177 | 178 | private String schemaName; 179 | 180 | private String tableSuffix; 181 | 182 | public String getSchemaName() { 183 | return schemaName; 184 | } 185 | 186 | public void setSchemaName(String schemaName) { 187 | this.schemaName = schemaName; 188 | } 189 | 190 | public String getTableSuffix() { 191 | return tableSuffix; 192 | } 193 | 194 | public void setTableSuffix(String tableSuffix) { 195 | this.tableSuffix = tableSuffix; 196 | } 197 | 198 | public Long getShardingKey() { 199 | return shardingKey; 200 | } 201 | 202 | public void setShardingKey(Long shardingKey) { 203 | this.shardingKey = shardingKey; 204 | } 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /tsharding-client/src/main/resources/tesla/support/service-loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/bean/BaseOrder.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.bean; 2 | 3 | 4 | public abstract class BaseOrder { 5 | 6 | private Long orderId; 7 | 8 | private Long buyerUserId; 9 | 10 | private Long sellerUserId; 11 | 12 | private Long shipTime; 13 | 14 | public Long getShipTime() { 15 | return shipTime; 16 | } 17 | 18 | public void setShipTime(Long shipTime) { 19 | this.shipTime = shipTime; 20 | } 21 | 22 | public Long getOrderId() { 23 | return orderId; 24 | } 25 | 26 | public void setOrderId(Long orderId) { 27 | this.orderId = orderId; 28 | } 29 | 30 | public Long getBuyerUserId() { 31 | return buyerUserId; 32 | } 33 | 34 | public void setBuyerUserId(Long buyerUserId) { 35 | this.buyerUserId = buyerUserId; 36 | } 37 | 38 | public Long getSellerUserId() { 39 | return sellerUserId; 40 | } 41 | 42 | public void setSellerUserId(Long sellerUserId) { 43 | this.sellerUserId = sellerUserId; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/bean/ShopOrder.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.bean; 2 | 3 | 4 | public class ShopOrder extends BaseOrder { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/dao/ShopOrderDao.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.dao; 2 | 3 | import com.mogujie.service.tsharding.bean.ShopOrder; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * @auther qigong on 6/5/15 8:50 PM. 9 | */ 10 | public interface ShopOrderDao { 11 | 12 | /** 13 | * 根据店铺级订单ID获取订单信息(同一个买家) 14 | * 15 | * @param listShopOrderIds 店铺级订单ID集合 16 | * @return List 17 | */ 18 | List getShopOrderByShopOrderIds(List listShopOrderIds); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/dao/ShopOrderDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.dao; 2 | 3 | import com.mogujie.service.tsharding.bean.ShopOrder; 4 | import com.mogujie.service.tsharding.mapper.ShopOrderMapper; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | /** 14 | * @auther qigong on 6/5/15 8:52 PM. 15 | */ 16 | @Service("shopOrderDao") 17 | public class ShopOrderDaoImpl implements ShopOrderDao { 18 | 19 | @Autowired 20 | private ShopOrderMapper shopOrderMapper; 21 | 22 | @Override 23 | public List getShopOrderByShopOrderIds(List listShopOrderIds) { 24 | if (listShopOrderIds == null || listShopOrderIds.size() == 0) { 25 | return null; 26 | } 27 | Set setShopOrderIds = new HashSet(); 28 | for (Long iShopOrderId : listShopOrderIds) { 29 | if (iShopOrderId > 0) { 30 | setShopOrderIds.add(iShopOrderId); 31 | } 32 | } 33 | return shopOrderMapper.getShopOrderByShopOrderIds(new ArrayList(setShopOrderIds)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/mapper/ShopOrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.mapper; 2 | 3 | import com.mogujie.service.tsharding.bean.ShopOrder; 4 | import com.mogujie.trade.db.DataSourceRouting; 5 | import com.mogujie.trade.tsharding.annotation.ShardingExtensionMethod; 6 | import com.mogujie.trade.tsharding.annotation.parameter.ShardingOrderPara; 7 | import com.mogujie.trade.tsharding.route.TShardingRoutingHandler; 8 | import com.mogujie.trade.tsharding.route.orm.MapperResourceEnhancer; 9 | import org.apache.ibatis.annotations.Param; 10 | 11 | import java.util.List; 12 | 13 | @DataSourceRouting(handler = TShardingRoutingHandler.class) 14 | public interface ShopOrderMapper { 15 | 16 | @ShardingExtensionMethod(type = MapperResourceEnhancer.class, method = "enhancedShardingSQL") 17 | public ShopOrder getShopOrderByShopOrderId(@ShardingOrderPara Long shopOrderId); 18 | 19 | @ShardingExtensionMethod(type = MapperResourceEnhancer.class, method = "enhancedShardingSQL") 20 | public List getShopOrderByShopOrderIds(@ShardingOrderPara List shopOrderIds); 21 | 22 | 23 | @ShardingExtensionMethod(type = MapperResourceEnhancer.class, method = "enhancedShardingSQL") 24 | int batchUpdateShopOrderByShopOrderIds(@ShardingOrderPara @Param("shopOrderIds") List shopOrderIds, @Param("shopOrder") ShopOrder shopOrder); 25 | 26 | } -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/test/BaseTest.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.test; 2 | 3 | import org.junit.runner.RunWith; 4 | import org.springframework.test.context.ContextConfiguration; 5 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 6 | 7 | /** 8 | * @author by jiuru on 16/7/14. 9 | */ 10 | 11 | @RunWith(SpringJUnit4ClassRunner.class) 12 | @ContextConfiguration({"classpath:spring-test.xml"}) 13 | public abstract class BaseTest { 14 | 15 | 16 | } 17 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/test/TShardingTest.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.test; 2 | 3 | import com.mogujie.service.tsharding.bean.ShopOrder; 4 | import com.mogujie.service.tsharding.dao.ShopOrderDao; 5 | import com.mogujie.service.tsharding.mapper.ShopOrderMapper; 6 | import org.junit.Test; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.util.Assert; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * @author by jiuru on 16/7/14. 17 | */ 18 | public class TShardingTest extends BaseTest { 19 | 20 | private final Logger logger = LoggerFactory.getLogger(TShardingTest.class); 21 | 22 | @Autowired 23 | private ShopOrderDao shopOrderDao; 24 | 25 | 26 | @Autowired 27 | private ShopOrderMapper shopOrderMapper; 28 | 29 | 30 | @Test 31 | public void testGetShopOrderByShopOrderIdsDao() { 32 | List orderIds = new ArrayList<>(); 33 | orderIds.add(50000280834672L); 34 | List orders = shopOrderDao.getShopOrderByShopOrderIds(orderIds); 35 | Assert.isTrue(orders.get(0).getOrderId().equals(50000280834672L)); 36 | } 37 | 38 | @Test 39 | public void testGetShopOrderByShopOrderIds() { 40 | List orderIds = new ArrayList<>(); 41 | orderIds.add(50000280834672L); 42 | List orders = shopOrderMapper.getShopOrderByShopOrderIds(orderIds); 43 | Assert.isTrue(orders.get(0).getOrderId().equals(50000280834672L)); 44 | } 45 | 46 | @Test 47 | public void testUpdateShopOrder() { 48 | List orderIds = new ArrayList<>(); 49 | orderIds.add(50000280834672L); 50 | ShopOrder shopOrder = new ShopOrder(); 51 | shopOrder.setShipTime(12345678L); 52 | int rows = shopOrderMapper.batchUpdateShopOrderByShopOrderIds(orderIds, shopOrder); 53 | Assert.isTrue(rows == 1); 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tsharding-client/src/test/java/com/mogujie/service/tsharding/test/client/ShardingCaculatorTest.java: -------------------------------------------------------------------------------- 1 | package com.mogujie.service.tsharding.test.client; 2 | 3 | import com.mogujie.trade.tsharding.client.ShardingCaculator; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | /** 8 | * @auther qigong on 5/29/15 8:28 AM. 9 | */ 10 | public class ShardingCaculatorTest { 11 | 12 | 13 | @Test 14 | public void testCaculateTableName() { 15 | 16 | ShardingparaObj para = new ShardingparaObj(); 17 | para.setName("buyerId"); 18 | para.setValue(100000000L); 19 | Assert.assertEquals("TestTable0000", ShardingCaculator.caculateTableName(para.getValue())); 20 | para.setValue(100000128L); 21 | Assert.assertEquals("TestTable0128", ShardingCaculator.caculateTableName(para.getValue())); 22 | para.setValue(100000512L); 23 | Assert.assertEquals("TestTable0000", ShardingCaculator.caculateTableName(para.getValue())); 24 | } 25 | 26 | @Test 27 | public void testCaculateSchemaName() { 28 | 29 | ShardingparaObj para = new ShardingparaObj(); 30 | para.setName("sellerUserId"); 31 | para.setValue(100000000L); 32 | Assert.assertEquals("sellertestschema0000", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); 33 | para.setValue(100000128L); 34 | Assert.assertEquals("sellertestschema0002", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); 35 | para.setName("buyerUserId"); 36 | para.setValue(100000512L); 37 | Assert.assertEquals("testschema0000", ShardingCaculator.caculateSchemaName(para.getName(), para.getValue())); 38 | } 39 | 40 | @Test 41 | public void testCaculateDatasourceName() { 42 | 43 | ShardingparaObj para = new ShardingparaObj(); 44 | para.setName("sellerUserId"); 45 | para.setValue(100000000L); 46 | Assert.assertEquals("seller_ds_0", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); 47 | para.setValue(100000128L); 48 | Assert.assertEquals("seller_ds_0", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); 49 | para.setName("buyerUserId"); 50 | para.setValue(100000511L); 51 | Assert.assertEquals("buyer_ds_1", ShardingCaculator.caculateDatasourceName(para.getName(), para.getValue())); 52 | } 53 | 54 | @Test 55 | public void testgetNumberWithZeroSuffix(){ 56 | Assert.assertEquals("0100", ShardingCaculator.getNumberWithZeroSuffix(100L)); 57 | } 58 | 59 | private class ShardingparaObj { 60 | private String name; 61 | private Long value; 62 | 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | public void setValue(Long value) { 68 | this.value = value; 69 | } 70 | 71 | public String getName() { 72 | return name; 73 | } 74 | 75 | public Long getValue() { 76 | return value; 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/META-INF/support/datasource.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/META-INF/support/service-loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/app.properties: -------------------------------------------------------------------------------- 1 | tesla.application.name = test-service 2 | tesla.owner = tesla 3 | tesla.port = 20019 4 | 5 | tesla.invokerExecutor.corePoolSize = 24 6 | tesla.invokerExecutor.maxPoolSize = 96 7 | tesla.invokerExecutor.keepAliveSeconds = 60 8 | tesla.invokerExecutor.queueCapacity = 5120 9 | 10 | tesla.worker.threads=0 11 | 12 | tesla.registry.ignoreError = true 13 | tesla.monitorTask.timeSpan = 60000 14 | 15 | tesla.network.readIdleTimeout = 0 16 | tesla.network.compressThreshold = 1024 17 | 18 | tsharding.orm.mapperPacakge=com.mogujie.service.tsharding.mapper 19 | tsharding.needEnhancedClasses=com.mogujie.service.tsharding.mapper.ShopOrderMapper -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/jdbc.properties: -------------------------------------------------------------------------------- 1 | # trade0000 master 2 | trade0000.master.url=jdbc:mysql://127.0.0.1/trade0000 3 | trade0000.master.username=test 4 | trade0000.master.password=test 5 | trade0000.master.minPoolSize=0 6 | trade0000.master.maxPoolSize=10 7 | trade0000.master.initialPoolSize=0 8 | # trade0000 slave 9 | trade0000.slave.url=jdbc:mysql://127.0.0.1/trade0000 10 | trade0000.slave.username=test 11 | trade0000.slave.password=test 12 | trade0000.slave.minPoolSize=1 13 | trade0000.slave.maxPoolSize=24 14 | trade0000.slave.initialPoolSize=1 15 | 16 | # trade0001 master 17 | trade0001.master.url=jdbc:mysql://127.0.0.1/trade0001 18 | trade0001.master.username=test 19 | trade0001.master.password=test 20 | trade0001.master.minPoolSize=0 21 | trade0001.master.maxPoolSize=10 22 | trade0001.master.initialPoolSize=0 23 | # trade0001 slave 24 | trade0001.slave.url=jdbc:mysql://127.0.0.1/trade0001 25 | trade0001.slave.username=test 26 | trade0001.slave.password=test 27 | trade0001.slave.minPoolSize=1 28 | trade0001.slave.maxPoolSize=24 29 | trade0001.slave.initialPoolSize=1 30 | 31 | # trade0002 master 32 | trade0002.master.url=jdbc:mysql://127.0.0.1/trade0002 33 | trade0002.master.username=test 34 | trade0002.master.password=test 35 | trade0002.master.minPoolSize=0 36 | trade0002.master.maxPoolSize=10 37 | trade0002.master.initialPoolSize=0 38 | # trade0002 slave 39 | trade0002.slave.url=jdbc:mysql://127.0.0.1/trade0002 40 | trade0002.slave.username=test 41 | trade0002.slave.password=test 42 | trade0002.slave.minPoolSize=1 43 | trade0002.slave.maxPoolSize=24 44 | trade0002.slave.initialPoolSize=1 45 | 46 | # trade0003 master 47 | trade0003.master.url=jdbc:mysql://127.0.0.1/trade0003 48 | trade0003.master.username=test 49 | trade0003.master.password=test 50 | trade0003.master.minPoolSize=0 51 | trade0003.master.maxPoolSize=10 52 | trade0003.master.initialPoolSize=0 53 | # trade0003 slave 54 | trade0003.slave.url=jdbc:mysql://127.0.0.1/trade0003 55 | trade0003.slave.username=test 56 | trade0003.slave.password=test 57 | trade0003.slave.minPoolSize=1 58 | trade0003.slave.maxPoolSize=24 59 | trade0003.slave.initialPoolSize=1 60 | 61 | # trade0004 master 62 | trade0004.master.url=jdbc:mysql://127.0.0.1/trade0004 63 | trade0004.master.username=test 64 | trade0004.master.password=test 65 | trade0004.master.minPoolSize=0 66 | trade0004.master.maxPoolSize=10 67 | trade0004.master.initialPoolSize=0 68 | # trade0004 slave 69 | trade0004.slave.url=jdbc:mysql://127.0.0.1/trade0004 70 | trade0004.slave.username=test 71 | trade0004.slave.password=test 72 | trade0004.slave.minPoolSize=1 73 | trade0004.slave.maxPoolSize=24 74 | trade0004.slave.initialPoolSize=1 75 | 76 | # trade0005 master 77 | trade0005.master.url=jdbc:mysql://127.0.0.1/trade0005 78 | trade0005.master.username=test 79 | trade0005.master.password=test 80 | trade0005.master.minPoolSize=0 81 | trade0005.master.maxPoolSize=10 82 | trade0005.master.initialPoolSize=0 83 | # trade0005 slave 84 | trade0005.slave.url=jdbc:mysql://127.0.0.1/trade0005 85 | trade0005.slave.username=test 86 | trade0005.slave.password=test 87 | trade0005.slave.minPoolSize=1 88 | trade0005.slave.maxPoolSize=24 89 | trade0005.slave.initialPoolSize=1 90 | 91 | # trade0006 master 92 | trade0006.master.url=jdbc:mysql://127.0.0.1/trade0006 93 | trade0006.master.username=test 94 | trade0006.master.password=test 95 | trade0006.master.minPoolSize=0 96 | trade0006.master.maxPoolSize=10 97 | trade0006.master.initialPoolSize=0 98 | # trade0006 slave 99 | trade0006.slave.url=jdbc:mysql://127.0.0.1/trade0006 100 | trade0006.slave.username=test 101 | trade0006.slave.password=test 102 | trade0006.slave.minPoolSize=1 103 | trade0006.slave.maxPoolSize=24 104 | trade0006.slave.initialPoolSize=1 105 | 106 | # trade0007 master 107 | trade0007.master.url=jdbc:mysql://127.0.0.1/trade0007 108 | trade0007.master.username=test 109 | trade0007.master.password=test 110 | trade0007.master.minPoolSize=0 111 | trade0007.master.maxPoolSize=10 112 | trade0007.master.initialPoolSize=0 113 | # trade0007 slave 114 | trade0007.slave.url=jdbc:mysql://127.0.0.1/trade0007 115 | trade0007.slave.username=test 116 | trade0007.slave.password=test 117 | trade0007.slave.minPoolSize=1 118 | trade0007.slave.maxPoolSize=24 119 | trade0007.slave.initialPoolSize=1 120 | 121 | # xdtrade单库 master 122 | xdtrade.master.url=jdbc:mysql://127.0.0.1/trade 123 | xdtrade.master.username=test 124 | xdtrade.master.password=test 125 | xdtrade.master.minPoolSize=0 126 | xdtrade.master.maxPoolSize=10 127 | xdtrade.master.initialPoolSize=0 128 | # rootjie slave 129 | xdtrade.slave.url=jdbc:mysql://127.0.0.1/trade 130 | xdtrade.slave.username=test 131 | xdtrade.slave.password=test 132 | xdtrade.slave.minPoolSize=1 133 | xdtrade.slave.maxPoolSize=24 134 | xdtrade.slave.initialPoolSize=1 135 | 136 | 137 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %d [%t] %5p \(%F:%L\) %M\(\) - %m%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/spring-test.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tsharding-client/src/test/resources/sqlmap/shoporder-mapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | shipTime=#{shopOrder.shipTime}, 13 | 14 | 15 | 16 | 17 | 18 | orderId, 19 | buyerUserId, 20 | sellerUserId 21 | 22 | 23 | 24 | 33 | 34 | 45 | 46 | 47 | 48 | UPDATE TradeOrder 49 | 50 | where orderId in 51 | 52 | #{shopOrderId} 53 | 54 | limit 500 55 | 56 | 57 | --------------------------------------------------------------------------------