1. 程式人生 > 其它 >SpringBoot配置多套資料來源

SpringBoot配置多套資料來源

springboot配置多套資料來源

前言

在使用springBoot開發系統的過程中,隨著開發的進行,可能會遇到需要配置多個不同資料來源的情況。這時我們可以通過Spring的重要功能AOP來解決這個問題。

資料庫

首先我們需要新建兩個資料庫,結構如下

資料庫 testdatasource1 testdatasource2
資料表 sys_user sys_user2
欄位 id,name,age,sex id,name,age,sex

程式碼實現

  1. 新建一個springboot專案

  1. 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>。
  1. 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
  1. 配置類

新建一個配置檔案,DynamicDataSourceConfig 用來配置我們相關的bean,程式碼如下

關於DataSource

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();
    }

}
  1. 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();
}
  1. 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>
  1. 相關介面檔案編寫好之後,就可以編寫我們的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();
}

參考文章