SpringBoot2.x系列教程48--多數據源配置之AOP動態切換數據源

2022-12-23 15:33:50 來源:51CTO博客

SpringBoot2.x系列教程48--多數據源配置之AOP動態切換數據源

作者:一一哥

在上一節中,我通過分包的方式實現了多數據源的配置,接下來我通過AOP切面的方式,帶領大家實現第二種多數據源配置方式,該方式是在前面案例的基礎上進行編寫的。

一. 實現過程

1. 創建web項目

我們按照之前的經驗,創建一個web程序,并將之改造成Spring Boot項目,具體過程略。


(資料圖片僅供參考)

?

2. 添加依賴包

   org.springframework.boot   spring-boot-starter-data-jpa   mysql   mysql-connector-java   com.alibaba   druid   1.1.10

3. 創建application.yml配置文件

在該配置文件中,要進行兩個數據庫的配置,本案例中我們使用默認的HikariDataSource數據源。

spring:  main:    allow-bean-definition-overriding: true  datasource:    ds1:      url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC      username: root      password: syc      driverClassName: com.mysql.jdbc.Driver    ds2:      url: jdbc:mysql://localhost:3306/db4?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC      username: root      password: syc      driverClassName: com.mysql.jdbc.Driver    type: com.zaxxer.hikari.HikariDataSource#    type: com.alibaba.druid.pool.DruidDataSource  jpa:    database: mysql    show-sql: true    hibernate:      ddl-auto: update      naming:        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl    database-platform: org.hibernate.dialect.MySQL5Dialect

4. 創建數據庫配置類

第一個數據庫配置類

package com.yyg.boot.config.properties;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description db1數據源配置類 */@ConfigurationProperties(prefix = "spring.datasource.ds1")@Component("ds1Properties")@Datapublic class Ds1Properties {    private String url;    private String username;    private String password;    private String driverClassName;}

第2個數據庫配置類

package com.yyg.boot.config.properties;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description db4數據源配置類 */@ConfigurationProperties(prefix = "spring.datasource.ds2")@Component("ds2Properties")@Datapublic class Ds2Properties {    private String url;    private String username;    private String password;    private String driverClassName;}

5. 注冊數據源

我們在一個類中注冊兩個數據源就可以了。

package com.yyg.boot.config;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import javax.sql.DataSource;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description 數據源的配置類 */@Configurationpublic class DataSourceRegisterConfig {    /**     * 主數據源配置 ds1數據源     */    @Primary    @Bean(name = "ds1Properties")    @ConfigurationProperties(prefix = "spring.datasource.ds1")    public DataSourceProperties ds1DataSourceProperties() {        return new DataSourceProperties();    }    /**     * 主數據源 ds1數據源     */    @Primary    @Bean(name = "ds1DataSource")    public DataSource ds1DataSource(@Qualifier("ds1Properties") DataSourceProperties dataSourceProperties) {        //HikariDataSource","org.apache.tomcat.jdbc.pool.DataSource", "org.apache.commons.dbcp2.BasicDataSource        return dataSourceProperties.initializeDataSourceBuilder().build();    }    /**     * 第二個ds2數據源配置     */    @Bean(name = "ds2Properties")    @ConfigurationProperties(prefix = "spring.datasource.ds2")    public DataSourceProperties ds2DataSourceProperties() {        return new DataSourceProperties();    }    /**     * 第二個ds2數據源     */    @Bean("ds2DataSource")    public DataSource ds2DataSource(@Qualifier("ds2Properties") DataSourceProperties dataSourceProperties) {        return dataSourceProperties.initializeDataSourceBuilder().build();    }}

6. 配置數據源、連接工廠、事務管理器、掃描dao目錄

注意合理的使用@Primary注解!

配置第一個數據源管理器

package com.yyg.boot.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description 配置數據源、連接工廠、事務管理器、dao目錄 */@Configuration@EnableTransactionManagement@EnableJpaRepositories(        entityManagerFactoryRef = "managerFactory1", // 配置連接工廠        transactionManagerRef = "transactionManager1", // 配置事物管理器        basePackages = {"com.yyg.boot.dao.db01"} // 設置dao所在位置)public class ManagerFactory01Config {    /**     * 配置數據源,連接第1個數據源     */    @Autowired    @Qualifier("ds1DataSource")    private DataSource ds1DataSource;    @Primary    @Bean(name = "managerFactory1")    public LocalContainerEntityManagerFactoryBean buildEntityManagerFactory1(EntityManagerFactoryBuilder builder) {        return builder                // 設置數據源                .dataSource(ds1DataSource)                //設置實體類所在位置.掃描所有帶有 @Entity 注解的類                .packages("com.yyg.boot.entity")                // Spring會將EntityManagerFactory注入到Repository之中.有了 EntityManagerFactory之后,                // Repository就能用它來創建 EntityManager 了,然后 EntityManager 就可以針對數據庫執行操作                .persistenceUnit("ds1PersistenceUnit")                .build();    }    /**     * 配置事務管理器     */    @Bean(name = "transactionManager1")    public PlatformTransactionManager transactionManagerDatabase1(EntityManagerFactoryBuilder builder) {        return new JpaTransactionManager(buildEntityManagerFactory1(builder).getObject());    }}

配置第2個數據源管理器

package com.yyg.boot.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Qualifier;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description 配置數據源、連接工廠、事務管理器、dao目錄 */@Configuration@EnableTransactionManagement@EnableJpaRepositories(        entityManagerFactoryRef = "managerFactory2", // 配置連接工廠        transactionManagerRef = "transactionManager2", // 配置事物管理器        basePackages = {"com.yyg.boot.dao.db02"} // 設置dao所在位置)public class ManagerFactory02Config {    /**     * 配置數據源,連接第2個數據源     */    @Autowired    @Qualifier("ds2DataSource")    private DataSource ds2DataSource;    @Bean(name = "managerFactory2")    public LocalContainerEntityManagerFactoryBean buildEntityManagerFactory2(EntityManagerFactoryBuilder builder) {        return builder                // 設置數據源                .dataSource(ds2DataSource)                //設置實體類所在位置.掃描所有帶有 @Entity 注解的類                .packages("com.yyg.boot.entity")                // Spring會將EntityManagerFactory注入到Repository之中.有了 EntityManagerFactory之后,                // Repository就能用它來創建 EntityManager 了,然后 EntityManager 就可以針對數據庫執行操作                .persistenceUnit("ds2PersistenceUnit")                .build();    }    /**     * 配置事務管理器     */    @Bean(name = "transactionManager2")    public PlatformTransactionManager transactionManagerDatabase1(EntityManagerFactoryBuilder builder) {        return new JpaTransactionManager(buildEntityManagerFactory2(builder).getObject());    }}

7. 創建數據源類型

利用ThreadLocal確保線程安全性,每個線程之間不會相互影響。

package com.yyg.boot.datasource;import lombok.extern.slf4j.Slf4j;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description 數據源類型 */@Slf4jpublic class DataSourceType {    public enum SourceType {        /**         * 用戶數據源         */        DS_USER,        /**         * 商品數據源         */        DS_SHOP    }    /**     * 使用ThreadLocal保證線程安全     */    private static final ThreadLocal TYPES = new ThreadLocal<>();    /**     * 往當前線程里設置數據源類型     */    public static void setDataSourceType(SourceType dataSourceType) {        if (dataSourceType == null) {            throw new NullPointerException();        }        log.warn("[設置當前數據源為]:" + dataSourceType);        TYPES.set(dataSourceType);    }    /**     * 獲取數據源類型     */    public static SourceType getDataSourceType() {        SourceType dataSourceType = TYPES.get() == null ? SourceType.DS_USER : TYPES.get();        log.warn("[當前數據源的類型為]:" + dataSourceType);        return dataSourceType;    }    /**     * 清空數據類型     */    public static void removeDataSourceType() {        TYPES.remove();    }}

8. 定義動態數據源

定義一個動態數據源,繼承AbstractRoutingDataSource 抽象類,并重寫determineCurrentLookupKey()方法。

AbstractRoutingDataSource這個類是實現多數據源的關鍵,作用是動態切換數據源。 在該類中有一個targetDataSources集合,該集合是AbstractRoutingDataSource的一個map類型的屬性,其中key表示每個數據源的名字,value為每個數據源。 然后根據determineCurrentLookupKey()這個方法獲取當前數據源在map中的key值,然后determineTargetDataSource()方法中動態獲取當前數據源,如果當前數據源不存且默認數據源也不存在時就會拋出異常。

package com.yyg.boot.datasource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description 動態切換數據源 */public class DynamicDataSource extends AbstractRoutingDataSource {    @Override    protected Object determineCurrentLookupKey() {        return DataSourceType.getDataSourceType();    }}

9. 配置多個數據源

package com.yyg.boot.datasource;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;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description 多數據源配置 */@Configurationpublic class DynamicDataSourceConfig {    @Bean(name = "dynamicDataSource")    public DynamicDataSource dynamicDataSource(@Qualifier("ds1DataSource") DataSource ds1DataSource,                                        @Qualifier("ds2DataSource") DataSource ds2DataSource) {        Map targetDataSource = new HashMap<>();        targetDataSource.put(DataSourceType.SourceType.DS_SHOP, ds1DataSource);        targetDataSource.put(DataSourceType.SourceType.DS_USER, ds2DataSource);        DynamicDataSource dataSource = new DynamicDataSource();        dataSource.setTargetDataSources(targetDataSource);        dataSource.setDefaultTargetDataSource(ds2DataSource);        return dataSource;    }}

10. 定義AOP切面

package com.yyg.boot.datasource;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.springframework.stereotype.Component;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description Description */@Aspect@Component@Slf4jpublic class DataSourceAop {    @Before("execution(* com.yyg.boot.service.impl.GoodsServiceImpl.*(..))")    public void setDataSource01() {        log.warn("db01商品數據源");        DataSourceType.setDataSourceType(DataSourceType.SourceType.DS_SHOP);    }    @Before("execution(* com.yyg.boot.service.impl.UserServiceImpl.*(..))")    public void setDataSource02() {        log.warn("db02用戶數據源");        DataSourceType.setDataSourceType(DataSourceType.SourceType.DS_USER);    }}

11. 創建Entity實體類

Goods商品類

package com.yyg.boot.entity;import lombok.Data;import javax.persistence.*;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description db1中的商品表 */@Entity@Table(name = "goods")@Datapublic class Goods {    @Id    @GeneratedValue(strategy=GenerationType.IDENTITY)    private Long id;    private String name;}

User用戶類

package com.yyg.boot.entity;import lombok.Data;import javax.persistence.*;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description db4中的用戶表 */@Entity@Table(name = "user")@Datapublic class User {    @Id    @GeneratedValue(strategy=GenerationType.IDENTITY)    private Long id;    private String username;    private String birthday;    private String sex;    private String address;}

12. 創建Dao層代碼

GoodsRepository類

package com.yyg.boot.dao.db01;import com.yyg.boot.entity.Goods;import com.yyg.boot.entity.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.JpaSpecificationExecutor;import org.springframework.stereotype.Repository;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description Description */@Repositorypublic interface GoodsRepository extends JpaRepository,JpaSpecificationExecutor {}

UserRepository類

package com.yyg.boot.dao.db02;import com.yyg.boot.entity.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.JpaSpecificationExecutor;import org.springframework.stereotype.Repository;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description Description */@Repositorypublic interface UserRepository extends JpaRepository,JpaSpecificationExecutor {}

13. 創建Service代碼

UserServiceImpl實現類

package com.yyg.boot.service.impl;import com.yyg.boot.dao.db02.UserRepository;import com.yyg.boot.entity.User;import com.yyg.boot.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description Description */@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserRepository userRepository;    @Override    public List findAll() {        return userRepository.findAll();    }}

GoodsServiceImpl實現類

package com.yyg.boot.service.impl;import com.yyg.boot.dao.db01.GoodsRepository;import com.yyg.boot.dao.db02.UserRepository;import com.yyg.boot.entity.Goods;import com.yyg.boot.entity.User;import com.yyg.boot.service.GoodsService;import com.yyg.boot.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;/** * @Author 一一哥Sun * @Date Created in 2020/4/7 * @Description Description */@Servicepublic class GoodsServiceImpl implements GoodsService {    @Autowired    private GoodsRepository goodsRepository;    @Override    public List findAll() {        return goodsRepository.findAll();    }}

14. 創建Controller接口

package com.yyg.boot.web;import com.yyg.boot.entity.Goods;import com.yyg.boot.entity.User;import com.yyg.boot.service.GoodsService;import com.yyg.boot.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import java.util.List;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description Description */@RestControllerpublic class GoodsController {    @Autowired    private UserService userService;    @Autowired    private GoodsService goodsService;    @GetMapping(value = "/users")    public List users() {        return userService.findAll();    }    @GetMapping(value = "/goods")    public List goods() {        return goodsService.findAll();    }}

15. 創建入口類

package com.yyg.boot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/** * @Author 一一哥Sun * @Date Created in 2020/4/3 * @Description Description */@SpringBootApplicationpublic class DataSourceApplication {    public static void main(String[] args){        SpringApplication.run(DataSourceApplication.class,args);    }}

16. 完整項目結構

?

17. 運行測試

我們首先測試一下goods接口,查詢的是db1數據庫里的數據。

?

對應的db1數據庫里的數據。

?然后再測試一下users接口,查詢的是db4數據庫里的數據。

?對應數據庫里的數據。

?

至此,我們在Spring Boot中,利用JPA實現了配置兩個數據源,其實也可以以此類推,配置3個,4個乃至更多的數據源!

標簽: 配置文件 數據源管理器

上一篇:當前最新:百度工程師教你玩轉設計模式(裝飾器模式)
下一篇:每日速遞:CDN的功能及原理