1.数据库层面Mysql(MariaDB),实现读写分离、主从切换、数据源切换:
首先实现读写分离,就意味着需要有两个数据源,当写操作的时候对主库(master)使用,当读操作的时候对从库(slave)使用。那么在启动数据库连接池的时候即要启动两个,在实际使用时可以在方法上加上自定义注解的形式来区分读写。
2.实现解析:
(1)配置好两个druid数据源,然后要区分开两个数据源:分别确定主、从数据源。
(2)通过mybatis配置文件把两个数据源注入到应用中,但要想实现读写分离,也就是能确定是吗情况下用写,什么情况下用读,需要自定义一个标示来区分。
(3)要实现一个即时切换主从数据源的标识,并且能够保证线程足够安全的基础下进行操作数据源(并发会影响数据源的获取、分不清主从,当发生从库进行写操作会影响MariaDB数据库机制,导致数据库服务器异常,当然也可以进行恢复,但千万小心谨慎,所以要使用threadlocal来解决这个问题,使用threadlocal来存储数据源的标识,判断主从库)。
(4)最后实现自定义注解,在方法上有注解则只读,无注解默认为写操作。
3.数据源实现:
step1. 创建SplitDruidProperties配置类封装shus数据源:
import org.springframework.context.annotation.Configuration;
import lombok.Data;
@Data
@Configuration
public class SplitDruidProperties {
private String type = "com.alibaba.druid.pool.DruidDataSource";
private String driver = "com.mysql.jdbc.Driver";
//private String driver = "com.mysql.cj.jdbc.Driver";
private String bjOrderMasterUrl = "jdbc:mysql://192.168.85.140:3306/bjorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String shOrderMasterUrl = "jdbc:mysql://192.168.85.140:3306/shorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String szOrderMasterUrl = "jdbc:mysql://192.168.85.140:3306/szorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String hzOrderMasterUrl = "jdbc:mysql://192.168.85.140:3306/hzorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String bjOrderSlaveUrl = "jdbc:mysql://192.168.85.142:3306/bjorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String shOrderSlaveUrl = "jdbc:mysql://192.168.85.142:3306/shorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String szOrderSlaveUrl = "jdbc:mysql://192.168.85.142:3306/szorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String hzOrderSlaveUrl = "jdbc:mysql://192.168.85.142:3306/hzorder?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true";
private String username = "root";
private String password = "123456";
private int initialSize = 5;
private int minIdle = 1;
private int maxIdle = 10;
private int maxActive = 100;
private int maxWait = 60000;
private int timeBetweenEvictionRunsMillis = 60000;
private int minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 1 FROM DUAL";
private boolean testWhileIdle = true;
private boolean testOnBorrow= false;
private boolean testOnReturn= false;
private boolean poolPreparedStatements= true;
private int maxPoolPreparedStatementPerConnectionSize = 20;
//private String[] filters= {"stat","wall","log4j"};
private boolean useGlobalDataSourceStat = true;
}
step2. 将数据源注入容器:
import java.sql.SQLException;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
/*
* 将配置中的数据源注入
*/
@Configuration //相当于一个xml配置文件
public class DataSourceConfiguration {
private static Logger LOGGER = org.slf4j.LoggerFactory.getLogger(DataSourceConfiguration.class);
@Autowired
private SplitDruidProperties splitDruidProperties;
@Value("${druid.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name = "bjorder-master")
@Primary
public DataSource bjorderMasterDataSource() throws SQLException {
DataSource bjorderMasterDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getBjOrderMasterUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= bjorderMasterDataSource: {} ================", bjorderMasterDataSource);
return bjorderMasterDataSource;
}
@Bean(name = "shorder-master")
public DataSource shorderMasterDataSource() throws SQLException {
DataSource shorderMasterDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getShOrderMasterUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= shorderMasterDataSource: {} ================", shorderMasterDataSource);
return shorderMasterDataSource;
}
@Bean(name = "szorder-master")
public DataSource szorderMasterDataSource() throws SQLException {
DataSource szorderMasterDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getSzOrderMasterUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= szorderMasterDataSource: {} ================", szorderMasterDataSource);
return szorderMasterDataSource;
}
@Bean(name = "hzorder-master")
public DataSource hzorderMasterDataSource() throws SQLException {
DataSource hzorderMasterDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getHzOrderMasterUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= hzorderMasterDataSource: {} ================", hzorderMasterDataSource);
return hzorderMasterDataSource;
}
@Bean(name = "bjorder-slave")
public DataSource bjorderSlaveDataSource() throws SQLException {
DataSource bjorderSlaveDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getBjOrderSlaveUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= bjorderSlaveDataSource: {} ================", bjorderSlaveDataSource);
return bjorderSlaveDataSource;
}
@Bean(name = "shorder-slave")
public DataSource shorderSlaveDataSource() throws SQLException {
DataSource shorderSlaveDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getShOrderSlaveUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= shorderSlaveDataSource: {} ================", shorderSlaveDataSource);
return shorderSlaveDataSource;
}
@Bean(name = "szorder-slave")
public DataSource szorderSlaveDataSource() throws SQLException {
DataSource szorderSlaveDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getSzOrderSlaveUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= szorderSlaveDataSource: {} ================", szorderSlaveDataSource);
return szorderSlaveDataSource;
}
@Bean(name = "hzorder-slave")
public DataSource hzorderSlaveDataSource() throws SQLException {
DataSource hzorderSlaveDataSource = DataSourceBuilder.create()
.type(dataSourceType)
.driverClassName(splitDruidProperties.getDriver())
.url(splitDruidProperties.getHzOrderSlaveUrl())
.username(splitDruidProperties.getUsername())
.password(splitDruidProperties.getPassword())
.build();
LOGGER.info("============= hzorderSlaveDataSource: {} ================", hzorderSlaveDataSource);
return hzorderSlaveDataSource;
}
@Bean
public ServletRegistrationBean druidServlet(){
//spring boot 的方式 自己写一个servlet
ServletRegistrationBean reg = new ServletRegistrationBean();
reg.setServlet(new StatViewServlet());
reg.addUrlMappings("/druid/*");
reg.addInitParameter("allow", "192.168.1.1");
//reg.addInitParameter("deny", "/deny");
LOGGER.info("============= init druid servlet ================");
return reg;
}
@Bean
public FilterRegistrationBean druidFilter(){
FilterRegistrationBean ftr = new FilterRegistrationBean();
ftr.setFilter(new WebStatFilter());
ftr.addUrlPatterns("/*");
ftr.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico, /druid/*");
LOGGER.info("============= init druid filter ================");
return ftr;
}
}
step3. 将数据源配置交由mybatis管理:
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import com.cc.config.database.DataBaseContextHolder.DataBaseType;
@Configuration
//启用事务注解(此处关闭事务)@EnableTransactionManagement
//当DataSourceConfiguration加载完成后加载MyBatisConfiguration
//将DataSourceConfiguration中定义的8个数据源加载到MyBatis;
@AutoConfigureAfter(value = {DataSourceConfiguration.class,SelectConnectionInterceptor.class})
public class MyBatisConfiguration {
@Autowired
private SelectConnectionInterceptor selectConnectionInterceptor;
@Resource(name= "bjorder-master")
private DataSource bjOrderMasterDataSource;
@Resource(name= "shorder-master")
private DataSource shOrderMasterDataSource;
@Resource(name= "szorder-master")
private DataSource szOrderMasterDataSource;
@Resource(name= "hzorder-master")
private DataSource hzOrderMasterDataSource;
@Resource(name= "bjorder-slave")
private DataSource bjOrderSlaveDataSource;
@Resource(name= "shorder-slave")
private DataSource shOrderSlaveDataSource;
@Resource(name= "szorder-slave")
private DataSource szOrderSlaveDataSource;
@Resource(name= "hzorder-slave")
private DataSource hzOrderSlaveDataSource;
/*
* 实现动态切换数据源,需要一个代理完成,DynamicDataSource自己创建,通过实现mybatis的AbstractRoutingDataSource
* 就可以完成数据源的动态切换;
*/
@Bean("dynamicDataSource")
public DynamicDataSource roundRobinDataSourceProxy(){
Map<Object, Object> targetDataSource = new HashMap<Object, Object>();
targetDataSource.put(DataBaseType.BJORDER_MASTER, bjOrderMasterDataSource);
targetDataSource.put(DataBaseType.SHORDER_MASTER, shOrderMasterDataSource);
targetDataSource.put(DataBaseType.SZORDER_MASTER, szOrderMasterDataSource);
targetDataSource.put(DataBaseType.HZORDER_MASTER, hzOrderMasterDataSource);
targetDataSource.put(DataBaseType.BJORDER_SLAVE, bjOrderSlaveDataSource);
targetDataSource.put(DataBaseType.SHORDER_SLAVE, shOrderSlaveDataSource);
targetDataSource.put(DataBaseType.SZORDER_SLAVE, szOrderSlaveDataSource);
targetDataSource.put(DataBaseType.HZORDER_SLAVE, hzOrderSlaveDataSource);
// 实例化动态数据源
DynamicDataSource proxy = new DynamicDataSource();
// 盛放所以需要切换的数据源
proxy.setTargetDataSources(targetDataSource);
// 设置默认的数据源
proxy.setDefaultTargetDataSource(bjOrderMasterDataSource);
return proxy;
}
/*
* 加载完成后创建sqlSessionFactory,将数据源交由MyBatis管理,将数据源池化;
* 将动态切换代理加入到sqlSessionFactory中;
*/
@Bean(name="sqlSessionFactory")
public SqlSessionFactory sqlSessionFactoryBean(DynamicDataSource dynamicDataSource) {
System.err.println("----------------执行--------------");
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
//添加mybatis自定义拦截器
bean.setPlugins(new Interceptor[] {selectConnectionInterceptor});
bean.setDataSource(dynamicDataSource);
// 添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("classpath:com/cc/mapping/*.xml"));
SqlSessionFactory sqlSessionFactory = bean.getObject();
sqlSessionFactory.getConfiguration().setCacheEnabled(Boolean.TRUE);
return sqlSessionFactory;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
step4. 创建数据源切换类:
/*
* 通过一个enum(枚举)定义标示主从库
*/
public class DataBaseContextHolder {
public enum DataBaseType {
BJORDER_MASTER("bjorder-master"),
SHORDER_MASTER("shorder-master"),
SZORDER_MASTER("szorder-master"),
HZORDER_MASTER("hzorder-master"),
BJORDER_SLAVE("bjorder-slave"),
SHORDER_SLAVE("shorder-slave"),
SZORDER_SLAVE("szorder-slave"),
HZORDER_SLAVE("hzorder-slave");
private String code;
private DataBaseType(String code){
this.code = code;
}
public String getCode(){
return code;
}
}
/*
* 此处应用ThreadLocal;
* 可以优化选用netty的FastThreadLocal,性能会更好;
* ThreadLocal中存储的内容是当前请求读取的是那个库,DataBaseType存储的是具体库的指向,
* 通过DataBaseType来判断具体数据源,切换数据库;
*/
private static final ThreadLocal<DataBaseType> contextHolder = new ThreadLocal<DataBaseType>();
/**
* <B>方法名称:</B>设置数据源类型<BR>
* <B>概要说明:</B><BR>
* 通过此方法设置具体的数据源
*/
public static void setDataBaseType(DataBaseType dataBaseType) {
if(dataBaseType == null) throw new NullPointerException();
contextHolder.set(dataBaseType);
}
public static DataBaseType getDataBaseType(){
//如果获取的DataBaseType为null默认选择ORDER1_MASTER("order1-master"),如果不为null则按照获取确定数据源
return contextHolder.get() == null ? DataBaseType.BJORDER_MASTER : contextHolder.get();
}
public static void clearDataBaseType(){
contextHolder.remove();
}
}
step5. 创建切换数据源代理类:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 动态的去切换数据源的类型
return DataBaseContextHolder.getDataBaseType();
}
}
数据源部分结束;
4.分库分表的算法实现(此处用到mybatis拦截器):
step1. 自定义注解实现分库分表与sql语句拦截:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
* 自定义注解:
* @Target 表示此注解要应用的位置(默认不加参数可以应用到任何地方)
* @Retention 配置注解应用时机(不加参数默认CLASS类加载阶段,三种状态包含关系,RUNTIME为全包含)
* @Documented 配置生成文档
* @Inheritance 配置子类是否可以集成
* 自定义注解一般使用前两个配置即可
* 注意:每一个注解本事就是要一个接口,每一个注解默认继承java.lang.annotation.Annotation
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SelectConnection {
String name() default "";
boolean readOnly() default false; //只读分配slave
boolean Inter() default true; //默认连接sql
}
step2. 自定义注解解析,分库分表算法应用及拦截操作的核心链路实现:
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import com.cc.config.database.DataBaseContextHolder.DataBaseType;
import com.cc.utils.Pair;
import com.cc.utils.SelectorUtil;
/**
* 核心链路实现
*/
@Aspect
@Component
@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})})
public class SelectConnectionInterceptor implements Ordered,Interceptor {
private static Logger LOGGER = org.slf4j.LoggerFactory.getLogger(SelectConnectionInterceptor.class);
private static String tableName = "";
private static final String SUFFIX_MASTER = "-master";
private static final String SUFFIX_SLAVE = "-slave";
//注解被调用的时候开始解析
@Around("@annotation(selectConnection)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, SelectConnection selectConnection) throws Throwable {
try{
//1 执行方法前(在加selectConnection的方法执行前进行解析)
LOGGER.info("--------------- select database source ---------------");
String currentDataBaseName = "";
// 如果在注解上添加了: name那么就按照其去获取
if(!StringUtils.isBlank(selectConnection.name())){
currentDataBaseName = selectConnection.name();
} else { //未指定注解selectConnection参数,按照传入(调用方法时参数)进行解析
String uuid = (String) proceedingJoinPoint.getArgs()[0]; // uuid
Pair<String, String> pair = SelectorUtil.getDataBaseAndTable(uuid);
currentDataBaseName = pair.getObject1();
tableName = pair.getObject2();
}
//判断readOnly,切换主从读写分离
if(selectConnection.readOnly()){
currentDataBaseName = currentDataBaseName + SUFFIX_SLAVE;
} else {
currentDataBaseName = currentDataBaseName + SUFFIX_MASTER;
}
for(DataBaseType type: DataBaseContextHolder.DataBaseType.values()){
if(!StringUtils.isBlank(currentDataBaseName)){
String typeCode = type.getCode();
if(typeCode.equals(currentDataBaseName)){
DataBaseContextHolder.setDataBaseType(type);//设置切换
System.err.println("----Interceptor: code :" + DataBaseContextHolder.getDataBaseType().getCode());
}
}
}
//2 开始执行方法
Object result = proceedingJoinPoint.proceed();
//3 执行方法后
return result;
} finally {
DataBaseContextHolder.clearDataBaseType();
LOGGER.info("---------------clear database connection---------------");
}
}
@Override
public int getOrder() {
// TODO Auto-generated method stub
return -1;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
LOGGER.info("---------------MyInterceptor---------------");
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//通过MetaObject优雅访问对象的属性,这里是访问statementHandler的属性;:MetaObject是Mybatis提供的一个用于方便、
//优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject
.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
//先拦截到RoutingStatementHandler,里面有个StatementHandler类型的delegate变量,其实现类是BaseStatementHandler,然后就到BaseStatementHandler的成员变量mappedStatement
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
//id为执行的mapper方法的全路径名,如com.uv.dao.UserMapper.insertUser
String id = mappedStatement.getId();
//sql语句类型 select、delete、insert、update
String sqlCommandType = mappedStatement.getSqlCommandType().toString();
LOGGER.info("---------------sql语句类型:{}---------------",sqlCommandType);
//数据库连接信息
// Configuration configuration = mappedStatement.getConfiguration();
// ComboPooledDataSource dataSource = (ComboPooledDataSource)configuration.getEnvironment().getDataSource();
// dataSource.getJdbcUrl();
BoundSql boundSql = statementHandler.getBoundSql();
//获取到原始sql语句
String sql = boundSql.getSql();
//boundSql.getParameterMappings();
LOGGER.info("----传入的原始sql语句:{}--------",sql);
/**
* 根据语句类型进行sql语句的修改,此处修改基本一致,分类型操作是方便后期优化
* 或有不同参数修改需求情况;
*/
if(sqlCommandType.equals("SELECT")) {
sql= sql.replaceAll("TABLE", tableName);
LOGGER.info("-------修改后sql语句:{}---------",sql);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
else if(sqlCommandType.equals("DELETE")){
sql= sql.replaceAll("TABLE", tableName);
LOGGER.info("-------修改后sql语句:{}---------",sql);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
else if(sqlCommandType.equals("UPDATE")){
sql= sql.replaceAll("TABLE", tableName);
LOGGER.info("-------修改后sql语句:{}---------",sql);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
else if(sqlCommandType.equals("INSERT")){
sql= sql.replaceAll("TABLE", tableName);
LOGGER.info("-------修改后sql语句:{}---------",sql);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
1.拦截器实现条件,需要引入封装的开发人员在mapper.xml中默认添加所有的数据库表名均为“TABLE”,可修改;
2.分库依据城市为库的索引依据,在数据生产是即注入到OrderId中,使用时进行解析;分表按照月为时间单位进行每月一表,tong同样注入到OrderId中,此处演示未进行加密解密设置,可优化;
step3. 分库编码对照类(简单实现)
import java.util.HashMap;
import java.util.Map;
public class DataBaseUnie {
static Map<String,String> m = new HashMap();
public String getDataBaseUnie(String id) {
m.put("1001", "bj");
m.put("1002", "sh");
m.put("1003", "sz");
m.put("1004", "hz");
String dataBase = m.get(id);
return dataBase;
}
}
step4. 分库分表算法实现类
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.cc.utils.Pair;
@Component
//数据源切换实现
public class SelectorUtil {
//@Autowired
static DataBaseUnie dataBaseUnie = new DataBaseUnie();
//传入uuid按照地域和时间查找表名
public static Pair<String, String> getDataBaseAndTable(String uuid) {
//取uuid前四位
String dataBase = dataBaseUnie.getDataBaseUnie(uuid.substring(0, 4))+"order";
//定位表
String selectTable = dataBaseUnie.getDataBaseUnie(uuid.substring(0, 4))+uuid.substring(4, 10);
System.err.println("----------- SelectorUtil: selectDataBase: " + dataBase + " ----------------");
System.err.println("----------- SelectorUtil: selectTable: " + selectTable + " ----------------");
return new Pair<String, String>(dataBase, selectTable);
}
//模拟用户IP取用户区域地址作为UUID生产的前四位,此处用随机生产
public static String getRandomAddress() {
String randomAddress = "";
String[] address = {"1001", "1002", "1003", "1004"};
int index = (int) (Math.random() * address.length);
randomAddress = address[index];
return randomAddress;
}
//取年月作为UUID生产的5至10位
public static String getDate() {
SimpleDateFormat df = new SimpleDateFormat("yyyyMM");//设置日期格式
String nowDate = df.format(new Date());
return nowDate;
}
}
基本实现思路如上,具体代码:https://github.com/wanheyigu/component-split