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-8</ project.build.sourceEncoding>
< spring-boot.version> 2.7.11</ spring-boot.version>
< lombok.version> 1.18.30</ lombok.version>
< mysql.version> 8.0.33</ mysql.version>
< mybatis-plus.version> 3.5.5</ mybatis-plus.version>
</ properties>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-web</ artifactId>
< exclusions>
< exclusion>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-logging</ artifactId>
</ exclusion>
</ exclusions>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-log4j2</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-jdbc</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-validation</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-aop</ artifactId>
</ dependency>
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
< scope> runtime</ scope>
</ dependency>
< dependency>
< groupId> com.baomidou</ groupId>
< artifactId> mybatis-plus-boot-starter</ artifactId>
</ dependency>
< dependency>
< groupId> org.projectlombok</ groupId>
< artifactId> lombok</ artifactId>
</ dependency>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-starter-test</ artifactId>
< scope> test</ scope>
</ dependency>
< dependencyManagement>
< dependencies>
< dependency>
< groupId> org.springframework.boot</ groupId>
< artifactId> spring-boot-dependencies</ artifactId>
< version> ${spring-boot.version}</ version>
< type> pom</ type>
< scope> import</ scope>
</ dependency>
< dependency>
< groupId> mysql</ groupId>
< artifactId> mysql-connector-java</ artifactId>
< version> ${mysql.version}</ version>
</ dependency>
< dependency>
< groupId> com.baomidou</ groupId>
< artifactId> mybatis-plus-boot-starter</ artifactId>
< version> ${mybatis-plus.version}</ version>
</ dependency>
< dependency>
< groupId> org.projectlombok</ groupId>
< artifactId> lombok</ artifactId>
< 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
<?xml version="1.0" encoding="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<User> {
}
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());
}
}