SpringBoot配置多套資料來源
前言
在使用springBoot開發系統的過程中,隨著開發的進行,可能會遇到需要配置多個不同資料來源的情況。這時我們可以通過Spring的重要功能AOP來解決這個問題。
資料庫
首先我們需要新建兩個資料庫,結構如下
資料庫 | testdatasource1 | testdatasource2 |
---|---|---|
資料表 | sys_user | sys_user2 |
欄位 | id,name,age,sex | id,name,age,sex |
程式碼實現
- 新建一個springboot專案
略
- maven依賴
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.1.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version> </dependency>。
- yml配置類
首先我們在配置類中分別配置兩套資料來源,其中主資料來源為primary,次資料來源為secondary。
注意:Springboot2.0 在配置資料庫連線的時候需要使用jdbc-url,如果只使用url的話會報jdbcUrl is required with driverClassName.錯誤。
spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/testdatasource1?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true username: root password: 1234 driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/testdatasource2?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true username: root password: 1234 driver-class-name: com.mysql.cj.jdbc.Driver server: port: 7000
- 配置類
新建一個配置檔案,DynamicDataSourceConfig 用來配置我們相關的bean,程式碼如下
package com.jzxx.demo.config; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Qualifier; 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 javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @MapperScan(basePackages = "com.jzxx.demo.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我們介面檔案的地址 public class DynamicDataSourceConfig { // 將這個物件放入Spring容器中 @Bean(name = "PrimaryDataSource") // 表示這個資料來源是預設資料來源 @Primary // 讀取application.properties中的配置引數對映成為一個物件 // prefix表示引數的字首 @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource getDateSource1() { return DataSourceBuilder.create().build(); } @Bean(name = "SecondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource getDateSource2() { return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource, @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) { //這個地方是比較核心的targetDataSource 集合是我們資料庫和名字之間的對映 Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource); targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource); dataSource.setDefaultTargetDataSource(primaryDataSource);//設定預設物件 return dataSource; } @Bean(name = "SqlSessionFactory") public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dynamicDataSource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));//設定我們的xml檔案路徑 return bean.getObject(); } }
而在這所有的配置中,最核心的地方就是DynamicDataSource這個類了,DynamicDataSource是我們自定義的動態切換資料來源的類。
DynamicDataSource程式碼如下
package com.jzxx.demo.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
DataSourceType類的程式碼如下:
package com.jzxx.demo.config;
public class DataSourceType {
//內部列舉類,用於選擇特定的資料型別
public enum DataBaseType {
Primary, Secondary
}
// 使用ThreadLocal保證執行緒安全
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
// 往當前執行緒裡設定資料來源型別
public static void setDataBaseType(DataBaseType dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 獲取資料來源型別
public static DataBaseType getDataBaseType() {
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
return dataBaseType;
}
// 清空資料型別
public static void clearDataBaseType() {
TYPE.remove();
}
}
- dao層
package com.jzxx.demo.mapper.one;
import com.jzxx.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface PrimaryUserMapper {
List<User> findAll();
}
package com.jzxx.demo.mapper.two;
import com.jzxx.demo.pojo.;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface SecondaryUserMapper {
List<User> findAll();
}
- 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.jzxx.demo.mapper.one.PrimaryUserMapper">
<select id="findAll" resultType="com.jzxx.demo.pojo.User">
select id,name,age,sex from sys_user
</select>
</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.jzxx.demo.mapper.two.SecondaryUserMapper">
<select id="findAll" resultType="com.jzxx.demo.pojo.User">
select id,name,age,sex from sys_user
</select>
</mapper>
- 相關介面檔案編寫好之後,就可以編寫我們的aop程式碼了:
package com.jzxx.demo.aop;
import com.jzxx.demo.config.DataSourceType;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAop {
//在primary方法前執行
@Before("execution(* com.jzxx.demo.controller.TestController.primary(..))")
public void setDataSource2test01() {
System.err.println("Primary業務");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
//在secondary方法前執行
@Before("execution(* com.jzxx.demo.controller.TestController.secondary(..))")
public void setDataSource2test02() {
System.err.println("Secondary業務");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}
}
編寫我們的測試 TestController: 程式碼如下:
package com.jzxx.demo.controller;
import com.jzxx.demo.mapper.one.PrimaryUserMapper;
import com.jzxx.demo.mapper.two.SecondaryUserMapper;
import com.jzxx.demo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class TestController {
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Autowired
private SecondaryUserMapper secondaryUserMapper;
@RequestMapping("primary")
public Object primary(){
List<Healthitem> list = primaryUserMapper.findAll();
return list;
}
@RequestMapping("secondary")
public Object secondary(){
List<HistoryData> list = secondaryUserMapper.findAll();
return list;
}
}
測試
啟動專案,在瀏覽器中分別輸入http://127.0.0.1:7000/primary 和http://127.0.0.1:7000/secondary
優化
上面的程式碼還不夠靈活,每寫一個介面就需要我們去寫aop程式碼。下面我們將通過攔截註解的方式,來替代攔截介面的方式。
首先自定義我們的註解 @DataSource
package com.jzxx.demo.config;
import java.lang.annotation.*;
/**
* 切換資料註解 可以用於類或者方法級別 方法級別優先順序 > 類級別
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "primary"; //該值即key值,預設使用預設資料庫
}
通過使用aop攔截,獲取註解的屬性value的值。如果value的值並沒有在我們DataBaseType裡面,則使用我們預設的資料來源,如果有的話,則切換為相應的資料來源。
package com.jzxx.demo.aop;
import com.jzxx.demo.config.DataSource;
import com.jzxx.demo.config.DataSourceType;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(dataSource)")//攔截我們的註解
public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {
String value = dataSource.value();
if (value.equals("primary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}else if (value.equals("secondary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}else {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//預設使用主資料庫
}
}
@After("@annotation(dataSource)") //清除資料來源的配置
public void restoreDataSource(JoinPoint point, DataSource dataSource) {
DataSourceType.clearDataBaseType();
}
}
修改我們的dao,新增我們的自定義的@DataSouse註解,並註解掉我們DataSourceAop類裡面的內容。
自定義的這個註解@DataSouse可以用於類或者方法級別 方法級別優先順序 > 類級別
package com.jzxx.demo.mapper.one;
import com.jzxx.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface PrimaryUserMapper {
@DataSource
List<User> findAll();
}
package com.jzxx.demo.mapper.two;
import com.jzxx.demo.pojo.;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface SecondaryUserMapper {
@DataSource("secondary")//指定資料來源為:secondary
List<User> findAll();
}