一:pom引入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-test</artifactId>-->
<!-- <scope>test</scope>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--lombok的依赖,自动生成get/set方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
<!--mybatis-plus 代码生成依赖-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!--mybatis-plus代码生成依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
二:引入配置
1、DynamicDataSource类
package com.future.rabbit.config.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* (切换数据源必须在调用service之前进行,也就是开启事务之前)
* 动态数据源实现类
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
/**
* 设置默认数据源
*
* @param defaultDataSource
*/
public void setDefaultDataSource(Object defaultDataSource) {
super.setDefaultTargetDataSource(defaultDataSource);
}
/**
* 设置数据源
*
* @param dataSources
*/
public void setDataSources(Map<Object, Object> dataSources) {
super.setTargetDataSources(dataSources);
// 将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
DynamicDataSourceContextHolder.addDataSourceKeys(dataSources.keySet());
}
}
2、DynamicDataSourceAspect
package com.future.rabbit.config.datasource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.future.rabbit.config.exception.DatasourceException;
import com.future.rabbit.constant.RabbitConstant;
import com.future.rabbit.mapper.SysDatasourceMapper;
import com.future.rabbit.pojo.Datasource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.annotation.Annotation;
import java.util.Enumeration;
@Slf4j
@Aspect
@Component
@Order(1) // 请注意:这里order一定要小于tx:annotation-driven的order,即先执行DynamicDataSourceAspectAdvice切面,再执行事务切面,才能获取到最终的数据源
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class DynamicDataSourceAspect {
@Autowired
SysDatasourceMapper datasourceMapper;
// /**
// * 普通Controller切面(interfaces)
// */
// @Pointcut("execution(public * com.future.rabbit.controller.*.*(..))&&!execution(public * com.future.rabbit.controller.DatasourceController.*(..))")
// public void addInterfaces() {
// }
//todo 以后需要放行登录接口
/**
* 普通Controller切面(interfaces)
*/
@Pointcut("execution(public * com.future.rabbit.controller.*.*(..))")
public void addInterfaces() {
}
@Around("addInterfaces()")
public Object Interceptor(ProceedingJoinPoint pjp) throws Throwable {
Object result = null;
MethodSignature signature = (MethodSignature) pjp.getSignature();
Annotation anoIgnore = signature.getMethod().getAnnotation(IgnoreDynamicData.class);
//如果是bpm操作业务数据 获取方法中的数据源参数作为拦截目标
if (anoIgnore != null) {
result = pjp.proceed();
} else {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
// 如果有session则返回session如果没有则返回null(避免创建过多的session浪费内存)
HttpSession session = request.getSession(false);
// 获取请求头
String tenantId = "";
Enumeration<String> enumeration = request.getHeaderNames();
while (enumeration.hasMoreElements()) {
String name = enumeration.nextElement();
if (name.equals(RabbitConstant.HEADER_TOKEN) || name.equals(RabbitConstant.HEADER_TOKEN_BACK)) {
String value = request.getHeader(name);
tenantId = value;
break;
}
}
//获取用户所对应的数据源
if (StringUtils.isNotBlank(tenantId)) {
Boolean flag = false;
if (tenantId.equals(RabbitConstant.DEFAULT_DATASOURCE)) {
//默认数据源放行
flag = true;
} else {
//非默认数据源,需要判断是否合法
Datasource datasource = datasourceMapper.selectOne(new QueryWrapper<Datasource>().eq("tenant_id", tenantId));
if (datasource != null) {
flag = true;
}
}
if (flag) {
DynamicDataSourceContextHolder.setDataSourceKey(tenantId);
result = pjp.proceed();
DynamicDataSourceContextHolder.clearDataSourceKey();
} else {
log.error("token验证异常,tenantId不合法,请重新登录获取token");
throw new DatasourceException(401, "token验证异常,tenantId不合法,请重新登录获取token");
}
} else {
log.error("token验证异常,tenantId为空,请重新登录获取token");
throw new DatasourceException(401, "token验证异常,tenantId不合法,请重新登录获取token");
}
}
return result;
}
}
3、DynamicDataSourceContextHolder
package com.future.rabbit.config.datasource;
import com.future.rabbit.constant.RabbitConstant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* (切换数据源必须在调用service之前进行,也就是开启事务之前)
* 动态数据源上下文
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
/**
* 将 master 数据源的 key作为默认数据源的 key
*/
@Override
protected String initialValue() {
return RabbitConstant.DEFAULT_DATASOURCE;
}
};
/**
* 数据源的 key集合,用于切换时判断数据源是否存在
*/
public static List<Object> dataSourceKeys = new ArrayList<>();
/**
* 切换数据源
*
* @param key
*/
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
/**
* 获取数据源
*
* @return
*/
public static String getDataSourceKey() {
return contextHolder.get();
}
/**
* 重置数据源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判断是否包含数据源
*
* @param key 数据源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
/**
* 添加数据源keys
*
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return dataSourceKeys.addAll(keys);
}
}
4、DynamicDataSourceInit
package com.future.rabbit.config.datasource;
import com.future.rabbit.constant.RabbitConstant;
import com.future.rabbit.pojo.Datasource;
import com.future.rabbit.service.impl.SysDatasourceImpl;
import com.zaxxer.hikari.HikariDataSource;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.InlineShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
/**
* 初始化动态数据源
*/
@Slf4j
@Configuration
public class DynamicDataSourceInit {
@Autowired
private SysDatasourceImpl sysDatasource;
@Resource
private ApplicationContext applicationContext;
@PostConstruct
public void InitDataSource() {
DynamicDataSource dynamicDataSource = (DynamicDataSource) applicationContext.getBean(RabbitConstant.DYNAMIC_DATASOURCE);
HikariDataSource master = (HikariDataSource) applicationContext.getBean(RabbitConstant.DEFAULT_DATASOURCE);
log.info("=====初始化动态数据源=====");
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(RabbitConstant.DEFAULT_DATASOURCE, master);
List<Datasource> list = sysDatasource.list();
//新增数据源
if (list != null && list.size() > 0) {
for (Datasource datasource : list) {
HikariDataSource hikariDataSource = new HikariDataSource();
BeanUtils.copyProperties(datasource, hikariDataSource);
dataSourceMap.put(datasource.getTenantId(), hikariDataSource);
}
}
//设置数据源
dynamicDataSource.setDataSources(dataSourceMap);
/**
* 必须执行此操作,才会重新初始化AbstractRoutingDataSource 中的 resolvedDataSources,也只有这样,动态切换才会起效
*/
dynamicDataSource.afterPropertiesSet();
}
}
5、IgnoreDynamicData
package com.future.rabbit.config.datasource;//package com.future.rabbit.config.config1;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 需要被忽略动态数据源的方法
* john
*/
@Target({ElementType.METHOD})
@Retention(RUNTIME)
@Documented
public @interface IgnoreDynamicData {
String value() default "";
}
6、MybatisPlusConfig
package com.future.rabbit.config.datasource;
import com.baomidou.mybatisplus.core.parser.ISqlParser;
import com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.future.rabbit.constant.RabbitConstant;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: guomh
* @Date: 2019/11/06
* @Description: mybatis配置*
*/
@EnableTransactionManagement
@Configuration
@MapperScan({"com.future.rabbit.mapper"})
public class MybatisPlusConfig {
@Bean(RabbitConstant.DEFAULT_DATASOURCE)
@Primary
@ConfigurationProperties(prefix = "spring.datasource.hikari")
public DataSource master() {
return DataSourceBuilder.create().build();
}
@Bean(RabbitConstant.DYNAMIC_DATASOURCE)
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(RabbitConstant.DEFAULT_DATASOURCE, master());
// 将 master 数据源作为默认指定的数据源
dynamicDataSource.setDefaultDataSource(master());
dynamicDataSource.setDataSources(dataSourceMap);
return dynamicDataSource;
}
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactoryBean() throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
/**
* 重点,使分页插件生效
*/
Interceptor[] plugins = new Interceptor[1];
plugins[0] = paginationInterceptor();
sessionFactory.setPlugins(plugins);
//配置数据源,此处配置为关键配置,如果没有将 dynamicDataSource作为数据源则不能实现切换
sessionFactory.setDataSource(dynamicDataSource());
sessionFactory.setTypeAliasesPackage("com.future.rabbit.pojo"); // 扫描Model
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*/*Mapper.xml")); // 扫描映射文件
return sessionFactory;
}
@Bean
public PlatformTransactionManager transactionManager() {
// 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
return new DataSourceTransactionManager(dynamicDataSource());
}
/**
* 加载分页插件
*
* @return
*/
@Bean
public Interceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}
7、DatasourceException
package com.future.rabbit.config.exception;
public class DatasourceException extends RuntimeException {
private Integer code;
private String message;
public DatasourceException(Integer code, String message) {
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
三:启动类配置
注:引入exclude = DataSourceAutoConfiguration.class
package com.future.rabbit;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableAsync
public class RabbitApplication {
public static void main(String[] args) {
SpringApplication.run(RabbitApplication.class, args);
}
}
四:yml配置
server:
port: 8602
tomcat:
uri-encoding: UTF-8
#最大工作线程数,默认200, 4核8g内存,线程数经验值800
#操作系统做线程之间的切换调度是有系统开销的,所以不是越多越好。
max-threads: 1000
#等待队列长度,默认100
accept-count: 1000
max-connections: 20000
#最小工作空闲线程数,默认10, 适当增大一些,以便应对突然增长的访问量
min-spare-threads: 100
spring:
application:
name: rabbit-service
rabbitmq:
host: localhost
port: 5672
username: admin
password: admin
virtual-host: /
#必须配置这个,生产者才会确认回调
#publisher-confirm-type: correlated
#publisher-returns: true
# 手动提交消息
listener:
simple:
acknowledge-mode: manual
direct:
acknowledge-mode: manual
datasource:
#username: root
#password: root
#url: jdbc:mysql://localhost:3306/rabbit_mq?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
#driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
username: root
password: root
jdbc-url: jdbc:mysql://localhost:3306/rabbit_mq?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&autoReconnect=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
mapper-locations: classpath:/mapper/**/*Mapper.xml
typeAliasesPackage: com.future.rabbit.pojo
configuration:
map-underscore-to-camel-case: true