项目是gradle项目,build.gradle对应maven的pom.xml
build.gradle
dependencies {//类似于maven的dependencies
implementation 'org.springframework.boot:spring-boot-starter-web'//spring web
implementation 'org.springframework.boot:spring-boot-starter-jdbc'//jdbc
runtimeClasspath 'mysql:mysql-connector-java'//mysql驱动
implementation 'org.springframework.boot:spring-boot-starter-aop'//切面
implementation 'org.springframework.boot:spring-boot-starter-data-redis'//redis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'//mybatis
}
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
#Hikari连接池
type: com.zaxxer.hikari.HikariDataSource
mysql1:
jdbc-url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
username: x
password: x
mysql2:
jdbc-url: jdbc:mysql://192.168.18.253:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai
username: x
password: x
SpringbootApplication.java
排除数据源自动配置,改成手动配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})//排除数据源自动配置,改成手动配置
public class GradledemoApplication {
public static void main(String[] args) {
SpringApplication.run(GradledemoApplication.class, args);
}
}
HikariConfig.java
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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;
@Configuration
public class HikariConfig {
/**
* 配置别名
*/
@Value("${mybatis.type-aliases-package}")
private String typeAliasesPackage;
/**
* 配置mapper的扫描,找到所有的mapper.xml映射文件
*/
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql1")
public HikariDataSource mysql1DataSource() {
return new HikariDataSource();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.mysql2")
public HikariDataSource mysql2DataSource() {
return new HikariDataSource();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource() {
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put("mysql1", mysql1DataSource());
targetDataSources.put("mysql2", mysql2DataSource());
return new DynamicDataSource(mysql1DataSource(), targetDataSources);
}
/**
* SqlSessionFactory 配置并放入容器中
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
return sqlSessionFactoryBean.getObject();
}
}
DynamicDataSource.java
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Resource
private RedisTemplate<String,Object> redisTemplate;
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
this.setDefaultTargetDataSource(defaultTargetDataSource);
this.setTargetDataSources(targetDataSources);
this.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return DataSourceAspect.DATA_SOURCE_LIST.get((int) redisTemplate.opsForValue().get(DataSourceAspect.INDEX));
}
}
DataSourceAspect.java
由切面去控制执行数据库操作时动态切换数据源
mysql1、mysql2对应application.yml的配置
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.mybatis.spring.MyBatisSystemException;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
public static final String INDEX = "index";
public static final List<String> DATA_SOURCE_LIST = new ArrayList<String>(2) {{
this.add("mysql1");
this.add("mysql2");
}};
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Around("execution(public * com.fu.gradledemo.mapper.*.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
try {
robin();//轮询数据库
return point.proceed();
} catch (MyBatisSystemException e) {
robin();//连接不上就再轮询一次,获取另外一个mysql数据库连接
return point.proceed();
}
}
/**
* 轮询mysql数据库
*/
public void robin() {
//没有key就创建key
if (!redisTemplate.hasKey(INDEX)) {
redisTemplate.opsForValue().set(INDEX, 0);
} else {
//有key就直接获取
int getIndex = (int) redisTemplate.opsForValue().get(INDEX);
//超过list集合的长度减一就重新赋值(轮询),利用redis单线程的特性存放全局index下标
if (getIndex >= DATA_SOURCE_LIST.size() - 1) {
redisTemplate.opsForValue().set(INDEX, 0);
} else {
redisTemplate.opsForValue().set(INDEX, ++getIndex);
}
}
}
}
模拟是否成功动态切换数据源
localhost和253都创建demo数据库,user表里面的id一样,数据不一样,来确认数据是否真的动态切换。
localhost的demo库user表
253的demo库user表
name全部给它加个2
启动springboot项目。
第一次访问接口
第二次访问接口
题外
想要实现某些接口使用mysql1,某些接口使用mysql2。则使用aop切面+自定义注解的形式,只要加了自定义注解,就访问另外一个库,否则访问默认库。
数据不同步,则可以实现一主一从、互为主从的mysql。