欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot配置主從數(shù)據(jù)庫(kù)實(shí)現(xiàn)讀寫分離

 更新時(shí)間:2023年11月06日 11:36:45   作者:Doker 多克 技術(shù)人的數(shù)碼品牌  
現(xiàn)在的 Web 應(yīng)用大都是讀多寫少,本文主要介紹了SpringBoot配置主從數(shù)據(jù)庫(kù)實(shí)現(xiàn)讀寫分離,具有一定的參考價(jià)值,感興趣的可以了解一下

一、前言

現(xiàn)在的 Web 應(yīng)用大都是讀多寫少。除了緩存以外還可以通過(guò)數(shù)據(jù)庫(kù) “主從復(fù)制” 架構(gòu),把讀請(qǐng)求路由到從數(shù)據(jù)庫(kù)節(jié)點(diǎn)上,實(shí)現(xiàn)讀寫分離,從而大大提高應(yīng)用的吞吐量。

通常,我們?cè)?Spring Boot 中只會(huì)用到一個(gè)數(shù)據(jù)源,即通過(guò) spring.datasource 進(jìn)行配置。前文 《在 Spring Boot 中配置和使用多個(gè)數(shù)據(jù)源》 介紹了一種在 Spring Boot 中定義、使用多個(gè)數(shù)據(jù)源的方式。但是這種方式對(duì)于實(shí)現(xiàn) “讀寫分離” 的場(chǎng)景不太適合。首先,多個(gè)數(shù)據(jù)源都是通過(guò) @Bean 定義的,當(dāng)需要新增額外的從數(shù)據(jù)庫(kù)時(shí)需要改動(dòng)代碼,非常不夠靈活。其次,在業(yè)務(wù)層中,如果需要根據(jù)讀、寫場(chǎng)景切換不同數(shù)據(jù)源的話只能手動(dòng)進(jìn)行。

對(duì)于 Spring Boot “讀寫分離” 架構(gòu)下的的多數(shù)據(jù)源,我們需要實(shí)現(xiàn)如下需求:

  • 可以通過(guò)配置文件新增數(shù)據(jù)庫(kù)(從庫(kù)),而不不需要修改代碼。
  • 自動(dòng)根據(jù)場(chǎng)景切換讀、寫數(shù)據(jù)源,對(duì)業(yè)務(wù)層是透明的。

幸運(yùn)的是,Spring Jdbc 模塊類提供了一個(gè) AbstractRoutingDataSource 抽象類可以實(shí)現(xiàn)我們的需求。

它本身也實(shí)現(xiàn)了 DataSource 接口,表示一個(gè) “可路由” 的數(shù)據(jù)源。

核心的代碼如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    // 維護(hù)的所有數(shù)據(jù)源
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;

    // 默認(rèn)的數(shù)據(jù)源
    @Nullable
    private DataSource resolvedDefaultDataSource;

    // 獲取 Jdbc 連接
    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

    // 獲取目標(biāo)數(shù)據(jù)源
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        // 調(diào)用  determineCurrentLookupKey() 抽象方法,獲取 resolvedDataSources 中定義的 key。
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

    // 抽象方法,返回 resolvedDataSources 中定義的 key。需要自己實(shí)現(xiàn)
    @Nullable
    protected abstract Object determineCurrentLookupKey();
}

核心代碼如上,它的工作原理一目了然。它在內(nèi)部維護(hù)了一個(gè) Map<Object, DataSource> 屬性,維護(hù)了多個(gè)數(shù)據(jù)源。

當(dāng)嘗試從 AbstractRoutingDataSource 數(shù)據(jù)源獲取數(shù)據(jù)源連接對(duì)象 Connection 時(shí),會(huì)調(diào)用 determineCurrentLookupKey() 方法得到一個(gè) Key,然后從數(shù)據(jù)源 Map<Object, DataSource> 中獲取到真正的目標(biāo)數(shù)據(jù)源,如果 Key 或者是目標(biāo)數(shù)據(jù)源為 null 則使用默認(rèn)的數(shù)據(jù)源。

得到目標(biāo)數(shù)據(jù)數(shù)據(jù)源后,返回真正的 Jdbc 連接。這一切對(duì)于使用到 Jdbc 的組件(Repository、JdbcTemplate 等)來(lái)說(shuō)都是透明的。

了解了 AbstractRoutingDataSource 后,我們來(lái)看看如何使用它來(lái)實(shí)現(xiàn) “讀寫分離”。

二、實(shí)現(xiàn)思路

首先,創(chuàng)建自己的 AbstractRoutingDataSource 實(shí)現(xiàn)類。把它的默認(rèn)數(shù)據(jù)源 resolvedDefaultDataSource 設(shè)置為主庫(kù),從庫(kù)則保存到 Map<Object, DataSource> resolvedDataSources 中。

在 Spring Boot 應(yīng)用中通常使用 @Transactional 注解來(lái)開啟聲明式事務(wù),它的默認(rèn)傳播級(jí)別為 REQUIRED,也就是保證多個(gè)事務(wù)方法之間的相互調(diào)用都是在同一個(gè)事務(wù)中,使用的是同一個(gè) Jdbc 連接。它還有一個(gè) readOnly 屬性表示是否是只讀事務(wù)。

于是,我們可以通過(guò) AOP 技術(shù),在事務(wù)方法執(zhí)行之前,先獲取到方法上的 @Transactional 注解從而判斷是讀、還是寫業(yè)務(wù)。并且把 “讀寫狀態(tài)” 存儲(chǔ)到線程上下文(ThreadLocal)中!

在 AbstractRoutingDataSource 的 determineCurrentLookupKey 方法中,我們就可以根據(jù)當(dāng)前線程上下文中的 “讀寫狀態(tài)” 判斷當(dāng)前是否是只讀業(yè)務(wù),如果是,則返回從庫(kù) resolvedDataSources 中的 Key,反之則返回 null 表示使用默認(rèn)數(shù)據(jù)源也就是主庫(kù)。

三、初始化數(shù)據(jù)庫(kù)

首先,在本地創(chuàng)建 4 個(gè)不同名稱的數(shù)據(jù)庫(kù),用于模擬 “MYSQL 主從” 架構(gòu)。

-- 主庫(kù)
CREATE DATABASE `demo_master` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫(kù)
CREATE DATABASE `demo_slave1` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫(kù)
CREATE DATABASE `demo_slave2` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫(kù)
CREATE DATABASE `demo_slave3` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

如上,創(chuàng)建了 4 個(gè)數(shù)據(jù)庫(kù)。1 個(gè)主庫(kù),3 個(gè)從庫(kù)。它們本質(zhì)上毫無(wú)關(guān)系,并不是真正意義上的主從架構(gòu),這里只是為了方便演示。

接著,在這 4 個(gè)數(shù)據(jù)庫(kù)下依次執(zhí)行如下 SQL 創(chuàng)建一張名為 test 的表。

該表只有 2 個(gè)字段,1 個(gè)是 id 表示主鍵,一個(gè)是 name 表示名稱。

CREATE TABLE `test` (
  `id` int NOT NULL COMMENT 'ID',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名稱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

最后,初始化數(shù)據(jù)。往不同的數(shù)據(jù)庫(kù)插入對(duì)應(yīng)的記錄。

INSERT INTO `demo_master`.`test` (`id`, `name`) VALUES (1, 'master');
INSERT INTO `demo_slave1`.`test` (`id`, `name`) VALUES (1, 'slave1');
INSERT INTO `demo_slave2`.`test` (`id`, `name`) VALUES (1, 'slave2');
INSERT INTO `demo_slave3`.`test` (`id`, `name`) VALUES (1, 'slave3');

不同數(shù)據(jù)庫(kù)節(jié)點(diǎn)下 test 表中的 name 字段不同,用于區(qū)別不同的數(shù)據(jù)庫(kù)節(jié)點(diǎn)。

四、創(chuàng)建應(yīng)用

創(chuàng)建 Spring Boot 應(yīng)用,添加 spring-boot-starter-jdbc 和 mysql-connector-j (MYSQL 驅(qū)動(dòng))依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

五、配置定義

我們需要在 application.yaml 中定義上面創(chuàng)建好的所有主、從數(shù)據(jù)庫(kù)。

app:
  datasource:
    master: # 唯一主庫(kù)
      jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
      username: root
      password: root

    slave: # 多個(gè)從庫(kù)
      slave1:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root
      
      slave2:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root
      
      slave3:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave3?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root

在 app.datasource.master 下配置了唯一的一個(gè)主庫(kù),也就是寫庫(kù)。然后在 app.datasource.slave 下以 Map 形式配置了多個(gè)從庫(kù)(也就是讀庫(kù)),每個(gè)從庫(kù)使用自定義的名稱作為 Key。

數(shù)據(jù)源的實(shí)現(xiàn)使用的是默認(rèn)的 HikariDataSource,并且數(shù)據(jù)源的配置是按照 HikariConfig 類定義的。也就是說(shuō),你可以根據(jù) HikariConfig 的屬性在配置中添加額外的設(shè)置。

有了配置后,還需要定義對(duì)應(yīng)的配置類,如下:

package cn.springdoc.demo.db;

import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

@ConfigurationProperties(prefix = "app.datasource")  //  配置前綴
public class MasterSlaveDataSourceProperties {

    // 主庫(kù)
    private final Properties master;

    // 從庫(kù)
    private final Map<String, Properties> slave;

    @ConstructorBinding // 通過(guò)構(gòu)造函數(shù)注入配置文件中的值
    public MasterSlaveDataSourceProperties(Properties master, Map<String, Properties> slave) {
        super();
        
        Objects.requireNonNull(master);
        Objects.requireNonNull(slave);
        
        this.master = master;
        this.slave = slave;
    }

    public Properties master() {
        return master;
    }

    public Map<String, Properties> slave() {
        return slave;
    }
}

還需要在 main 類上使用 @EnableConfigurationProperties 注解來(lái)加載我們的配置類:

package cn.springdoc.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import cn.springdoc.demo.db.MasterSlaveDataSourceProperties;

@SpringBootApplication
@EnableAspectJAutoProxy
@EnableConfigurationProperties(value = {MasterSlaveDataSourceProperties.class}) // 指定要加載的配置類
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

這里還使用 @EnableAspectJAutoProxy 開啟了 AOP 的支持,后面會(huì)用到。

六、創(chuàng)建 MasterSlaveDataSourceMarker

創(chuàng)建一個(gè) MasterSlaveDataSourceMarker 類,用于維護(hù)當(dāng)前業(yè)務(wù)的 “讀寫狀態(tài)”。

package cn.springdoc.demo.db;

public class MasterSlaveDataSourceMarker {

    private static final ThreadLocal<Boolean> flag = new ThreadLocal<Boolean>();

    // 返回標(biāo)記
    public static Boolean get() {
        return flag.get();
    }

    // 寫狀態(tài),標(biāo)記為主庫(kù)
    public static void master() {
        flag.set(Boolean.TRUE);
    }

    // 讀狀態(tài),標(biāo)記為從庫(kù)
    public static void slave() {
        flag.set(Boolean.FALSE);
    }

    // 清空標(biāo)記
    public static void clean() {
        flag.remove();
    }
}

通過(guò) ThreadLocal<Boolean> 在當(dāng)前線程中保存當(dāng)前業(yè)務(wù)的讀寫狀態(tài)。

如果 get() 返回 null 或者 true 則表示非只讀,需要使用主庫(kù)。反之則表示只讀業(yè)務(wù),使用從庫(kù)。

七、創(chuàng)建 MasterSlaveDataSourceAop

創(chuàng)建 MasterSlaveDataSourceAop 切面類,在事務(wù)方法開始之前執(zhí)行。

package cn.springdoc.demo.db;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)  // 在事務(wù)開始之前執(zhí)行
public class MasterSlaveDataSourceAop {

    static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSourceAop.class);

    @Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
    public void txMethod () {}

    @Around("txMethod()")
    public Object handle (ProceedingJoinPoint joinPoint) throws Throwable {
        
        // 獲取當(dāng)前請(qǐng)求的主從標(biāo)識(shí)
        try {
                
            // 獲取事務(wù)方法上的注解
            Transactional transactional = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Transactional.class);
            
            if (transactional != null && transactional.readOnly()) {
                log.info("標(biāo)記為從庫(kù)");
                MasterSlaveDataSourceMarker.slave();    // 只讀,從庫(kù)
            } else {
                log.info("標(biāo)記為主庫(kù)");
                MasterSlaveDataSourceMarker.master(); // 可寫,主庫(kù)
            }
            
            // 執(zhí)行業(yè)務(wù)方法
            Object ret = joinPoint.proceed();
            
            return ret;
            
        } catch (Throwable e) {
            throw e;
        } finally {
            MasterSlaveDataSourceMarker.clean();
        }
    }
}

首先,通過(guò) @Order(Ordered.HIGHEST_PRECEDENCE) 注解保證它必須比聲明式事務(wù) AOP 更先執(zhí)行。

該 AOP 會(huì)攔截所有聲明了 @Transactional 的方法,在執(zhí)行前從該注解獲取 readOnly 屬性從而判斷是否是只讀業(yè)務(wù),并且在 MasterSlaveDataSourceMarker 標(biāo)記。

八、創(chuàng)建 MasterSlaveDataSource

現(xiàn)在,創(chuàng)建 AbstractRoutingDataSource 的實(shí)現(xiàn)類 MasterSlaveDataSource:

package cn.springdoc.demo.db;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class MasterSlaveDataSource extends AbstractRoutingDataSource {

    static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSource.class);

    // 從庫(kù)的 Key 列表
    private List<Object> slaveKeys;

    // 從庫(kù) key 列表的索引
    private AtomicInteger index = new AtomicInteger(0);

    @Override
    protected Object determineCurrentLookupKey() {
        
        // 當(dāng)前線程的主從標(biāo)識(shí)
        Boolean master = MasterSlaveDataSourceMarker.get();
        
        if (master == null || master || this.slaveKeys.isEmpty()) {
            // 主庫(kù),返回 null,使用默認(rèn)數(shù)據(jù)源
            log.info("數(shù)據(jù)庫(kù)路由:主庫(kù)");
            return null;
        }
        
        // 從庫(kù),從 slaveKeys 中選擇一個(gè) Key
        int index = this.index.getAndIncrement() % this.slaveKeys.size();

        if (this.index.get() > 9999999) {
            this.index.set(0); 
        }
        
        Object key = slaveKeys.get(index);
        
        log.info("數(shù)據(jù)庫(kù)路由:從庫(kù) = {}", key);
        
        return key;
    }


    public List<Object> getSlaveKeys() {
        return slaveKeys;
    }
    public void setSlaveKeys(List<Object> slaveKeys) {
        this.slaveKeys = slaveKeys;
    }
}

其中,定義了一個(gè) List<Object> slaveKeys 字段,用于存儲(chǔ)在配置文件中定義的所有從庫(kù)的 Key。

在 determineCurrentLookupKey 方法中,判斷當(dāng)前業(yè)務(wù)的 “讀寫狀態(tài)”,如果是只讀則通過(guò) AtomicInteger 原子類自增后從 slaveKeys 輪詢出一個(gè)從庫(kù)的 Key。反之則返回 null 使用主庫(kù)。

九、創(chuàng)建 MasterSlaveDataSourceConfiguration 配置類

最后,需要在 @Configuration 配置類中,創(chuàng)建 MasterSlaveDataSource 數(shù)據(jù)源 Bean。

package cn.springdoc.demo.db;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class MasterSlaveDataSourceConfiguration {

    @Bean
    public DataSource dataSource(MasterSlaveDataSourceProperties properties) {

        MasterSlaveDataSource dataSource = new MasterSlaveDataSource();

        // 主數(shù)據(jù)庫(kù)
        dataSource.setDefaultTargetDataSource(new HikariDataSource(new HikariConfig(properties.master())));

        // 從數(shù)據(jù)庫(kù)
        Map<Object, Object> slaveDataSource = new HashMap<>();
        
        // 從數(shù)據(jù)庫(kù) Key
        dataSource.setSlaveKeys(new ArrayList<>());
        
        for (Map.Entry<String,Properties> entry : properties.slave().entrySet()) {
            
            if (slaveDataSource.containsKey(entry.getKey())) {
                throw new IllegalArgumentException("存在同名的從數(shù)據(jù)庫(kù)定義:" + entry.getKey());
            }
            
            slaveDataSource.put(entry.getKey(), new HikariDataSource(new HikariConfig(entry.getValue())));
            
            dataSource.getSlaveKeys().add(entry.getKey());
        }
        
        // 設(shè)置從庫(kù)
        dataSource.setTargetDataSources(slaveDataSource);

        return dataSource;
    }
}

首先,通過(guò)配置方法注入配置類,該類定義了配置文件中的主庫(kù)、從庫(kù)屬性。

使用 HikariDataSource 實(shí)例化唯一主庫(kù)數(shù)據(jù)源、和多個(gè)從庫(kù)數(shù)據(jù)源,并且設(shè)置到 MasterSlaveDataSource 對(duì)應(yīng)的屬性中。

同時(shí)還存儲(chǔ)每個(gè)從庫(kù)的 Key,且該 Key 不允許重復(fù)。

十、測(cè)試

1、創(chuàng)建 TestService

創(chuàng)建用于測(cè)試的業(yè)務(wù)類。

package cn.springdoc.demo.service;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TestService {
    final JdbcTemplate jdbcTemplate;
    public TestService(JdbcTemplate jdbcTemplate) {
        super();
        this.jdbcTemplate = jdbcTemplate;
    }

    // 只讀
    @Transactional(readOnly = true)
    public String read () {
        return this.jdbcTemplate.queryForObject("SELECT `name` FROM `test` WHERE id = 1;", String.class);
    } 


    // 先讀,再寫
    @Transactional
    public String write () {
        this.jdbcTemplate.update("UPDATE `test` SET `name` = ? WHERE id = 1;", "new name");
        return this.read();
    }
}

通過(guò)構(gòu)造函數(shù)注入 JdbcTemplate(spring jdbc 模塊自動(dòng)配置的)。

Service 類中定義了 2 個(gè)方法。

  • read():只讀業(yè)務(wù),從表中檢索 name 字段返回。
  • write:可寫業(yè)務(wù),先修改表中的 name 字段值為: new name,然后再調(diào)用 read() 方法讀取修改后的結(jié)果、返回。

2、創(chuàng)建測(cè)試類

創(chuàng)建測(cè)試類,如下:

package cn.springdoc.demo.test;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import cn.springdoc.demo.service.TestService;


@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {

    static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);

    @Autowired
    TestService testService;

    @Test
    public void test() throws Exception {

        // 連續(xù)4次讀
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());

        // 寫
        log.info("write={}", this.testService.write());
    }
}

在測(cè)試類方法中,連續(xù)調(diào)用 4 次 TestService 的 read() 方法。由于這是一個(gè)只讀方法,按照我們的設(shè)定,它會(huì)在 3 個(gè)從庫(kù)之間輪詢使用。由于我們故意把三個(gè)從庫(kù) test 表中 name 的字段值設(shè)置得不一樣,所以這里可以通過(guò)返回的結(jié)果看出來(lái)是否符合我們的預(yù)期。

最后調(diào)用了一次 write() 方法,按照設(shè)定會(huì)路由到主庫(kù)。先 UPDATE 修改數(shù)據(jù),再調(diào)用 read() 讀取數(shù)據(jù),雖然 read() 設(shè)置了 @Transactional(readOnly = true),但因?yàn)槿肟诜椒ㄊ?write(),所以 read() 還是會(huì)從主庫(kù)讀取數(shù)據(jù)(默認(rèn)的事務(wù)傳播級(jí)別)。

執(zhí)行測(cè)試,輸出的日志如下:

[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標(biāo)記為從庫(kù)
[           main] c.s.demo.db.MasterSlaveDataSource        : 數(shù)據(jù)庫(kù)路由:從庫(kù) = slave1
[           main] c.s.demo.test.DemoApplicationTests       : read=slave1
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標(biāo)記為從庫(kù)
[           main] c.s.demo.db.MasterSlaveDataSource        : 數(shù)據(jù)庫(kù)路由:從庫(kù) = slave2
[           main] c.s.demo.test.DemoApplicationTests       : read=slave2
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標(biāo)記為從庫(kù)
[           main] c.s.demo.db.MasterSlaveDataSource        : 數(shù)據(jù)庫(kù)路由:從庫(kù) = slave3
[           main] c.s.demo.test.DemoApplicationTests       : read=slave3
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標(biāo)記為從庫(kù)
[           main] c.s.demo.db.MasterSlaveDataSource        : 數(shù)據(jù)庫(kù)路由:從庫(kù) = slave1
[           main] c.s.demo.test.DemoApplicationTests       : read=slave1
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標(biāo)記為主庫(kù)
[           main] c.s.demo.db.MasterSlaveDataSource        : 數(shù)據(jù)庫(kù)路由:主庫(kù)
[           main] c.s.demo.test.DemoApplicationTests       : write=new name

你可以看到,對(duì)于只讀業(yè)務(wù)。確實(shí)輪詢了三個(gè)不同的從庫(kù),符合預(yù)期。最后的 write() 方法也成功地路由到了主庫(kù),執(zhí)行了修改并且返回了修改后的結(jié)果。

十一總結(jié)

通過(guò) AbstractRoutingDataSource 可以不使用任何第三方中間件就可以在 Spring Boot 中實(shí)現(xiàn)數(shù)據(jù)源 “讀寫分離”,這種方式需要在每個(gè)業(yè)務(wù)方法上通過(guò) @Transactional 注解明確定義是讀還是寫。

到此這篇關(guān)于SpringBoot配置主從數(shù)據(jù)庫(kù)實(shí)現(xiàn)讀寫分離的文章就介紹到這了,更多相關(guān)SpringBoot 讀寫分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java基礎(chǔ)題新手練習(xí)(二)

    Java基礎(chǔ)題新手練習(xí)(二)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你
    2021-07-07
  • MyBatis?Xml映射文件之字符串替換方式

    MyBatis?Xml映射文件之字符串替換方式

    這篇文章主要介紹了MyBatis?Xml映射文件之字符串替換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot實(shí)現(xiàn)單元測(cè)試示例詳解

    SpringBoot實(shí)現(xiàn)單元測(cè)試示例詳解

    單元測(cè)試(unit testing),是指對(duì)軟件中的最小可測(cè)試單元進(jìn)行檢查和驗(yàn)證。這篇文章主要為大家介紹了C語(yǔ)言實(shí)現(xiàn)單元測(cè)試的方法,需要的可以參考一下
    2022-11-11
  • SpringMVC文件上傳 多文件上傳實(shí)例

    SpringMVC文件上傳 多文件上傳實(shí)例

    這篇文章主要介紹了SpringMVC文件上傳 多文件上傳實(shí)例,有需要的朋友可以參考一下
    2014-01-01
  • Java的Synchronized關(guān)鍵字學(xué)習(xí)指南(全面 & 詳細(xì))

    Java的Synchronized關(guān)鍵字學(xué)習(xí)指南(全面 & 詳細(xì))

    這篇文章主要給大家介紹了關(guān)于Java的Synchronized關(guān)鍵字的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • Java中線程Thread的三種方式和對(duì)比

    Java中線程Thread的三種方式和對(duì)比

    這篇文章主要介紹了Java中線程Thread的三種方式和對(duì)比,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • Quarkus改造Pmml模型項(xiàng)目異常記錄及解決處理

    Quarkus改造Pmml模型項(xiàng)目異常記錄及解決處理

    這篇文章主要為大家介紹了Quarkus改造Pmml模型項(xiàng)目是遇到的異常記錄以及解決方法,有需要的同學(xué)可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-02-02
  • java集合框架詳解

    java集合框架詳解

    本文主要介紹了java集合框架的相關(guān)知識(shí)。具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-01-01
  • Java如何實(shí)現(xiàn)遠(yuǎn)程文件下載到本地目錄

    Java如何實(shí)現(xiàn)遠(yuǎn)程文件下載到本地目錄

    本文介紹了如何使用Java來(lái)實(shí)現(xiàn)遠(yuǎn)程文件的下載功能,主要通過(guò)HTTPS路徑下載文件到本地目錄,詳細(xì)介紹了相關(guān)代碼和測(cè)試步驟,并提供了實(shí)際案例供參考,本文旨在幫助需要實(shí)現(xiàn)文件下載功能的開發(fā)者快速掌握核心技術(shù)
    2024-10-10
  • springboot項(xiàng)目打成war包部署到tomcat遇到的一些問(wèn)題

    springboot項(xiàng)目打成war包部署到tomcat遇到的一些問(wèn)題

    這篇文章主要介紹了springboot項(xiàng)目打成war包部署到tomcat遇到的一些問(wèn)題,需要的朋友可以參考下
    2017-06-06

最新評(píng)論