spring boot mybatis 多数据源配置

时间:2023-03-09 06:46:36
spring boot  mybatis 多数据源配置
package com.xynet.statistics.config.dataresources;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源
* Copyright © 2019 xynet Tech Ltd. All rights reserved
* @author: sund
* @date: 2019年3月23日 下午4:35:39
* @remark:
*/
public class DynamicDataSource extends AbstractRoutingDataSource { /**
* 代码中的determineCurrentLookupKey方法取得一个字符串, 该字符串将与配置文件中的相应字符串进行匹配以定位数据源
*/
@Override
protected Object determineCurrentLookupKey() {
/**
* DynamicDataSourceContextHolder代码中使用setDataSourceType
* 设置当前的数据源,在路由类中使用getDataSourceType进行获取,
* 交给AbstractRoutingDataSource进行注入使用
*/
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
package com.xynet.statistics.config.dataresources;

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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; /**
* 数据源切面
*
* @Order(-5)保证该AOP在@Transactional之前执行 order 越小优先级越高
* Copyright © 2019 xynet Tech Ltd. All rights reserved
* @author: sund
* @date: 2019年3月23日 下午4:24:49
* @remark:
*/
@Aspect
@Order(-5)
@Component
public class DynamicDataSourceAspect { /**
* @Before("@annotation(ds)") @Before:在方法执行之前进行执行: @annotation(
* targetDataSource): 会拦截注解targetDataSource的方法,否则不拦截;
*
* @param point
* @param targetDataSource
* @throws Throwable
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
// 获取当前的指定的数据源;
String dsId = targetDataSource.value();
// 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
System.out.println("数据源【" + targetDataSource.value() + "】不存在,使用默认数据源:" + point.getSignature());
} else {
System.out.println("Use DataSource:" + targetDataSource.value() + ":" + point.getSignature());
// 找到的话,那么设置到动态数据源上下文中指定的数据源
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
} @After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
System.out.println("Revert DataSource:" + targetDataSource.value() + ":" + point.getSignature());
// 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
package com.xynet.statistics.config.dataresources;

import java.util.ArrayList;
import java.util.List; /**
* 动态数据源上下文
* Copyright © 2019 xynet Tech Ltd. All rights reserved
* @author: sund
* @date: 2019年3月23日 下午4:32:45
* @remark:
*/
public class DynamicDataSourceContextHolder { /**
* 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); /**
* 管理所有的数据源id
* 主要是为了判断数据源是否存在
*/
public static List<String> dataSourceIds = new ArrayList<String>(); /**
* 使用setDataSourceType设置当前的
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
} public static String getDataSourceType() {
return contextHolder.get();
} public static void clearDataSourceType() {
contextHolder.remove();
} /**
* 判断指定DataSource当前是否存在
* @param dataSourceId
* @return
*/
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
} }
package com.xynet.statistics.config.dataresources;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; /**
* 动态数据源注册 Copyright © 2019 xynet Tech Ltd. All rights reserved
*
* @author: sund
* @date: 2019年3月23日 下午4:34:37
* @remark:要在启动类上增加注解 @Import({DynamicDataSourceRegister.class})
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 默认数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>(); @Override
public void setEnvironment(Environment environment) {
initDefaultDataSource(environment);
initCustomDataSources(environment);
} /**
* 加载主数据源配置.
*
* @param env
*/
private void initDefaultDataSource(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
} /**
* 加载更多据源配置.
*
* @param env
*/
private void initCustomDataSources(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
} public DataSource buildDataSource(Map<String, Object> dsMap) {
Object type = dsMap.get("type");
if (type == null) {
type = DATASOURCE_TYPE_DEFAULT;
}
Class<? extends DataSource> dataSourceType;
try {
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
} private void dataBinder(DataSource dataSource, Environment env) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);
dataBinder.setIgnoreInvalidFields(false);
dataBinder.setIgnoreUnknownFields(true);
if (dataSourcePropertyValues == null) {
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<>(rpr);
// 排除已经设置的属性
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
} @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
// 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
// 添加属性:AbstractRoutingDataSource.defaultTargetDataSource
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
System.out.println("============注册数据源成功==============");
}
}
package com.xynet.statistics.config.dataresources;

import java.lang.annotation.*;

/**
* 自定义注解,数据源指定
* Copyright © 2019 xynet Tech Ltd. All rights reserved
* @author: sund
* @date: 2019年3月23日 下午4:37:16
* @remark:
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}

将上面五个类建立好后在启动类中加入@Import({DynamicDataSourceRegister.class}),这个很关键不然加载不了多数据源,只会调用默认数据源

@EnableDiscoveryClient
@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker
@ServletComponentScan
@EnableRedisHttpSession
@EnableTransactionManagement
@EnableFeignClients
@EnableScheduling
@MapperScan(basePackages = "com.xynet.statistics.dao")
@Import({DynamicDataSourceRegister.class})
public class XynetServiceStatisticsApplication { public static void main(String[] args) {
SpringApplication.run(XynetServiceStatisticsApplication.class, args);
} @Bean
public AsyncTaskExecutor paraTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("statistics-paraTaskExecutor-Executor");
executor.setCorePoolSize(24);
executor.setQueueCapacity(100);
executor.setMaxPoolSize(500);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); executor.initialize();
/*
* // 设置拒绝策略 executor.setRejectedExecutionHandler(new
* RejectedExecutionHandler() {
*
* @Override public void rejectedExecution(Runnable r,
* ThreadPoolExecutor executor) { // ..... } }); // 使用预定义的异常处理类
* executor.setRejectedExecutionHandler(new
* ThreadPoolExecutor.CallerRunsPolicy());
*/
return executor;
}
}

数据源切换注解要加到service上不要加到Mapper中上否则不会生效


@Service
public class BigDataServiceImpl implements BigDataService {

    @Override
@TargetDataSource("slave1")
public List<T_jq_jqxx> selectJqbhByShbh(String shbh) {
return t_jq_jqxxMapper.selectJqbhByShbh(shbh);
}

application-dev.yaml 配置文件如下:

spring:
profiles: dev
#mysql
datasource:
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT NOW()
testWhileIdle: true
testOnBorrow: true
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,log4j
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
driverClassName: com.mysql.jdbc.Driver
username: root
password: 1234
url: jdbc:mysql://192.168.1.253:3306/xy-platform?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false #jpa
jpa:
show-sql: true
open-in-view: true #redis
redis:
database: 15
host: 192.168.1.253
password:
port: 6379
pool:
min-idle: 1
max-idle: 8
max-active: 100
max-wait: 1
timeout: 10000 custom:
datasource:
names: slave1,slave2
slave1:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
username: root
password: 1234
url: jdbc:mysql://192.168.1.253:3306/test?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
slave2:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
username: root
password: 1234
url: jdbc:mysql://192.168.1.253:3306/sy?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false #log
logging:
level:
root: INFO
com.xynet: DEBUG
file: logs/service-statistics.log #server
server:
port: 8081 app:
fileHome: C:\
tempFileHome: tmp/
fileDownloadUrl: C:\ auth:
#url: http://localhost:7011/remote/authRemoteService
url: http://192.168.1.253:8899/authentication/remote/authRemoteService eureka:
client:
#service-url.defaultZone: http://localhost:8761/eureka
service-url.defaultZone: http://admin:123@192.168.1.253:7010/eureka enabled: true
registerWithEureka: true
fetchRegistry: true
healthcheck.enabled: true instance:
lease-renewal-interval-in-seconds: 5
lease-expiration-duration-in-seconds: 5
prefer-ip-address: true
instance-id: ${spring.cloud.client.ipAddress}:${server.port}

最上附上项目结构图:标红的地方为要修改的地方

spring boot  mybatis 多数据源配置