diff --git a/pom.xml b/pom.xml index ce9b601720eb212c604b1a763df40579463fb608..ec9f8100405137f10018b31b1ebf9ceecc7b4f9a 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,12 @@ commons-beanutils commons-beanutils - 1.8.3 + 1.9.4 + + + commons-lang + commons-lang + 2.6 com.alibaba diff --git a/src/main/java/cn/bugstack/middleware/db/router/DBContextHolder.java b/src/main/java/cn/bugstack/middleware/db/router/DBContextHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..7a942d986c1b07afa48dbf72805d66620fc9a428 --- /dev/null +++ b/src/main/java/cn/bugstack/middleware/db/router/DBContextHolder.java @@ -0,0 +1,38 @@ +package cn.bugstack.middleware.db.router; + +/** + * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! + * 公众号:bugstack虫洞栈 + * Create by 小傅哥(fustack) + */ +public class DBContextHolder { + + private static final ThreadLocal dbKey = new ThreadLocal(); + private static final ThreadLocal tbKey = new ThreadLocal(); + + public static void setDBKey(String dbKeyIdx){ + dbKey.set(dbKeyIdx); + } + + public static String getDBKey(){ + return dbKey.get(); + } + + public static void setTBKey(String tbKeyIdx){ + tbKey.set(tbKeyIdx); + } + + public static String getTBKey(){ + return tbKey.get(); + } + + public static void clearDBKey(){ + dbKey.remove(); + } + + public static void clearTBKey(){ + tbKey.remove(); + } + + +} diff --git a/src/main/java/cn/bugstack/middleware/db/router/DBRouterBase.java b/src/main/java/cn/bugstack/middleware/db/router/DBRouterBase.java new file mode 100644 index 0000000000000000000000000000000000000000..66d7205d44eadb72d71b1b7336667c38ccfd3c6d --- /dev/null +++ b/src/main/java/cn/bugstack/middleware/db/router/DBRouterBase.java @@ -0,0 +1,16 @@ +package cn.bugstack.middleware.db.router; + +/** + * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! + * 公众号:bugstack虫洞栈 + * Create by 小傅哥(fustack) + */ +public class DBRouterBase { + + private String tbIdx; + + public String getTbIdx() { + return DBContextHolder.getTBKey(); + } + +} diff --git a/src/main/java/cn/bugstack/middleware/db/router/DBRouterConfig.java b/src/main/java/cn/bugstack/middleware/db/router/DBRouterConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..2f783636baad11963ba1e990b5dbaadfcab0686c --- /dev/null +++ b/src/main/java/cn/bugstack/middleware/db/router/DBRouterConfig.java @@ -0,0 +1,37 @@ +package cn.bugstack.middleware.db.router; + +/** + * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! + * 公众号:bugstack虫洞栈 + * Create by 小傅哥(fustack) + */ +public class DBRouterConfig { + + private int dbCount; //分库数 + private int tbCount; //分表数 + + public DBRouterConfig() { + } + + public DBRouterConfig(int dbCount, int tbCount) { + this.dbCount = dbCount; + this.tbCount = tbCount; + } + + public int getDbCount() { + return dbCount; + } + + public void setDbCount(int dbCount) { + this.dbCount = dbCount; + } + + public int getTbCount() { + return tbCount; + } + + public void setTbCount(int tbCount) { + this.tbCount = tbCount; + } + +} diff --git a/src/main/java/cn/bugstack/middleware/db/router/DBRouterJoinPoint.java b/src/main/java/cn/bugstack/middleware/db/router/DBRouterJoinPoint.java new file mode 100644 index 0000000000000000000000000000000000000000..c4fd1bdbc6af427c68d745a71aec265a98324be3 --- /dev/null +++ b/src/main/java/cn/bugstack/middleware/db/router/DBRouterJoinPoint.java @@ -0,0 +1,92 @@ +package cn.bugstack.middleware.db.router; + +import cn.bugstack.middleware.db.router.annotation.DBRouter; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.lang.StringUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! + * 公众号:bugstack虫洞栈 + * Create by 小傅哥(fustack) + */ +@Aspect +@Component("db-router-point") +public class DBRouterJoinPoint { + + private Logger logger = LoggerFactory.getLogger(DBRouterJoinPoint.class); + + @Autowired + private DBRouterConfig dbRouterConfig; + + @Pointcut("@annotation(cn.bugstack.middleware.db.router.annotation.DBRouter)") + public void aopPoint() { + } + + @Around("aopPoint()") + public Object doRouter(ProceedingJoinPoint jp) throws Throwable { + Method method = getMethod(jp); + DBRouter dbRouter = method.getAnnotation(DBRouter.class); +// String dbKey = dbRouter.key(); + String dbKey = "userId"; + if (StringUtils.isBlank(dbKey)) throw new RuntimeException("annotation DBRouter key is null!"); + // 计算路由 + String dbKeyAttr = getAttrValue(dbKey, jp.getArgs()); + int size = dbRouterConfig.getDbCount() * dbRouterConfig.getTbCount(); + // 扰动函数 + int idx = (size - 1) & (dbKeyAttr.hashCode() ^ (dbKeyAttr.hashCode() >>> 16)); + // 库表索引 + int dbIdx = idx / dbRouterConfig.getTbCount() + 1; + int tbIdx = idx - dbRouterConfig.getTbCount() * (dbIdx - 1); + // 设置到ThreadLocal + DBContextHolder.setDBKey(String.format("%02d", dbIdx)); + DBContextHolder.setTBKey(String.format("%02d", tbIdx)); + logger.info("数据库路由 method:{} dbIdx:{} tbIdx:{}", method.getName(), dbIdx, tbIdx); + try { + return jp.proceed(); + } finally { + DBContextHolder.clearDBKey(); + DBContextHolder.clearTBKey(); + } + } + + private Method getMethod(JoinPoint jp) throws NoSuchMethodException { + Signature sig = jp.getSignature(); + MethodSignature methodSignature = (MethodSignature) sig; + return getClass(jp).getMethod(methodSignature.getName(), methodSignature.getParameterTypes()); + } + + private Class getClass(JoinPoint jp) throws NoSuchMethodException { + return jp.getTarget().getClass(); + } + + public String getAttrValue(String attr, Object[] args) { + String filedValue = null; + for (Object arg : args) { + try { + if (StringUtils.isBlank(filedValue)) { + filedValue = BeanUtils.getProperty(arg, attr); + } else { + break; + } + } catch (Exception e) { + logger.error("获取路由属性值失败 attr:{}", attr, e); + } + } + return filedValue; + } + +} diff --git a/src/main/java/cn/bugstack/middleware/db/router/annotation/DBRouter.java b/src/main/java/cn/bugstack/middleware/db/router/annotation/DBRouter.java new file mode 100644 index 0000000000000000000000000000000000000000..bf258e19fce37ba9f9a6e210b618d18eae91390b --- /dev/null +++ b/src/main/java/cn/bugstack/middleware/db/router/annotation/DBRouter.java @@ -0,0 +1,16 @@ +package cn.bugstack.middleware.db.router.annotation; + +import java.lang.annotation.*; + +/** + * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! + * 公众号:bugstack虫洞栈 + * Create by 小傅哥(fustack) + */ +//@Retention(RetentionPolicy.RUNTIME) +//@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface DBRouter { + + String key() default ""; + +} diff --git a/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceAutoConfig.java b/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceAutoConfig.java index 805b28f0d476717997ff4dcee2101c8b37b894d4..cb5735707f600249f18f97f300491651f3683282 100644 --- a/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceAutoConfig.java +++ b/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceAutoConfig.java @@ -1,5 +1,6 @@ package cn.bugstack.middleware.db.router.config; +import cn.bugstack.middleware.db.router.DBRouterConfig; import cn.bugstack.middleware.db.router.dynamic.DynamicDataSource; import cn.bugstack.middleware.db.router.util.PropertyUtil; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -24,27 +25,41 @@ import java.util.Map; @Configuration public class DataSourceAutoConfig implements EnvironmentAware { + private Map> dataSourceMap = new HashMap<>(); + + private int dbCount; //分库数 + private int tbCount; //分表数 + @Bean - public DataSource dataSource() { + public DBRouterConfig dbRouterConfig() { + return new DBRouterConfig(dbCount, tbCount); + } + @Bean + public DataSource dataSource() { + // 创建数据源 Map targetDataSources = new HashMap<>(); - targetDataSources.put("db01", new DriverManagerDataSource("jdbc:mysql://127.0.0.1:3306/bugstack?useUnicode=true", "root", "1234")); - + for (String dbInfo : dataSourceMap.keySet()) { + Map objMap = dataSourceMap.get(dbInfo); + targetDataSources.put(dbInfo, new DriverManagerDataSource(objMap.get("url").toString(), objMap.get("username").toString(), objMap.get("password").toString())); + } + // 设置数据源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); - return dynamicDataSource; } @Override public void setEnvironment(Environment environment) { String prefix = "router.jdbc.datasource."; - String dataSources = environment.getProperty(prefix + "list"); - assert dataSources != null; - for (String each : dataSources.split(",")) { - Map dataSourceProps = PropertyUtil.handle(environment, prefix + each, Map.class); + dbCount = Integer.valueOf(environment.getProperty(prefix + "dbCount")); + tbCount = Integer.valueOf(environment.getProperty(prefix + "tbCount")); + String dataSources = environment.getProperty(prefix + "list"); + for (String dbInfo : dataSources.split(",")) { + Map dataSourceProps = PropertyUtil.handle(environment, prefix + dbInfo, Map.class); + dataSourceMap.put(dbInfo, dataSourceProps); } } diff --git a/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceProperties.java b/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceProperties.java deleted file mode 100644 index 12cf347d4cf29b9ec444cbf6dea3a9d6886a8c5d..0000000000000000000000000000000000000000 --- a/src/main/java/cn/bugstack/middleware/db/router/config/DataSourceProperties.java +++ /dev/null @@ -1,51 +0,0 @@ -package cn.bugstack.middleware.db.router.config; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! - * 公众号:bugstack虫洞栈 - * Create by 小傅哥(fustack) - */ -public class DataSourceProperties { - - public static final String PREFIX = "spring.datasource"; - - private String username; - private String password; - private String url; - private String driverClassName; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - public String getDriverClassName() { - return driverClassName; - } - - public void setDriverClassName(String driverClassName) { - this.driverClassName = driverClassName; - } - -} diff --git a/src/main/java/cn/bugstack/middleware/db/router/dynamic/DynamicDataSource.java b/src/main/java/cn/bugstack/middleware/db/router/dynamic/DynamicDataSource.java index b13eb769a6262dfed495047796658c64c48c8d79..6a98df39db8e546297db3bb879f69fef98eeee1b 100644 --- a/src/main/java/cn/bugstack/middleware/db/router/dynamic/DynamicDataSource.java +++ b/src/main/java/cn/bugstack/middleware/db/router/dynamic/DynamicDataSource.java @@ -1,5 +1,6 @@ package cn.bugstack.middleware.db.router.dynamic; +import cn.bugstack.middleware.db.router.DBContextHolder; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** @@ -11,7 +12,7 @@ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { - return "db01"; + return "db" + DBContextHolder.getDBKey(); } } diff --git a/src/test/java/cn/bugstack/middleware/test/ApiTest.java b/src/test/java/cn/bugstack/middleware/test/ApiTest.java index 6a18a4f517ff8e702573920035f4d0192d475c57..d30ab4412298b6d0ec8ddc43e20d6be2f2011417 100644 --- a/src/test/java/cn/bugstack/middleware/test/ApiTest.java +++ b/src/test/java/cn/bugstack/middleware/test/ApiTest.java @@ -1,5 +1,7 @@ package cn.bugstack.middleware.test; +import org.junit.Test; + /** * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获! * 公众号:bugstack虫洞栈 @@ -11,4 +13,29 @@ public class ApiTest { System.out.println("Hi"); } + @Test + public void test_db_hash() { + String key = "小傅哥"; + + int dbCount = 2, tbCount = 32; + int size = dbCount * tbCount; + // 散列 + int idx = (size - 1) & (key.hashCode() ^ (key.hashCode() >>> 16)); + + System.out.println(idx); + + int dbIdx = idx / tbCount + 1; + int tbIdx = idx - tbCount * (dbIdx - 1); + + System.out.println(dbIdx); + System.out.println(tbIdx); + + } + + @Test + public void test_str_format(){ + System.out.println(String.format("db%02d", 1)); + System.out.println(String.format("_%02d", 25)); + } + }