springboot+aop+自定义注解,实现多数据源切换(有坑版)

一.新建springboot项目,引入相应的maven依赖。


	mysql
	mysql-connector-java


	com.alibaba
	druid
	1.0.5



	org.aspectj
	aspectjtools
	1.9.2


	org.apache.commons
	commons-lang3
	3.6


	org.springframework.boot
	spring-boot-starter-jdbc

二.配置数据源

  •  这里application.properties配置了三个数据源
spring.datasource.jdbc-url = jdbc:mysql://10.237.147.94:3306/gdz?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.filters = stat
spring.datasource.maxActive = 100
spring.datasource.initialSize = 10
spring.datasource.maxWait = 60000
spring.datasource.minIdle = 500

spring.datasource.world.jdbc-url = jdbc:mysql://172.25.62.101:3306/world?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.world.username = root
spring.datasource.world.password = root
spring.datasource.world.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.world.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.world.filters = stat
spring.datasource.world.maxActive = 100
spring.datasource.world.initialSize = 10
spring.datasource.world.maxWait = 60000
spring.datasource.world.minIdle = 500

spring.datasource.china.jdbc-url = jdbc:mysql://172.25.62.101:3306/china?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.china.username = root
spring.datasource.china.password = root
spring.datasource.china.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.china.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.china.filters = stat
spring.datasource.china.maxActive = 100
spring.datasource.china.initialSize = 10
spring.datasource.china.maxWait = 60000
spring.datasource.china.minIdle = 500
  • 初始化数据源
package com.gdz.dynamicdatasource.config;


import com.gdz.dynamicdatasource.dynamic.DataSourceKey;
import com.gdz.dynamicdatasource.dynamic.DynamicRoutingDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
 * @Author: guandezhi
 * @Date: 2019/1/4 12:02
 */
@Configuration
public class DynamicDataSourceConfig {


    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource defaultDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.china")
    public DataSource chinaDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.world")
    public DataSource worldDataSource() {
        return DataSourceBuilder.create().build();
    }


    /**
     * 核心动态数据源
     *
     * @return 数据源实例
     */
    @Bean
    public DataSource dynamicDataSource() {
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setDefaultTargetDataSource(defaultDataSource());
        Map dataSourceMap = new HashMap<>(3);
        dataSourceMap.put(DataSourceKey.DB_USER, defaultDataSource());
        dataSourceMap.put(DataSourceKey.DB_CHINA, chinaDataSource());
        dataSourceMap.put(DataSourceKey.DB_WORLD, worldDataSource());
        dataSource.setTargetDataSources(dataSourceMap);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());
        //此处设置为了解决找不到mapper文件的问题
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    /**
     * 事务管理
     *
     * @return 事务管理实例
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

}

  说明:其中的数据源枚举类型如下(有三种类型):

package com.gdz.dynamicdatasource.dynamic;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:40
 */
public enum DataSourceKey {
    DB_USER,
    DB_WORLD,
    DB_CHINA
}

三.新建一个本地线程变量,用于数据源aop切换之后的存储。

package com.gdz.dynamicdatasource.dynamic;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:38
 */
@Slf4j
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal currentDatesource = new ThreadLocal<>();

    /**
     * 清除当前数据源
     */
    public static void clear() {
        currentDatesource.remove();
    }

    /**
     * 获取当前使用的数据源
     *
     * @return 当前使用数据源的ID
     */
    public static DataSourceKey get() {
        return currentDatesource.get();
    }

    /**
     * 设置当前使用的数据源
     *
     * @param value 需要设置的数据源ID
     */
    public static void set(DataSourceKey value) {
        currentDatesource.set(value);
    }

}

四.将本地线程变量中的数据源设置到spring数据源中

package com.gdz.dynamicdatasource.dynamic;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:38
 */
@Slf4j
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        log.info("当前数据源:{}", DynamicDataSourceContextHolder.get());
        return DynamicDataSourceContextHolder.get();
    }
}

 说明:这是最关键的一步,数据源能否切换成功就在这。

五.自定义数据源注解,将注解用于需要改变数据源的方法上。

package com.gdz.dynamicdatasource.service;

import com.gdz.dynamicdatasource.annotation.DynamicDataSource;
import com.gdz.dynamicdatasource.mapper.ChinaMapper;
import com.gdz.dynamicdatasource.mapper.UserMapper;
import com.gdz.dynamicdatasource.mapper.WorldMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:45
 */
@Service
@Slf4j
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private ChinaMapper chinaMapper;

    @Autowired
    private WorldMapper worldMapper;

    
    @DynamicDataSource(dataSource = "user")
    public String getUserName() {
        String userName = userMapper.queryUserName(1);
        log.info("username:{}", userName);
        return userName;
    }

    @DynamicDataSource(dataSource = "china")
    public String getChinaName() {
        String chinaName = chinaMapper.queryCountryName(0);
        log.info("chinaname:{}", chinaName);
        return chinaName;
    }

    @DynamicDataSource(dataSource = "world")
    public String getWorldName() {
        String worldName = worldMapper.queryWorldName(7);
        log.info("worldname:{}", worldName);
        return worldName;
    }



}

说明:这里的@DynamicDataSource为自定义注解,如下:

package com.gdz.dynamicdatasource.annotation;

import java.lang.annotation.*;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:35
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface DynamicDataSource {

    String dataSource() default "";
}

六.定义一个aop切面,将方法上标注的数据源设置到本地线程变量。

package com.gdz.dynamicdatasource.aspect;

import com.gdz.dynamicdatasource.annotation.DynamicDataSource;
import com.gdz.dynamicdatasource.dynamic.DataSourceKey;
import com.gdz.dynamicdatasource.dynamic.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @Author: guandezhi
 * @Date: 2019/3/3 10:22
 */
@Slf4j
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {


    @Around("@annotation(com.gdz.dynamicdatasource.annotation.DynamicDataSource)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        return doinvoke(pjp);
    }

    private Object doinvoke(ProceedingJoinPoint pjp) throws Throwable {
        //设置数据源
        setDynamicDataSource(pjp);
        //执行目标方法
        Object result = pjp.proceed();
        //清空数据源
        DynamicDataSourceContextHolder.clear();
        return result;
    }

    private void setDynamicDataSource(ProceedingJoinPoint pjp) {
        MethodSignature methodSignature = (MethodSignature)pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        DynamicDataSource dynamicDataSource = targetMethod.getAnnotation(DynamicDataSource.class);
        if (dynamicDataSource != null) {
            String dataSource = dynamicDataSource.dataSource();
            if (StringUtils.isNotEmpty(dataSource)) {
                if ("china".equals(dataSource)) {
                    DynamicDataSourceContextHolder.set(DataSourceKey.DB_CHINA);
                } else if ("world".equals(dataSource)) {
                    DynamicDataSourceContextHolder.set(DataSourceKey.DB_WORLD);
                }
            }
        }
        log.info("当前aop数据源:{}", DynamicDataSourceContextHolder.get());
    }
}

说明:这里必须要加注解@Order(-1),目的是让切面优先执行,否则有可能导致数据源设置不成功。

总结:

    1.此方式有待进一步研究,经测试发现事务无法管理。

    2.有时数据源切换容易失败,请谨慎使用

代码地址:https://github.com/dezhiguan/DynamicDataSource

你可能感兴趣的:(springboot)