开发场景中经常会遇到多数据源的情况,比如MySQL读写分离等,下面是通过springboot+AOP实现.需要的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 阿里的数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.github.noraui</groupId>
<artifactId>ojdbc7</artifactId>
<version>12.1.0.2</version>
</dependency>
yml配置:
server:
port: 8080
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
slave:
url: jdbc:mysql://localhost:3307/test
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
max-idle: 10
max-wait: 10000
min-idle: 5
initial-size: 5
关键的配置文件目录:
DynamicDataSourceHolder:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DynamicDataSourceHolder {
private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceHolder.class);
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
logger.info("切换到{"+dbType+"}数据源");
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
DataSourceConfig:
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import java.util.HashMap;
import java.util.Map;
/**
* author:ccf
*/
@Configuration
public class DataSourceConfig {
//master数据源
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
//slave数据源
@Bean(name = "slaveDatasource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDatasource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap();
dsMap.put("masterDataSource", masterDataSource());
dsMap.put("slaveDataSource", slaveDatasource());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事物
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
DynamicDataSource:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
// 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
logger.info("数据源为"+DynamicDataSourceHolder.getDB());
return DynamicDataSourceHolder.getDB();
}
}
DataSourceEnum:
public interface DataSourceEnum {
String MASTER_DATA_SOURCE_NAME = "masterDataSource";
String CLUSTER_DATA_SOURCE_NAME = "slaveDataSource";
}
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAspect {
/**
* 在进入Service方法之前执行
*这里只是做了简单的关键字匹配,可按需求通过自定义注解、正则等方式处理
* @param point 切面对象
*/
@Before("execution(* xxx.xxx.xxx.service.*.*(..))")
public void before(JoinPoint point) {
// 获取到当前执行的方法名
String methodName = point.getSignature().getName();
// 设置数据源
if (isSlave(methodName)) {//从库
DynamicDataSourceHolder.setDB(DataSourceEnum.CLUSTER_DATA_SOURCE_NAME);
}else{//主库
DynamicDataSourceHolder.setDB(DataSourceEnum.MASTER_DATA_SOURCE_NAME);
}
}
@After("execution(* com.yili.report.service.*.*(..))")
public void afterSwitchDS(JoinPoint point){
DynamicDataSourceHolder.clearDB();
}
/**
* 判断是否为从库
* @param methodName
* @return
*/
private Boolean isSlave(String methodName) {
// 方法名以select,query、find、get开头的方法名走从库
return StringUtils.startsWithAny(methodName, "select","query", "find", "get");
}
}
注意!!!:主启动类过滤掉springboot的默认单数据源配置
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class ReportApplication {
public static void main(String[] args) {
SpringApplication.run(ReportApplication.class, args);
}
}
补充通过自定义注解的方式
自定义注解:DataSource
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "";
}
可在类上加注解也可在方法上加注解
@Service
public class TestService {
@Autowired
private TestMapper testMapper;
@Cacheable(value="redisCache",key="'redis_user_'+#id")
@DataSource(DataSourceEnum.THIRD_DATA_SOURCE_NAME)
public List<Map> findAll(String id) {
return testMapper.findAll(id);
}
}
然后AOP DataSourceAspect
@Component
@Aspect
public class DataSourceAspect{
/**
* 在进入Service方法之前执行
* @param point 切面对象
*/
@Before("@annotation(xxx.xxx.xxx.DataSource)")
public void before(JoinPoint point) {
MethodSignature signature = (MethodSignature)point.getSignature();
Method method = signature.getMethod();
DataSource annotation = method.getAnnotation(DataSource.class);
if(null == annotation){
DynamicDataSourceHolder.setDB(DataSourceEnum.PRIMARY_DATA_SOURCE_NAME);
}else{
DynamicDataSourceHolder.setDB(annotation.value());
}
}
@After("@annotation(xxx.xxx.xxx.DataSource)")
public void afterSwitchDS(JoinPoint point){
DynamicDataSourceHolder.clearDB();
}
}
以上就是关键的配置,其他的按照正常逻辑写controller、service、mapper即可