1. 程式人生 > >Spring Boot動態資料來源切換實現

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