Spring Boot动态数据源切换实现

       Spring Boot实现动态数据源切换可以先参看下http://blog.csdn.net/zero__007/article/details/48711017,了解大致下实现的原理。
       首先我们使用db_dao.xml来配置各个datasource:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:property-placeholder location="jdbc.properties"/>

    <!-- 主库数据源 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!-- 配置连接池属性 -->
        <property name="url" value="${jdbc.url.master}"/>
        <property name="username" value="${jdbc.username.master}"/>
        <property name="password" value="${jdbc.password.master}"/>
    </bean>

    <!-- 从库数据源 -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <!-- 配置连接池属性 -->
        <property name="url" value="${jdbc.url.slave}"/>
        <property name="username" value="${jdbc.username.slave}"/>
        <property name="password" value="${jdbc.password.slave}"/>
    </bean>

    <bean id="dataSource" class="org.zero.datasource.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="master" value-ref="masterDataSource"/>
                <entry key="slave" value-ref="slaveDataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource"/>
    </bean>
</beans>
#jdbc.properties
jdbc.driver.master=com.mysql.jdbc.Driver
jdbc.url.master=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
jdbc.username.master=xxxxxx
jdbc.password.master=xxxxxx

jdbc.driver.slave=com.mysql.jdbc.Driver
jdbc.url.slave=jdbc:mysql://xxxxxx
jdbc.username.slave=xxxxxx
jdbc.password.slave=xxxxxx
       上面的配置文件中我们就已经配置好了我们的关键Bean---DynamicDataSource,DynamicDataSource.java如下:
package org.zero.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> datasourceHolder = new ThreadLocal<>();

    @Override
    public Object determineCurrentLookupKey() {
        return datasourceHolder.get();
    }

    static void setDataSource(String sourceName) {
        datasourceHolder.set(sourceName);
    }

    static void clearDataSource() {
        datasourceHolder.remove();
    }
}
       不再细作代码分析,上面的文章已经有作解释。这次的实现数据源动态切换将用到AOP,通过注解来达到切换目的。
       首先是自定义注解类:
package org.zero.datasource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBSource {
    String name();
}
       这里定义了DBSource注解,然后可以将该注解写在dao层的接口方法或实现类的方法上来使用,当然我们需要AOP来识别注解:
package org.zero.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(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {

    @Before(value = "execution(* org.zero.dao..*.*(..))")
    public void changeDataSource(JoinPoint point) throws Throwable {
        String sourceName = null;

        //获得当前访问的class
        Class<?> classes = point.getTarget().getClass();

        //获得访问的方法名称
        String methodName = point.getSignature().getName();

        //定义的接口方法
        Method abstractMethod = ((MethodSignature) point.getSignature()).getMethod();
        if (abstractMethod.isAnnotationPresent(DBSource.class)) {
            sourceName = abstractMethod.getAnnotation(DBSource.class).name();
            System.out.println(("动态切换数据源:--- " + sourceName));
        }

        //接口方法参数类型
        Class<?>[] parameterTypes = abstractMethod.getParameterTypes();

        try {
            //实现类中的该方法
            Method method = classes.getMethod(methodName, parameterTypes);
            if (method.isAnnotationPresent(DBSource.class)) {
                sourceName = method.getAnnotation(DBSource.class).name();
                System.out.println("动态切换数据源:------ " + sourceName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (sourceName != null) {
            DynamicDataSource.setDataSource(sourceName);
        }
    }

    @Pointcut("execution(* org.zero.dao..*.*(..))")
    public void pointCut() {
    }

    @After("pointCut()")
    public void after(JoinPoint point) {
        System.out.println("after");
        DynamicDataSource.clearDataSource();
    }
}
       上面的类会拦截org.zero.dao目录及其子目录的类的方法,先会获取接口方法上的注解,然后是实现类该方法的注解,优先级是实现类该方法的注解>接口方法上的注解。
       dao层的代码如下:
package org.zero.dao;

import org.zero.datasource.DBSource;
import org.zero.entity.Book;
import java.util.List;

public interface IBookDao {

    @DBSource(name="master")
    Book queryById(long id);

    List<Book> queryAll();
}
package org.zero.dao.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.zero.dao.IBookDao;
import org.zero.datasource.DBSource;
import org.zero.entity.Book;
import javax.sql.DataSource;
import java.util.List;

@Repository
public class BookDaoImpl implements IBookDao {
    private JdbcTemplate jdbcTemplate;

    @Override
    public Book queryById(long id) {
        String sql = "SELECT * FROM book WHERE book_id = ?";

        List<Book> list = jdbcTemplate.query(sql, (rs, rowNum) -> {
            Book book = new Book();
            book.setBookId(rs.getLong("book_id"));
            book.setName(rs.getString("name"));
            book.setNumber(rs.getInt("number"));
            return book;
        }, id);
        if (list.size() == 0) {
            return null;
        } else {
            return list.get(0);
        }
    }

    @Override
    @DBSource(name="slave")
    public List<Book> queryAll() {
        List<Book> list = jdbcTemplate.query("SELECT * FROM book", new BeanPropertyRowMapper<>(Book.class));
        return list;
    }

    @Autowired
    @Qualifier("dataSource")
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
}
       至于service层和controller层的代码就省略了。

       万事俱备,现在就是把容器启动了。这里需要格外注意,Spring Boot自带有DataSourceAutoConfiguration,需要把它禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可。同时还需要引入我们自己的db_dao.xml文件。
package org.zero;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class
})
@ImportResource("classpath:db_dao.xml") //导入xml配置项
public class Application {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.setWebEnvironment(true);
        application.run(args);
    }
}


       既然知道了如何使用xml来配置的话,对使用代码作配置理解起来就应该很简单了,首先需要在application.properties定义数据源:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.url=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
spring.datasource.master.username=xxxxxx
spring.datasource.master.password=xxxxxx
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
spring.datasource.slave.username=xxxxxx
spring.datasource.slave.password=xxxxxx
       然后按照之前的那个db_dao.xml的配置用代码来实现:
package org.zero.datasource;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {
    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;

    @Bean(name="masterDataSource", destroyMethod = "close", initMethod="init")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(name="slaveDataSource", destroyMethod = "close", initMethod="init")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();

        // 配置多数据源
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource());
        targetDataSources.put("slave", slaveDataSource());
        dynamicDataSource.setTargetDataSources(targetDataSources);

        dynamicDataSource.setDefaultTargetDataSource(slaveDataSource());
        return dynamicDataSource;
    }
}
       剩下的就是启动类:
package org.zero;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.zero.datasource.DataSourceConfig;

@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class
})
public class Application {
    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(Application.class);
        application.setWebEnvironment(true);
        application.run(args);
    }
}

参考:
https://blog.csdn.net/tjcyjd/article/details/78399771

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页