第十章SpringBoot动态数据源

1、原理图

2、创建枚举类

/**
 * 存数据源key值
 */
public enum DataSourceKey {
    master,salve,migration
}

3、创建自定义注解类

/**
 * 自定义注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value() default "master";
}

 4、切换数据源类

/**
 * @author yehui
 * 根据线程动态切换数据源
 */
@Configuration
public class DynamicDataSourceContextHolder {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

    /**
     * 设置默认数据源
     */
    public static  String DEFAULT_DS = "master";
    /**
     *用于轮训计数
     */
    private static int counter = 0;
    /*
     * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> contextHolder = ThreadLocal.withInitial(() -> DataSourceKey.master.name());

    /**
     *用于在切换数据源时保证不会被其他线程修改
     */
    public static Lock lock = new ReentrantLock();

    /**
     * 设置数据源
     */
    public static void setDB(String dbType){
        log.info("切换到{" + dbType + "}数据源");
        contextHolder.set(dbType);
    }

    /**
     * 得到数据源
     *
     */
    public static String getDB(){
        return contextHolder.get();
    }

    /**
     * 使用主数据源
     */
    public static void useMasterDataSource() {
        contextHolder.set(DataSourceKey.master.name());
    }
    /**
     * 移除数据源
     */
    public static void removeDB(){
        contextHolder.remove();
    }
    /**
     * The constant slaveDataSourceKeys.
     */
    public static List<Object> slaveDataSourceKeys = new ArrayList<>();
    /**
     * 当使用只读数据源时通过轮循方式选择要使用的数据源
     */
    public static String getSlaveDB(){
        lock.lock();
        try {
            int datasourceKeyIndex = counter % slaveDataSourceKeys.size();
            counter++;
            return String.valueOf(slaveDataSourceKeys.get(datasourceKeyIndex));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            e.printStackTrace();
            return "master";
        } finally {
            lock.unlock();
        }
    }
}

5、获取数据源类

/**
 * @author yehui
 * 多数据源的选择
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);

    @Override
    protected Object determineCurrentLookupKey() {
        log.info("Current DataSource is " + DynamicDataSourceContextHolder.getDB());
        return DynamicDataSourceContextHolder.getDB();
    }
}

6、Aop类

/**
 * @author yehui
 * 自定义注解 + AOP的方式实现数据源动态切换。
 */
@Aspect
@Component
public class DynamicDataSourceAspect {
    private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(DBSource)")
    public void beforeSwitchDB(JoinPoint joinPoint,DBSource DBSource){
        //获取目标类的方法
        Class<?> aClass = joinPoint.getTarget().getClass();
        //获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        //得到方法的参数类型
        Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
        String dataSource = DynamicDataSourceContextHolder.DEFAULT_DS;
        try {
            Method method = aClass.getMethod(methodName, parameterTypes);
            if(method.isAnnotationPresent(DBSource.class)){
                DBSource db = method.getAnnotation(DBSource.class);
                //指定数据源
                dataSource = db.value();
            }else{
                //轮训设置数据源
                dataSource = DynamicDataSourceContextHolder.getSlaveDB();
            }
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(), e);
        }
        //设置数据源
        DynamicDataSourceContextHolder.setDB(dataSource);
    }

    @After("@annotation(DBSource)")
    public void afterSwitchDB(DBSource DBSource){
        DynamicDataSourceContextHolder.removeDB();
    }
}

6、application.properties文件

spring.druid.datasource.slave.password=root
spring.druid.datasource.slave.username=root
spring.druid.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/study
spring.druid.datasource.slave.driver-class-name=com.mysql.jdbc.Driver

spring.druid.datasource.master.password=root
spring.druid.datasource.master.username=root
spring.druid.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/study01
spring.druid.datasource.master.driver-class-name=com.mysql.jdbc.Driver


spring.druid.datasource.migration.password=root
spring.druid.datasource.migration.username=root
#2.0版本多数据源必须是使用jdbc-url 不能使用url,否则报错 jdbcUrl is required with driverClassName
spring.druid.datasource.migration.jdbc-url=jdbc:mysql://localhost:3306/study02
spring.druid.datasource.migration.driver-class-name=com.mysql.jdbc.Driver

7、数据源配置类

/**
 * @author yehui
 * 数据源配置类
 */
@Configuration
public class DataSourceConfig {


    /**
     * 主数据
     *
     * @return data source
     */
    @Bean("master")
    @Primary
    @ConfigurationProperties(prefix = "spring.druid.datasource.master")
    public DataSource master() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 从数据库
     *
     * @return data source
     */
    @Bean("slave")
    @ConfigurationProperties(prefix ="spring.druid.datasource.slave")
    public DataSource slave() {
        return DataSourceBuilder.create().build();
    }
    /**
     * 从数据库
     *
     * @return data source
     */
    @Bean("migration")
    @ConfigurationProperties(prefix ="spring.druid.datasource.migration")
    public DataSource migration() {
        return DataSourceBuilder.create().build();
    }


    /**
     * 配置动态数据源
     *
     * @return
     */
    @Bean("dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicRoutingDataSource = new DynamicDataSource();
        Map<Object, Object> dataSourceMap = new HashMap<>(4);
        dataSourceMap.put(DataSourceKey.master.name(), master());
        dataSourceMap.put(DataSourceKey.salve.name(), slave());
        dataSourceMap.put(DataSourceKey.master.name(), slave());

        //设置默认的数据源
        dynamicRoutingDataSource.setDefaultTargetDataSource(master());
        // 多个slave数据源在此添加,自定义key,用于轮询
        dataSourceMap.put(DataSourceKey.salve.name() + "1", slave());
        //设置目标数据源
        dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
        //将数据源的key放在集合中判断是否正常
        DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet());

        //实现负载均衡算法   将 Slave 数据源的 key 放在集合中,用于轮循
        DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet());
        DynamicDataSourceContextHolder.slaveDataSourceKeys.remove(DataSourceKey.migration.name());
        return dynamicRoutingDataSource;
    }

    /**
     * 设置工厂类
     */
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource());

        //此处设置为了解决找不到mapper文件的问题
        try {
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sqlSessionFactoryBean;
    }

    /**
     * 事物管理器
     */
    @Bean("transactionManager")
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

8、启动类

/**
 * springboot入口类,此类需要在所有用到的package上层 exclude =
 * {DataSourceAutoConfiguration.class}
 * 禁用springboot默认加载的application.properties单数据源配置
 */
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class StartApp {
    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }
}

9、测试

mapper接口

@Mapper
public interface UserDataSourceMapper {
    public List<TbUser> findUser();
}

mapper文件

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yehui.mapper.UserDataSourceMapper">
    <select id="findUser" resultType="com.yehui.entity.TbUser">
        select  * from tb_user
    </select>
</mapper>

 service类

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDataSourceMapper userMapper;

    @Override
    @DBSource("slave")//使用数据源打上注解即可
    public List<TbUser> findUser() {
        return userMapper.findUser();
    }
}

controller类

@RestController
public class UserController {

    @Autowired
    private UserService userService;
    @RequestMapping("/findUser")
    public List<TbUser> findUser(){
        return userService.findUser();
    }
}

效果:

如图所示,则动态数据源配置成功

猜你喜欢

转载自www.cnblogs.com/cxyyh/p/10630544.html
今日推荐