1. 数据准备
create database if not exists `ds1`;
create database if not exists `ds3`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
BEGIN;
INSERT INTO `tb_user` (`id`, `username`) VALUES (1, 'wms');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
BEGIN;
INSERT INTO `tb_user` (`id`, `username`) VALUES (1, 'zhangsan');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
2. 版本
- SprintBoot:2.7.11
- Mybatis-Plus:3.5.5
- MySQL:8.0.30
3. 引入依赖
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring-boot.version>2.7.11spring-boot.version>
<lombok.version>1.18.30lombok.version>
<mysql.version>8.0.33mysql.version>
<mybatis-plus.version>3.5.5mybatis-plus.version>
properties>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-log4j2artifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis-plus.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
dependency>
dependencies>
dependencyManagement>
4. 编码
@MapperScan(
basePackages = "com.wxf.metadata.mapper"
)
@SpringBootApplication(
scanBasePackages = "com.wxf.metadata"
)
public class MetadataApplication {
public static void main(String[] args) {
SpringApplication.run(MetadataApplication.class, args);
}
}
spring:
profiles:
active: dev
server:
port: 8099
shutdown: graceful
spring:
application:
name: metadata-service
datasource:
jdbcUrl: jdbc:mysql://127.0.0.1:3306/ds1?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&useIPv6=false
username: root
password: root
driverClassName: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 500
max-lifetime: 18000000
minimum-idle: 30
connection-timeout: 30000
connection-test-query: SELECT 1
pool-name: HiKariDataSource
type: com.zaxxer.hikari.HikariDataSource
idle-timeout: 180000
auto-commit: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
type-aliases-package: com.dcits.metadata.entity
check-config-location: false
global-config:
db-config:
id-type: assign_id
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
configuration:
map-underscore-to-camel-case: true
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
config: classpath:log4j2-spring.xml
charset:
file: UTF-8
<configuration monitorInterval="5">
<Properties>
<property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"/>
<property name="FILE_PATH" value="${env:LOG_DIR:-logs/metadata-service}"/>
<property name="FILE_NAME" value="metadata-service"/>
Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
console>
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/${FILE_NAME}-info.log"
filePattern="${FILE_PATH}/backup/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/${FILE_NAME}-warn.log"
filePattern="${FILE_PATH}/backup/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileDebug" fileName="${FILE_PATH}/${FILE_NAME}-debug.log"
filePattern="${FILE_PATH}/backup/${FILE_NAME}-DEBUG-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/${FILE_NAME}-error.log"
filePattern="${FILE_PATH}/backup/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="10MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
appenders>
<loggers>
<root level="DEBUG">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
<appender-ref ref="RollingFileWarn"/>
<appender-ref ref="RollingFileError"/>
<appender-ref ref="RollingFileDebug"/>
root>
loggers>
configuration>
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusPropertiesCustomizer;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> {
GlobalConfig globalConfig = properties.getGlobalConfig();
globalConfig.setBanner(false);
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
properties.setGlobalConfig(globalConfig);
};
}
}
public class DynamicDatasource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DatasourceContextHolder.getDataSource();
}
}
public class DatasourceMapCache {
private static final Map<Object, Object> DATA_SOURCE_MAP = new ConcurrentHashMap<>(16);
public static Map<Object, Object> getDataSourceMap() {
return DATA_SOURCE_MAP;
}
public static void refreshDataSource(String datasourceKey, DataSource dataSource) {
DATA_SOURCE_MAP.put(datasourceKey, dataSource);
DynamicDatasource dynamicDatasource = SpringApplicationContext.getBean(DynamicDatasource.class);
dynamicDatasource.setTargetDataSources(DATA_SOURCE_MAP);
dynamicDatasource.afterPropertiesSet();
}
public static void removeDataSource(String datasourceKey) {
DATA_SOURCE_MAP.remove(datasourceKey);
}
}
public class DatasourceContextHolder {
private static final ThreadLocal<String> DATA_SOURCE_THREAD_LOCAL = ThreadLocal.withInitial(() -> "defaultDatasource");
public static String getDataSource() {
return DATA_SOURCE_THREAD_LOCAL.get();
}
public static void setDataSource(String datasourceKey) {
DATA_SOURCE_THREAD_LOCAL.set(datasourceKey);
}
public static void remove() {
DATA_SOURCE_THREAD_LOCAL.remove();
}
}
import javax.sql.DataSource;
import java.util.Map;
@Configuration
public class DatasourceConfig {
@Bean
public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDatasource") DynamicDatasource dynamicDatasource) {
return new JdbcTemplate(dynamicDatasource);
}
@Bean(name = "defaultDatasource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean("dynamicDatasource")
public DynamicDatasource dynamicDatasource(@Qualifier("defaultDatasource") DataSource dataSource) {
DynamicDatasource dynamicDatasource = new DynamicDatasource();
dynamicDatasource.setDefaultTargetDataSource(dataSource);
Map<Object, Object> dataSourceMap = DatasourceMapCache.getDataSourceMap();
dataSourceMap.put("defaultDatasource", dataSource);
dynamicDatasource.setTargetDataSources(dataSourceMap);
return dynamicDatasource;
}
@Bean
public MybatisSqlSessionFactoryBean sqlSessionFactory(@Qualifier("dynamicDatasource") DynamicDatasource dynamicDatasource) throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dynamicDatasource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/*.xml"));
return sqlSessionFactoryBean;
}
@Bean
public PlatformTransactionManager platformTransactionManager(@Qualifier("dynamicDatasource") DynamicDatasource dynamicDatasource) {
return new DataSourceTransactionManager(dynamicDatasource);
}
}
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ds {
String value() default "datasource";
}
import com.dcits.metadata.config.datasource.DatasourceContextHolder;
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.stereotype.Component;
import java.util.Objects;
@Aspect
@Component
public class DynamicDatasourceAspect {
@Pointcut("@annotation(com.dcits.metadata.config.datasource.aspect.Ds)")
public void dynamicDatasource() {
}
@Around("dynamicDatasource()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
Class<?> clazz = joinPoint.getTarget().getClass();
Ds clasDs = clazz.getAnnotation(Ds.class);
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Ds methodDs = methodSignature.getMethod().getAnnotation(Ds.class);
if (Objects.nonNull(methodDs)) {
DatasourceContextHolder.setDataSource(methodDs.value());
} else {
DatasourceContextHolder.setDataSource(clasDs.value());
}
return joinPoint.proceed();
} finally {
DatasourceContextHolder.remove();
}
}
}
@Component
public class SpringApplicationContext implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringApplicationContext.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static void publishEvent(Object event) {
applicationContext.publishEvent(event);
}
}
import com.dcits.metadata.config.datasource.DatasourceMapCache;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
public class HikariConfigUtils {
public static DataSource initHikariDatasource(String url, String driver, String username, String password) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(url);
hikariConfig.setDriverClassName(driver);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
return new HikariDataSource(hikariConfig);
}
public static void refreshDataSource(String datasourceKey, DataSource dataSource) {
DatasourceMapCache.refreshDataSource(datasourceKey, dataSource);
}
}
5. 测试
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
}
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dcits.metadata.entity.User;
import org.apache.ibatis.annotations.Mapper;
/**
* @author Wxf
* @since 2024-03-05 11:40:43
**/
@Mapper
public interface UserMapper extends BaseMapper {
}
import com.baomidou.mybatisplus.extension.service.IService;
import com.dcits.metadata.entity.User;
import java.util.List;
public interface UserService extends IService<User> {
List<User> selectUserList();
List<User> getDynamicUserList();
}
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dcits.metadata.config.datasource.DatasourceContextHolder;
import com.dcits.metadata.entity.User;
import com.dcits.metadata.mapper.UserMapper;
import com.dcits.metadata.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Override
public List<User> selectUserList() {
return this.baseMapper.selectList(null);
}
@Override
public List<User> getDynamicUserList() {
DatasourceContextHolder.setDataSource("ds3");
List<User> userList = this.baseMapper.selectList(null);
DatasourceContextHolder.remove();
return userList;
}
}
import com.dcits.metadata.service.UserService;
import com.dcits.metadata.utils.HikariConfigUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* 测试动态数据源
*
* @author Wxf
* @since 2024-03-05 11:46:06
**/
@SpringBootTest
public class UserTest {
@Resource
private UserService userService;
@Test
void selectUserList() {
System.out.println(this.userService.selectUserList());
}
@Test
void getDynamicUserList() {
DataSource dataSource = HikariConfigUtils.initHikariDatasource(
"jdbc:mysql://127.0.0.1:3306/ds3?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&useIPv6=false",
"com.mysql.cj.jdbc.Driver",
"root",
"root"
);
HikariConfigUtils.refreshDataSource("ds3", dataSource);
System.out.println(this.userService.getDynamicUserList());
}
}