001 分库分表_多数据源切换读写分离优化(封装组件,采用地域分表算法)

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值