SpringCloud 基于 Hint 算法分片策略分库

1、添加 aop 、sharding-jdbc 依赖

<!--aop->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--sharding-jdbc-->
<dependency>
    <groupId>io.shardingsphere</groupId>
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
    <exclusions>
        <exclusion>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
        </exclusion>
    </exclusions>
</dependency>

  2、yml文件配置

# 数据源
sharding.jdbc.datasource.names=xxx-pub,xxx-ha,xxx-sd,xxx-zj,xxx-hq,xxx-tw,xxx-js,xxx-fj
# 河南
sharding.jdbc.datasource.xxx-ha.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.xxx-ha.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.xxx-ha.jdbcUrl=jdbc:mysql://localhost:3306/xxx_ha?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
sharding.jdbc.datasource.xxx-ha.username=root
sharding.jdbc.datasource.xxx-ha.password=root
 
# 山东
sharding.jdbc.datasource.xxx-sd.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.xxx-sd.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.xxx-sd.jdbcUrl=jdbc:mysql://localhost:3306/xxx_sd?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
sharding.jdbc.datasource.xxx-sd.username=root
sharding.jdbc.datasource.xxx-sd.password=root
 
# 浙江
sharding.jdbc.datasource.xxx-zj.type=com.zaxxer.hikari.HikariDataSource
sharding.jdbc.datasource.xxx-zj.driver-class-name=com.mysql.cj.jdbc.Driver
sharding.jdbc.datasource.xxx-zj.jdbcUrl=jdbc:mysql://localhost:3306/xxx_zj?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
sharding.jdbc.datasource.xxx-zj.username=root
sharding.jdbc.datasource.xxx-zj.password=root

  3、编写 DatasourceRouteAspect 切面类

package com.xxx.xxx.xxx.config.shard;
 
import com.xxx.xxx.common.shard.RequestShardValueHolder;
import io.shardingsphere.api.HintManager;
import io.shardingsphere.core.hint.HintManagerHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
 
/**
 * Description: 数据源强制路由切面,在进入DAO之前,强制根据省分库方案,使用sharding-jdbc实现强制路由
 */
@Component
@Aspect
public class DatasourceRouteAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(DatasourceRouteAspect.class);
 
    /**
     * DAO层切面
     */
    //@Pointcut("execution( * com.xxx.xxx.xxx..*.dao.*Mapper.*(..))")
    @Pointcut("execution( * com.xxx.xxx.xxx..*.*Mapper.*(..))")
    public void pointCut() {
    }
 
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) {
        HintManagerHolder.clear();
        HintManager hintManager = HintManager.getInstance();
 
        String province = RequestShardValueHolder.get();
        LOGGER.info("aspect:current request thread id:{}, province code:{}", Thread.currentThread().getId(), province);
        hintManager.setDatabaseShardingValue(province);
    }
 
    /**
     * 完成之后清理shardvalue
     *
     * @param joinPoint
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint) {
        HintManagerHolder.clear();
    }
}

  4、编写分库路由值实体类

package com.xxx.xxx.common.shard;
 
/**
 * Description: 分库路由值(省份编码)暂存器,提供线程封闭环境
 */
public class RequestShardValueHolder {
    /**
     * 当前目标数据源键值
     */
    private static final ThreadLocal<String> CURRENT_SHARD_VALUE = new ThreadLocal<>();
 
    /**
     * 插入目标数据源键值
     *
     * @param provinceCode 省份编码
     */
    public static void set(String provinceCode) {
        CURRENT_SHARD_VALUE.set(provinceCode);
    }
 
    /**
     * 获取目标数据源键值
     *
     * @return 目标数据源键值
     */
    public static String get() {
        return CURRENT_SHARD_VALUE.get();
    }
 
    public static void remove() {
        CURRENT_SHARD_VALUE.remove();
    }
}

  5、编写 config 

package com.xxx.xxx.xxx.service.config;
 
import com.xxx.xxx.common.shard.RequestShardValueInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
/**
 * Description:
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
 
    @Bean
    public RequestShardValueInterceptor requestShardValueInterceptor() {
        return new RequestShardValueInterceptor();
    }
 
 
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new RequestShardValueInterceptor()).addPathPatterns("/**").excludePathPatterns("/api/bill/**").excludePathPatterns("/**/InquiryNmfaXxxResultSrv");
    }
}

 6、编写拦截器,每次请求都拦截,从session 中获取用户省份,set 到 RequestShardValueHolder 中。

package com.xxx.xxx.common.shard;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
import com.xxx.xxx.common.model.sys.SysUserVo;
import com.xxx.xxx.common.utils.SessionUtil;
 
/**
 */
public class RequestShardValueInterceptor extends HandlerInterceptorAdapter {
	private static final Logger LOGGER = LoggerFactory.getLogger(RequestShardValueInterceptor.class);
 
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// TODO 从request中header中获取province参数作为键值
		SysUserVo userVo = SessionUtil.getSessionUserInfo(request);
		if (userVo == null) {
			LOGGER.warn("无法从http请求的header中解析出用户信息,request url:{},has no shardvalue", request.getRequestURI());
			return true;
		}
		String province = userVo.getPrvCode();
		// 集团用户直接从header中取值
		if (userVo.getLevel() == 0) {
			province = SessionUtil.getUserProvince(request);
			LOGGER.info("---------集团用户,分库取值{}", province);
		}
		if (province == null) {
			LOGGER.warn("user:{},has no shardvalue", userVo);
			return true;
		}
		LOGGER.info("filter:current request thread id:{}, province code:{}", Thread.currentThread().getId(), province);
		// 在Threalocal中添加当前线程的province code 作为目标数据源键值
		RequestShardValueHolder.set(province.toLowerCase());
		return true;
	}
 
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		String province = RequestShardValueHolder.get();
		LOGGER.info("interceptor:current request thread id:{}, province code:{}", Thread.currentThread().getId(),
				province);
		RequestShardValueHolder.remove();
		LOGGER.info("afterCompletion");
	}
 
}

  

7、编写Hint分片策略类(自定义分片策略)

package com.xxx.xxx.xxx.config.share;
 
 
import io.shardingsphere.api.algorithm.sharding.ListShardingValue;
import io.shardingsphere.api.algorithm.sharding.ShardingValue;
import io.shardingsphere.api.algorithm.sharding.hint.HintShardingAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
import java.util.Collection;
import java.util.HashSet;
 
/**
 * Description: 自定义hint分片策略,使用提示而不是SQL来指示分片结果。
 */
public class ProvinceHintShardingAlgorithm implements HintShardingAlgorithm {
    private static final Logger LOGGER = LoggerFactory.getLogger(ProvinceHintShardingAlgorithm.class);
    private static final String DEFAULT_DATA_SOURCE = "xxx-pub";
 
    @Override
    public Collection<String> doSharding(Collection<String> availableTargetNames, ShardingValue shardingValue) {
        Collection<String> result = new HashSet<>();
        if (shardingValue instanceof ListShardingValue) {
            Collection shardingValues = ((ListShardingValue) shardingValue).getValues();
            //TODO 解析出对应省份代码的数据源
            if (availableTargetNames != null && !availableTargetNames.isEmpty() && shardingValues != null && !shardingValues.isEmpty()) {
                for (String availableName : availableTargetNames) {
                    for (Object value : shardingValues) {
                        if (value != null && availableName.endsWith(value.toString().toLowerCase())) {
                            result.add(availableName);
                        }
                    }
                }
            }
        }
 
        //匹配到目标库,直接返回目标库
        if (!result.isEmpty()) {
            LOGGER.info("force route data source to:{}", result);
        } else {
            LOGGER.warn("has no target datasource, route to datasource: {}", DEFAULT_DATA_SOURCE);
            result.add(DEFAULT_DATA_SOURCE);
        }
 
        return result;
    }
}

  最终:前端访问接口,在 mapper 每次访问数据库的时候,会先执行 aspect 切面,去确定执行哪个省份库,系统才会对对应的库进行  CRUD 操作。

 

本文转载自:https://blog.csdn.net/qq_39415129/article/details/106115503

 

  1.  
    <dependency>
  2.  
    <groupId>org.springframework.boot</groupId>
  3.  
    <artifactId>spring-boot-starter-aop</artifactId>
  4.  
    </dependency>
  5.  
    <!--sharding-jdbc-->
  6.  
    <dependency>
  7.  
    <groupId>io.shardingsphere</groupId>
  8.  
    <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  9.  
    <exclusions>
  10.  
    <exclusion>
  11.  
    <groupId>com.google.guava</groupId>
  12.  
    <artifactId>guava</artifactId>
  13.  
    </exclusion>
  14.  
    </exclusions>
  15.  
    </dependency>
posted @ 2020-09-01 16:08  looyee  阅读(858)  评论(0编辑  收藏  举报