spring-jdbc模块提供了AbstractRoutingDataSource抽象类,其内部可以包含多个DataSource,只需要实现其抽象方法,在运行时就可以动态访问指定的数据库。
新建一个springboot项目,pom.xml文件中引入如下依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.2.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.2.9version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
server:
port: 8090
spring:
application:
name: springboot-dynamic-aop
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master:
jdbc-url: jdbc:mysql://localhost:3306/dynamic-master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
jdbc-url: jdbc:mysql://localhost:3306/dynamic-slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
use-actual-param-name: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
public DataSourceTransactionManager masterDataSourceTransactionManager(DynamicDataSource dynamicDataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dynamicDataSource);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager slaveDataSourceTransactionManager(DynamicDataSource dynamicDataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dynamicDataSource);
return dataSourceTransactionManager;
}
}
新建一个类继承AbstractRoutingDataSource,实现其抽象类
@Primary
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final ThreadLocal<String> name = new ThreadLocal<>();
@Autowired
DataSource masterDataSource;
@Autowired
DataSource slaveDataSource;
@Override
protected Object determineCurrentLookupKey() {
return name.get();
}
@Override
public void afterPropertiesSet() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource);
targetDataSources.put("slave", slaveDataSource);
//设置目标数据源
super.setTargetDataSources(targetDataSources);
//设置默认数据源
super.setDefaultTargetDataSource(masterDataSource);
super.afterPropertiesSet();
}
}
一般情况下,读写分离的数据源使用MyBatis插件实现动态切换数据源,不同业务来源的数据源使用AOP结合自定义注解实现动态切换数据源
新建一个插件类,实现Interceptor接口
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class DynamicDataSourcePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] objects = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) objects[0];
if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
DynamicDataSource.name.set("slave");
} else {
DynamicDataSource.name.set("master");
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
} else {
return target;
}
}
@Override
public void setProperties(Properties properties) {
}
}
再将DynamicDataSourcePlugin类加入DataSourceConfig配置类
@Bean
public Interceptor interceptor() {
return new DynamicDataSourcePlugin();
}
新建一个自定义注解DS
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default "";
}
新建切面类
@Aspect
@Component
public class DynamicDataSourceAspect implements Ordered {
@Before("@within(ds)")
public void before(JoinPoint joinPoint, DS ds) {
DynamicDataSource.name.set(ds.value());
}
@Override
public int getOrder() {
return 0;
}
}
注意:@within注解用于在类上加自定义注解,@annotation注解用于在方法上加自定义注解
在持久层mapper接口上加上自定义注解
@Mapper
@DS(value = "slave")
public interface OrdersMapper {
Orders getOrdersById(Long id);
}
自己整合实现多数据源多有麻烦,baomidou提供的dynamic-datasource-spring-boot-starter已实现了上述功能,只需要引入该依赖即可,可以参阅SpringBoot整合dynamic-datasource实现动态切换多数据源