springboot+Aop实现动态数据源配置
文章目录
- springboot+Aop实现动态数据源配置
- 前言
- 1、添加依赖(本项目是一个springboot项目)
- 2、定义数据库类型枚举类(enum)
- 3、定义注解(annotation)
- 4、编写ThreadLocal存储数据上下文信息
- 5、获取数据源信息
- 6、自定义切面(Aspect)
- 7、配置数据源信息
- 8、编写配置文件
前言
这是我司在对于动态数据源的一个处理方法,采用动态加载数据库信息实现动态数据源的切换。通过使用Spring AOP + 注解来替换当前线程ThreadLocal中的值,并且通过重写AbstractRoutingDataSource类重写determineCurrentLookUpKey()方法,实现动态数据源切换,满足功能实现的代码0侵入性,并且高度解耦,实现可拔插功能效果
1、添加依赖(本项目是一个springboot项目)
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>2.2.0</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.5.2</version>
</dependency>
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-core</artifactId><version>3.5.1</version>
</dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.9</version>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.1.22</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.32</version><scope>runtime</scope>
</dependency>
2、定义数据库类型枚举类(enum)
/*** 数据库配置名字字段* @author zh* @data 17点14分**/
public enum DBTypeEnum {cs1db("cs1-db"), cs2db("cs2-db");private String value;DBTypeEnum(String value) {this.value = value;}public String getValue() {return value;}
}
此枚举主要是用于简化if-else语句。
3、定义注解(annotation)
/*** @author zh* @data 17点18分*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataSourceSwitch {/***** 顺便设置下默认数据源* @return*/DBTypeEnum value() default DBTypeEnum.cs1db;
}
定义DataSourceSwitch注解,默认数据源为cs1db(数据库一)。
4、编写ThreadLocal存储数据上下文信息
/*** @author zh*/
public class DbContextHolder {private static final ThreadLocal contextHolder = new ThreadLocal<>();/*** 设置数据源* @param dbTypeEnum*/public static void setDbType(DBTypeEnum dbTypeEnum) {contextHolder.set(dbTypeEnum.getValue());}/*** 取得当前数据源* @return*/public static String getDbType() {return (String) contextHolder.get();}/*** 清除上下文数据*/public static void clearDbType() {contextHolder.remove();}
}
工具类DbContextHolder用于存储数据名上下文。
5、获取数据源信息
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/*** @author zh*/
public class DynamicDataSource extends AbstractRoutingDataSource {/*** 取得当前使用哪个数据源* @return*/@Overrideprotected Object determineCurrentLookupKey() {return DbContextHolder.getDbType();}
}
工具类DynamicDataSource继承AbstractRoutingDataSource重写determineCurrentLookupKey()方法,从工具类DbContextHolder中获取当前数据源信息。
6、自定义切面(Aspect)
/*** @author zh* 数据源AOP注解实现*/
@Component
@Aspect
@Order(-100)
public class DataSourceSwitchAspect {private Logger log= LoggerFactory.getLogger(DataSourceSwitchAspect.class);@Pointcut("execution(* com.zh.cn.business.cs1db..*.*(..))")private void cs1dbAspect() {}@Pointcut("execution(* com.zh.cn.business.cs2db..*.*(..))")private void cs2dbAspect() {}@Before( "cs1dbAspect()" )public void basic(JoinPoint joinPoint) {//log.info("切换到cs1db 数据源...");setDataSource(joinPoint, DBTypeEnum.cs1db);}@Before("cs2dbAspect()" )public void order (JoinPoint joinPoint) {//log.info("切换到cs2db 数据源...");setDataSource(joinPoint,DBTypeEnum.cs2db);}/*** 添加注解方式,如果有注解优先注解,没有则按传过来的数据源配置* @param joinPoint* @param dbTypeEnum*/private void setDataSource(JoinPoint joinPoint, DBTypeEnum dbTypeEnum) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();DataSourceSwitch dataSourceSwitch = methodSignature.getMethod().getAnnotation(DataSourceSwitch.class);if (Objects.isNull(dataSourceSwitch) || Objects.isNull(dataSourceSwitch.value())) {DbContextHolder.setDbType(dbTypeEnum);}else{// log.info("根据注解来切换数据源,注解值为:"+dataSourceSwitch.value());switch (dataSourceSwitch.value().getValue()) {case "cs1-db":DbContextHolder.setDbType(DBTypeEnum.cs1db);break;case "cs2-db":DbContextHolder.setDbType(DBTypeEnum.cs2db);break;default:DbContextHolder.setDbType(dbTypeEnum);}}}
}
通过springAop自定义切面,切入点在* com.zh.cn.business.cs2db..*.*(..))
,表示切入点在com.zh.cn.business.cs2db包下的任意子包、任意方法和任意返回值,并通过setDataSource()方法设置工具类DbContextHolder中数据源信息。
7、配置数据源信息
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.baomidou.mybatisplus.generator.config.rules.DbType;
import com.zh.cn.Utils.DynamicDataSource;
import com.zh.cn.constants.DBTypeEnum;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** 加载DataSource*/
@Configuration
@MapperScan({"com.zh.cn.**.mapper"})
public class MybatisPlusConfig {/*** mapper-plus分页插件<br>* 文档:http://mp.baomidou.com<br>*//* @Beanpublic PaginationInterceptor paginationInterceptor() {PaginationInterceptor paginationInterceptor = new PaginationInterceptor();//paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持return paginationInterceptor;}*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//乐观锁interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}@Bean(name = "cs1db")@ConfigurationProperties(prefix = "spring.datasource.druid.cs1-db" )public DataSource cs1db () {return DruidDataSourceBuilder.create().build();}@Bean(name = "cs2db")@ConfigurationProperties(prefix = "spring.datasource.druid.cs2-db" )public DataSource cs2db () {return DruidDataSourceBuilder.create().build();}/*** 动态数据源配置* @return*/@Bean@Primarypublic DataSource multipleDataSource (@Qualifier("cs1db") DataSource cs1db,@Qualifier("cs2db") DataSource cs2db){DynamicDataSource dynamicDataSource = new DynamicDataSource();Map< Object, Object > targetDataSources = new HashMap<>();targetDataSources.put(DBTypeEnum.cs1db.getValue(), cs1db );targetDataSources.put(DBTypeEnum.cs2db.getValue(), cs2db);dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(cs1db);//指定默认return dynamicDataSource;}@Bean("sqlSessionFactory")public SqlSessionFactory sqlSessionFactory() throws Exception {MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();sqlSessionFactory.setDataSource(multipleDataSource(cs1db(),cs2db()));sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:/mapper/*/*Mapper.xml"));MybatisConfiguration configuration = new MybatisConfiguration();configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setMapUnderscoreToCamelCase(true);configuration.setCacheEnabled(false);sqlSessionFactory.setConfiguration(configuration);sqlSessionFactory.setPlugins(new Interceptor[]{mybatisPlusInterceptor()});return sqlSessionFactory.getObject();}}
通过覆盖默认的DataSource进行加载动态数据源的配置信息,通过**multipleDataSource()方法设置数据源,最后通过sqlSessionFactory()**设置数据源的最终加载
8、编写配置文件
spring:datasource:druid:type: com.alibaba.druid.pool.DruidDataSourcecs1-db:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/short-chain?useUnicode=true&characterEncoding=UTF-8username: rootpassword: initialSize: 10minIdle: 20maxActive: 100maxWait: 60000cs2-db:driverClassName: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/short-chain?useUnicode=true&characterEncoding=UTF-8username: rootpassword: initialSize: 10minIdle: 20maxActive: 100maxWait: 60000maxActive: 100maxWait: 60000