springboot數據源切換


 

需求:對不同的數據庫進行操作,如讀寫分離

思想:在對數據庫進行操作時,程序會默認去找數據源,從數據源獲取一個連接connection,要做的點是在程序調用之前把數據源換了(例如本身默認是寫,換成讀的數據源),spring boot提供了一個AbstractRoutingDataSource類,翻譯過來是抽象路由數據源,這個類可以指定使用的數據源,繼承這個抽象類之后需要實現兩個方法,一個方法是用來添加數據源的(讀寫分離就兩個數據源,有其他的也可以添加進去),設置默認數據源的,還有一個是指定使用的數據源的。方法添加數據源的時候是以鍵值對的形式添加到map中,當指定數據源的方法返回的值在map中存在時,就是用這個key對應的數據源,如果不存在,就會使用設置的默認數據源

這里使用aop的方式來進行數據源的動態切換,先看數據源配置(這里兩個數據源是讀寫分離,寫在本機上,讀在虛擬機上)

application.xml:

server:
port: 9222
spring:
datasource:
  localmysql:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=utf8
    driverClassName: com.mysql.cj.jdbc.Driver
  remomysql:
    username: root
    password: root
    url: jdbc:mysql://172.21.4.130:3306/mytest?useUnicode=true&characterEncoding=utf8
    driverClassName: com.mysql.cj.jdbc.Driver
  type: com.alibaba.druid.pool.DruidDataSource

數據源配置為bean,加入ioc,供后面切換:

DatabaseResourceConfig

package com.gzt.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

/**
* @Description:
* @Author: guozhengtao
* @Date: 2022/1/29 11:48
*/
@Configuration
public class DatabaseResourceConfig {

@ConfigurationProperties(prefix = "spring.datasource.localmysql")
@Bean(value = "LocalMysql")
public DataSource getlocalDataSource() {
return new DruidDataSource();
}

@ConfigurationProperties(prefix = "spring.datasource.remomysql")
@Bean(value = "RemoMysql")
public DataSource getremoDataSource() {
return new DruidDataSource();
}
}

 

數據源key枚舉:DatabaseSelectEnum

package com.gzt.config;

/**
* @Description: 定義一個枚舉變量,當枚舉變量的值為write是,選擇寫入的數據庫連接,當枚舉變量的值為read時,返回讀取的數據庫連接
* @Author: guozhengtao
* @Date: 2022/1/29 13:30
*/
public enum DatabaseSelectEnum {
/**
* 選擇寫枚舉
* */
Write("write"),
/**
* 選擇讀枚舉
* */
Read("read");

private final String select;

DatabaseSelectEnum(String select) {
this.select = select;
}

public String getSelect() {
return select;
}
}

 

數據源切換適配:DatabaseAdapt

package com.gzt.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
* @Description:
* @Author: guozhengtao
* @Date: 2022/1/29 13:19
*/
@Component
@Primary
public class DatabaseAdapt extends AbstractRoutingDataSource {
/**
* 默認為寫的數據庫(主數據庫)
*/
public static String flag = DatabaseSelectEnum.Write.getSelect();

@Autowired
@Qualifier("LocalMysql")
DataSource localMysql;

@Autowired
@Qualifier("RemoMysql")
DataSource remoMysql;

/**
* 指定數據源的key,這里數據源的key使用了enum
* */
@Override
protected Object determineCurrentLookupKey() {
return flag;
}

/**
* 設置默認數據源和添加所有數據源
*/
@Override
public void afterPropertiesSet() {
//初始化所有數據源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseSelectEnum.Write.getSelect(), localMysql);
targetDataSources.put(DatabaseSelectEnum.Read.getSelect(), remoMysql);
super.setTargetDataSources(targetDataSources);

//設置默認數據源,當上面方法返回的key找不到時,使用這里設置的數據源
super.setDefaultTargetDataSource(localMysql);

//屬性設置(把上面數據源的設置到父類)
super.afterPropertiesSet();
}
}

到現在為止,數據源相關操作設置完畢,下面切換數據源的兩種方式,因為在DatabaseAdapt類中定義了一個變量flag,這個flag是數據源對應的key,有兩種方式來進行設置:

  1. 在方法中調用mapper之前先使用

    DatabaseAdapt.flag=DatabaseSelectEnum.Write.getSelect();

    來切換數據源,但這種方式適合單個請求的,如果類里面有多個方法請求,每個方法里都寫很麻煩,所以推薦注解的方式,在類上使用注解+aop進行增強

  2. 使用aop的方式在執行方法或者類之前進行切換,主要介紹:

    注解:DatabaseResourcedefine

    package com.gzt.annotation;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    /**
    * @Description:
    * @Author: guozhengtao
    * @Date: 2022/1/29 14:37
    */

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DatabaseResourcedefine {
    String value() default "";
    }

    Aop:DatabaseResourceToggle

    package com.gzt.aspect;

    import com.gzt.annotation.DatabaseResourcedefine;
    import com.gzt.config.DatabaseAdapt;
    import org.aspectj.lang.JoinPoint;
    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;

    /**
    * @Description:
    * @Author: guozhengtao
    * @Date: 2022/1/29 15:01
    */
    @Aspect
    @Component
    public class DatabaseResourceToggle {
    final Logger logger = LoggerFactory.getLogger(getClass());

    //使用前置通知或者是環繞通知
    @Before("within(com.gzt.controller.*)&&@annotation(databaseResourcedefine)")
    public void before(JoinPoint joinPoint, DatabaseResourcedefine databaseResourcedefine) {
    DatabaseAdapt.flag=databaseResourcedefine.value();
    logger.info(databaseResourcedefine.value());
    }
    }

    目錄結構如下:

  

 

主要代碼部分:

package com.gzt.service;

import com.gzt.annotation.DatabaseResourcedefine;
import com.gzt.entity.User;
import com.gzt.mapper.TestMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @Description:
* @Author: guozhengtao
* @Date: 2022/1/30 13:03
*/
@Service
@DatabaseResourcedefine(value = "read")
public class implTestImpl implements TestService{
  final Logger logger = LoggerFactory.getLogger(getClass());
  @Autowired
  TestMapper testMapper;

  //@DatabaseResourcedefine(value = "read")
  @Override
  public List<User> getUser() {
     //DatabaseAdapt.flag= DatabaseSelectEnum.Write.getSelect();
     List<User> user = testMapper.getUser();
     logger.info(String.valueOf(user));
     return user;
  }

  //@DatabaseResourcedefine(value = "write")
  @Override
  public void setUserInfo(User user) {
     logger.info("獲取到用戶信息:{}",user);
     //DatabaseAdapt.flag= DatabaseSelectEnum.Write.getSelect();
     testMapper.setUserInfo(user.getName(), user.getAge(), user.getSex());
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM