SpringBoot通过AOP自动切换数据源

SpringBoot通过AOP自动切换数据源

    • 1.pom文件
    • 2.application.yml配置文件
    • 3.自定义注解
    • 4.DataSourceContextHolder保存数据源的类
    • 5.aop解析注解
    • 6.动态数据源类
    • 7.mybatis配置文件类
    • 8.使用

1.pom文件


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>

    <groupId>com.caohaogroupId>
    <artifactId>springbootartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>jarpackaging>

    <name>springbootname>
    <description>Demo project for Spring Bootdescription>

    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.0.4.RELEASEversion>
        <relativePath/> 
    parent>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <java.version>1.8java.version>
    properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>1.1.1version>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.0.18version>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
        dependency>


        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>
    dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>


project>

2.application.yml配置文件

server:
  port: 5678
  servlet:
    context-path: /springboot

spring:
  datasource:
     url: jdbc:mysql://localhost:3306/jlmall
     username: root
     password: 123
     driverClassName: com.mysql.jdbc.Driver
  jpa:
     database: mysql

customer:
  datasource:
     url: jdbc:mysql://localhost:3306/jlmallpro
     username: root
     password: 123
     driverClassName: com.mysql.jdbc.Driver

mybatis:
   mapper-locations: classpath*:mapper/*.xml

3.自定义注解

package com.caohao.springboot.datasource;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface TargetDataSource {
    String value();
}

4.DataSourceContextHolder保存数据源的类

package com.caohao.springboot.datasource;

import java.util.ArrayList;
import java.util.List;

public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    /*
     *管理所有的数据源id;
     *主要是为了判断数据源是否存在;
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    public static boolean containsDataSourceType(String DataSourceId) {
        return dataSourceIds.contains(DataSourceId);
    }
}

5.aop解析注解

package com.caohao.springboot.datasource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Order(-10)//保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
    @Pointcut("@annotation(com.caohao.springboot.datasource.TargetDataSource)")
    public void annotationPointcut() {
        
    }

  


    @Before("annotationPointcut()")
    public void changeDataSource(JoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        System.out.println("注解的拦截方法名注解内容前:" + targetDataSource.value());
        //获取当前的指定的数据源;
        String dsId = targetDataSource.value();
        //如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
        if (!DataSourceContextHolder.containsDataSourceType(dsId)) {
            System.err.println("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + joinPoint.getSignature());
        } else {
            System.out.println("UseDataSource : {} > {}" + targetDataSource.value() + "=====" + joinPoint.getSignature());
            //找到的话,那么设置到动态数据源上下文中。
            DataSourceContextHolder.setDataSourceType(targetDataSource.value());
            System.out.println("数据源切换为:" + DataSourceContextHolder.getDataSourceType());
        }
    }

    @After("annotationPointcut()")
    public void restoreDataSource(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
        System.out.println("注解的拦截方法名注解内容前:" + targetDataSource.value());
        //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DataSourceContextHolder.clearDataSourceType();

        System.out.println("clear后:" + DataSourceContextHolder.getDataSourceType());

    }
}

6.动态数据源类

package com.caohao.springboot.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

7.mybatis配置文件类

package com.caohao.springboot.datasource;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
public class MyBatisConfig {
    @Autowired
    private Environment env;

    private String MYBATIS_CONFIG = "classpath*:mapper/mybatis.xml";

    /**
     * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
     */
    @Bean("masterDbDataSource")
    public DataSource masterDbDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("spring.datasource.driverClassName"));
        props.put("url", env.getProperty("spring.datasource.url"));
        props.put("username", env.getProperty("spring.datasource.username"));
        props.put("password", env.getProperty("spring.datasource.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean("slaveDb2DataSource")
    public DataSource slaveDb2DataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("customer.datasource.driverClassName"));
        props.put("url", env.getProperty("customer.datasource.url"));
        props.put("username", env.getProperty("customer.datasource.username"));
        props.put("password", env.getProperty("customer.datasource.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("masterDbDataSource", masterDbDataSource);
        targetDataSources.put("slaveDb2DataSource", slaveDb2DataSource);
        DataSourceContextHolder.dataSourceIds.add("masterDbDataSource");
        DataSourceContextHolder.dataSourceIds.add("slaveDb2DataSource");
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(slaveDb2DataSource);// 默认的datasource设置为masterDbDataSource
        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("masterDbDataSource") DataSource masterDbDataSource, @Qualifier("slaveDb2DataSource") DataSource slaveDb2DataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
       /* sqlSessionFactoryBean.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapper-locations")));*/
        /** 设置mybatis configuration 扫描路径 */
//        sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
        // 指定数据源(这个必须有,否则报错)
        sqlSessionFactoryBean.setDataSource(this.dataSource(masterDbDataSource, slaveDb2DataSource));
        /** 添加mapper 扫描路径 */
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapper-locations")));
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }
}

8.使用

package com.caohao.springboot.controller;

import com.caohao.springboot.aspectj.MyLog;
import com.caohao.springboot.dao.UserMapper;
import com.caohao.springboot.datasource.TargetDataSource;
import com.caohao.springboot.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class HelloController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/getUserList")
    @TargetDataSource(value = "masterDbDataSource")
    public List<User> hello() {
        return userMapper.findAllUser();
    }


    @RequestMapping("/getUserListPro")
    @TargetDataSource(value = "slaveDb2DataSource")
    public List<User> list() {
        return userMapper.findAllUser();
    }
}

你可能感兴趣的:(SpringBoot通过AOP自动切换数据源)