springboot+AOP+MySQL+mybatis多数据源动态切换实现MySQL读写分离

 

开发场景中经常会遇到多数据源的情况,比如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即可

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值