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 : gracefulspring : application : name : metadata- servicedatasource : 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 : rootpassword : rootdriverClassName : com.mysql.cj.jdbc.Driverhikari : maximum-pool-size : 500 max-lifetime : 18000000 minimum-idle : 30 connection-timeout : 30000 connection-test-query : SELECT 1pool-name : HiKariDataSourcetype : com.zaxxer.hikari.HikariDataSourceidle-timeout : 180000 auto-commit : true jackson : date-format : yyyy- MM- dd HH: mm: sstime-zone : GMT+8
mybatis-plus : mapper-locations : classpath: /mapper/*.xml type-aliases-package : com.dcits.metadata.entitycheck-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.MybatisEnumTypeHandlerlog-impl : org.apache.ibatis.logging.stdout.StdOutImpl
logging : config : classpath: log4j2- spring.xmlcharset : 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 {@Resourceprivate UserService userService;@Testvoid selectUserList() {System.out.println(this.userService.selectUserList());}@Testvoid 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());}}