Spring Boot動態資料來源切換實現
阿新 • • 發佈:2019-02-14
Spring Boot實現動態資料來源切換可以先參看下http://blog.csdn.net/zero__007/article/details/48711017,瞭解大致下實現的原理。
首先我們使用db_dao.xml來配置各個datasource:
首先是自定義註解類:
dao層的程式碼如下:
萬事俱備,現在就是把容器啟動了。這裡需要格外注意,Spring Boot自帶有DataSourceAutoConfiguration,需要把它禁掉,因為它會讀取application.properties檔案的spring.datasource.*屬性並自動配置單資料來源。在@SpringBootApplication註解中新增exclude屬性即可。同時還需要引入我們自己的db_dao.xml檔案。
既然知道了如何使用xml來配置的話,對使用程式碼作配置理解起來就應該很簡單了,首先需要在application.properties定義資料來源:
參考:
https://blog.csdn.net/tjcyjd/article/details/78399771
首先我們使用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>
上面的配置檔案中我們就已經配置好了我們的關鍵Bean---DynamicDataSource,DynamicDataSource.java如下:#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
不再細作程式碼分析,上面的文章已經有作解釋。這次的實現資料來源動態切換將用到AOP,通過註解來達到切換目的。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(); } }
首先是自定義註解類:
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