SpringBoot+MyBatis利用切面的方式配置多数据源
SpringBoot+MyBatis利用切面的方式配置多数据源,同时用druid监控sql
重点
1、AOP相关的知识,这里主要是切点的定位
2、重写AbstractRoutingDataSource的determineCurrentLookupKey方法
3、数据源的切换
项目结构
在分包的基础上增加切面相关的配置
![在这里插入图片描述](https://img-blog.csdnimg.cn/8dd45e82294749f1abaa528042ea5ab7.png
项目依赖
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
<scope>runtime</scope>
</dependency>
<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>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
配置文件
spring.datasource.db1.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db1.username=root
spring.datasource.db1.password=123456
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db1.filters=stat
spring.datasource.db1.testOnBorrow=false
spring.datasource.db1.testOnReturn=false
spring.datasource.db1.testWhileIdle=true
spring.datasource.db1.validationQuery=SELECT 1
spring.datasource.druid.db1.inital-size=3
spring.datasource.druid.db1.min-idle=3
spring.datasource.druid.db1.max-active=10
spring.datasource.druid.db1.max-wait=60000
spring.datasource.druid.db1.filters.slf4j.enabled=true
spring.datasource.db2.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.db2.username=root
spring.datasource.db2.password=123456
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.db2.filters=stat
spring.datasource.db2.testOnBorrow=false
spring.datasource.db2.testOnReturn=false
spring.datasource.db2.testWhileIdle=true
spring.datasource.db2.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.db2.inital-size=3
spring.datasource.druid.db2.min-idle=3
spring.datasource.druid.db2.max-active=10
spring.datasource.druid.db2.max-wait=60000
spring.datasource.druid.db2.filters.slf4j.enabled=true
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
数据源配置
1、MultipleDatasourceConfig
/**
* 多数据源配置
*/
@Configuration
public class MultipleDatasourceConfig {
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource db1() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource db2(){
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Primary
@Bean("dynamicDatasource")
public DataSource dynamicDatasource() {
DynamicDatasource dynamicDatasource = new DynamicDatasource();
// 配置默认数据源
dynamicDatasource.setDefaultTargetDataSource(db1());
// 配置多数据源
Map<Object, Object> datasourceMap = new HashMap<>();
datasourceMap.put("db2", db2());
dynamicDatasource.setTargetDataSources(datasourceMap);
return dynamicDatasource;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDatasource());
}
}
2、db1
/**
* @author code
* @version 1.0
* @Date 2022/6/15 17:48
* @Description ${mysql数据源配置}
*/
@Configuration
@MapperScan(basePackages = "路径",sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class DB1DSConfig {
static final String MAPPER_LOCATION = "classpath:mapper/mysql/*.xml";
@Resource
DataSource mysqlDataSource;
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource mysqlDataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(mysqlDataSource);
//将下滑线映射为驼峰配置
sqlSessionFactoryBean.setConfiguration(configuration());
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MysqlDSConfig.MAPPER_LOCATION));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager mysqlTransactionManager(){
return new DataSourceTransactionManager(mysqlDataSource);
}
@Bean(name = "mysqlTransactionTemplate")
@Primary
public SqlSessionTemplate mysqlTransactionTemplate(@Qualifier("mysqlSqlSessionFactory")SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@Primary
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration configuration(){
return new org.apache.ibatis.session.Configuration();
}
}
3、db2
/**
* @author code
* @version 1.0
* @Date 2022/6/15 17:48
* @Description ${mysql数据源配置}
*/
@Configuration
@MapperScan(basePackages = "路径",sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class DB2DSConfig {
static final String MAPPER_LOCATION = "classpath:mapper/mysql/*.xml";
@Resource
DataSource mysqlDataSource;
@Bean(name = "mysqlSqlSessionFactory")
@Primary
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource mysqlDataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(mysqlDataSource);
//将下滑线映射为驼峰配置
sqlSessionFactoryBean.setConfiguration(configuration());
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MysqlDSConfig.MAPPER_LOCATION));
return sqlSessionFactoryBean.getObject();
}
@Bean(name = "mysqlTransactionManager")
@Primary
public DataSourceTransactionManager mysqlTransactionManager(){
return new DataSourceTransactionManager(mysqlDataSource);
}
@Bean(name = "mysqlTransactionTemplate")
@Primary
public SqlSessionTemplate mysqlTransactionTemplate(@Qualifier("mysqlSqlSessionFactory")SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
@Primary
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration configuration(){
return new org.apache.ibatis.session.Configuration();
}
}
动态的数据源对象
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Datasource {
String value() default "db1";
}
DataSourceType容器
/**
* 动态数据源持有类
*/
public class DatasourceContextHolder {
private static final Logger logger = LoggerFactory.getLogger(DatasourceContextHolder.class);
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDatasource(String name) {
logger.info("切换到[{}]数据源", name);
contextHolder.set(name);
}
public static String getDatasource() {
return contextHolder.get();
}
public static void clearDatasource() {
contextHolder.remove();
}
}
切换数据源
AbstractRoutingDataSource类是Spring为方便的在项目中实现动态切换数据源而提供的一个抽象类, 我们只需要实现上面的这个抽象方法 即可 , 该方法用于返回当前需要切换的数据源的key;
/**
* 动态数据源
*/
public class DynamicDatasource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(DynamicDatasource.class);
// public DynamicDatasource(Datasource datasource, Map<Object,Object> objectMap){
// this.setDefaultTargetDataSource(datasource);
// this.setTargetDataSources(objectMap);
// this.afterPropertiesSet();
// }
@Override
protected Object determineCurrentLookupKey() {
String datasource = DatasourceContextHolder.getDatasource();
if (datasource == null) {
logger.info("当前数据源为[db1]");
datasource = "db1";
} else {
logger.info("当前数据源为[{}]", datasource);
}
return datasource;
}
}
切面拦截切换数据源
@Component
@Aspect
@Order(-1)// 使该切面调用优先于AbstractRoutingDataSource的determineCurrentLookupKey()
public class DynamicDatasourceAspect {
@Pointcut("@annotation(数据源对象的路径,即Datasource的路径)")
public void pointCut() {
System.out.println("使用[@Pointcut]定义切入点");
}
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
try {
// 获取当前访问的class
Class<?> aClass = joinPoint.getTarget().getClass();
// 获取当前访问的method
String methodName = joinPoint.getSignature().getName();
// 获取方法的参数类型数组
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
// 获取方法对象
Method method = aClass.getDeclaredMethod(methodName, parameterTypes);
Datasource datasource = method.getAnnotation(Datasource.class);
if (null!=datasource){
DatasourceContextHolder.setDatasource(datasource.value());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DatasourceContextHolder.clearDatasource();
}
}
@After("pointCut()")
public void after() {
System.out.println("after");
DatasourceContextHolder.clearDatasource();
}
}
切点的路径即,上文动态的数据源对象的路径,也可以定义在service层,但是我这边只有定义在数据源对象这里才能实现切换
dao层配置指定数据源
1、db1
@Datasource(value = "db1")
2、db2
@Datasource(value = "db2")