最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring實現(xiàn)數(shù)據(jù)庫讀寫分離詳解

 更新時間:2024年01月25日 08:34:33   作者:小明的學習圈子  
這篇文章主要介紹了Spring?實現(xiàn)數(shù)據(jù)庫讀寫分離,大多數(shù)系統(tǒng)都是讀多寫少,為了降低數(shù)據(jù)庫的壓力,可以對主庫創(chuàng)建多個從庫,從庫自動從主庫同步數(shù)據(jù),程序中將寫的操作發(fā)送到主庫,將讀的操作發(fā)送到從庫去執(zhí)行,需要的朋友可以參考下

1、背景

大多數(shù)系統(tǒng)都是讀多寫少,為了降低數(shù)據(jù)庫的壓力,可以對主庫創(chuàng)建多個從庫,從庫自動從主庫同步數(shù)據(jù),程序中將寫的操作發(fā)送到主庫,將讀的操作發(fā)送到從庫去執(zhí)行。

今天的主要目標:通過 spring 實現(xiàn)讀寫分離。

讀寫分離需實現(xiàn)下面 2 個功能:

1、讀的方法,由調(diào)用者來控制具體是讀從庫還是主庫

2、有事務(wù)的方法,內(nèi)部的所有讀寫操作都走主庫

2、思考 3 個問題

讀的方法,由調(diào)用者來控制具體是讀從庫還是主庫,如何實現(xiàn)?

可以給所有讀的方法添加一個參數(shù),來控制讀從庫還是主庫。

數(shù)據(jù)源如何路由?

spring-jdbc 包中提供了一個抽象類:AbstractRoutingDataSource,實現(xiàn)了 javax.sql.DataSource 接口,我們用這個類來作為數(shù)據(jù)源類,重點是這個類可以用來做數(shù)據(jù)源的路由,可以在其內(nèi)部配置多個真實的數(shù)據(jù)源,最終用哪個數(shù)據(jù)源,由開發(fā)者來決定。

AbstractRoutingDataSource 中有個 map,用來存儲多個目標數(shù)據(jù)源

private Map<Object, DataSource> resolvedDataSources;

比如主從庫可以這么存儲

resolvedDataSources.put("master",主庫數(shù)據(jù)源);
resolvedDataSources.put("salave",從庫數(shù)據(jù)源);

AbstractRoutingDataSource 中還有抽象方法determineCurrentLookupKey,將這個方法的返回值作為 key 到上面的 resolvedDataSources 中查找對應(yīng)的數(shù)據(jù)源,作為當前操作 db 的數(shù)據(jù)源

protected abstract Object determineCurrentLookupKey();

讀寫分離在哪控制?

讀寫分離屬于一個通用的功能,可以通過 spring 的 aop 來實現(xiàn),添加一個攔截器,攔截目標方法的之前,在目標方法執(zhí)行之前,獲取一下當前需要走哪個庫,將這個標志存儲在 ThreadLocal 中,將這個標志作為 AbstractRoutingDataSource.determineCurrentLookupKey()方法的返回值,攔截器中在目標方法執(zhí)行完畢之后,將這個標志從 ThreadLocal 中清除。

3、代碼實現(xiàn)

DsType

表示數(shù)據(jù)源類型,有 2 個值,用來區(qū)分是主庫還是從庫。

package com.javacode2018.readwritesplit.base;
public enum DsType {
    MASTER, SLAVE;
}

DsTypeHolder

內(nèi)部有個 ThreadLocal,用來記錄當前走主庫還是從庫,將這個標志放在 dsTypeThreadLocal 中

package com.javacode2018.readwritesplit.base;
public class DsTypeHolder {
    private static ThreadLocal<DsType> dsTypeThreadLocal = new ThreadLocal<>();
    public static void master() {
        dsTypeThreadLocal.set(DsType.MASTER);
    }
    public static void slave() {
        dsTypeThreadLocal.set(DsType.SLAVE);
    }
    public static DsType getDsType() {
        return dsTypeThreadLocal.get();
    }
    public static void clearDsType() {
        dsTypeThreadLocal.remove();
    }
}

IService 接口

這個接口起到標志的作用,當某個類需要啟用讀寫分離的時候,需要實現(xiàn)這個接口,實現(xiàn)這個接口的類都會被讀寫分離攔截器攔截。

package com.javacode2018.readwritesplit.base;
//需要實現(xiàn)讀寫分離的service需要實現(xiàn)該接口
public interface IService {
}

ReadWriteDataSource

讀寫分離數(shù)據(jù)源,繼承 ReadWriteDataSource,注意其內(nèi)部的 determineCurrentLookupKey 方法,從上面的 ThreadLocal 中獲取當前需要走主庫還是從庫的標志。

package com.javacode2018.readwritesplit.base;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
public class ReadWriteDataSource extends AbstractRoutingDataSource {
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DsTypeHolder.getDsType();
    }
}

ReadWriteInterceptor

讀寫分離攔截器,需放在事務(wù)攔截器前面執(zhí)行,通過@1 代碼我們將此攔截器的順序設(shè)置為 Integer.MAX_VALUE - 2,稍后我們將事務(wù)攔截器的順序設(shè)置為 Integer.MAX_VALUE - 1,事務(wù)攔截器的執(zhí)行順序是從小到達的,所以,ReadWriteInterceptor 會在事務(wù)攔截器 org.springframework.transaction.interceptor.TransactionInterceptor 之前執(zhí)行。

由于業(yè)務(wù)方法中存在相互調(diào)用的情況,比如 service1.m1 中調(diào)用 service2.m2,而 service2.m2 中調(diào)用了 service2.m3,我們只需要在 m1 方法執(zhí)行之前,獲取具體要用哪個數(shù)據(jù)源就可以了,所以下面代碼中會在第一次進入這個攔截器的時候,記錄一下走主庫還是從庫。

下面方法中會獲取當前目標方法的最后一個參數(shù),最后一個參數(shù)可以是 DsType 類型的,開發(fā)者可以通過這個參數(shù)來控制具體走主庫還是從庫。

package com.javacode2018.readwritesplit.base;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
@Aspect
@Order(Integer.MAX_VALUE - 2) //@1
@Component
public class ReadWriteInterceptor {
    @Pointcut("target(IService)")
    public void pointcut() {
    }
    //獲取當前目標方法的最后一個參數(shù)
    private Object getLastArgs(final ProceedingJoinPoint pjp) {
        Object[] args = pjp.getArgs();
        if (Objects.nonNull(args) && args.length > 0) {
            return args[args.length - 1];
        } else {
            return null;
        }
    }
    @Around("pointcut()")
    public Object around(final ProceedingJoinPoint pjp) throws Throwable {
        //判斷是否是第一次進來,用于處理事務(wù)嵌套
        boolean isFirst = false;
        try {
            if (DsTypeHolder.getDsType() == null) {
                isFirst = true;
            }
            if (isFirst) {
                Object lastArgs = getLastArgs(pjp);
                if (DsType.SLAVE.equals(lastArgs)) {
                    DsTypeHolder.slave();
                } else {
                    DsTypeHolder.master();
                }
            }
            return pjp.proceed();
        } finally {
            //退出的時候,清理
            if (isFirst) {
                DsTypeHolder.clearDsType();
            }
        }
    }
}

ReadWriteConfiguration

spring 配置類,作用

1、@3:用來將 com.javacode2018.readwritesplit.base 包中的一些類注冊到 spring 容器中,比如上面的攔截器 ReadWriteInterceptor

2、@1:開啟 spring aop 的功能

3、@2:開啟 spring 自動管理事務(wù)的功能,@EnableTransactionManagement 的 order 用來指定事務(wù)攔截器 org.springframework.transaction.interceptor.TransactionInterceptor 順序,在這里我們將 order 設(shè)置為 Integer.MAX_VALUE - 1,而上面 ReadWriteInterceptor 的 order 是 Integer.MAX_VALUE - 2,所以 ReadWriteInterceptor 會在事務(wù)攔截器之前執(zhí)行。

package com.javacode2018.readwritesplit.base;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableAspectJAutoProxy //@1
@EnableTransactionManagement(proxyTargetClass = true, order = Integer.MAX_VALUE - 1) //@2
@ComponentScan(basePackageClasses = IService.class) //@3
public class ReadWriteConfiguration {
}

@EnableReadWrite

這個注解用倆開啟讀寫分離的功能,@1 通過@Import 將 ReadWriteConfiguration 導入到 spring 容器了,這樣就會自動啟用讀寫分離的功能。業(yè)務(wù)中需要使用讀寫分離,只需要在 spring 配置類中加上@EnableReadWrite 注解就可以了。

package com.javacode2018.readwritesplit.base;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ReadWriteConfiguration.class) //@1
public @interface EnableReadWrite {
}

4、案例

讀寫分離的關(guān)鍵代碼寫完了,下面我們來上案例驗證一下效果。

執(zhí)行 sql 腳本

下面準備 2 個數(shù)據(jù)庫:javacode2018_master(主庫)、javacode2018_slave(從庫)

2 個庫中都創(chuàng)建一個 t_user 表,分別插入了一條數(shù)據(jù),稍后用這個數(shù)據(jù)來驗證走的是主庫還是從庫。

DROP DATABASE IF EXISTS javacode2018_master;
CREATE DATABASE IF NOT EXISTS javacode2018_master;
USE javacode2018_master;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
  id   INT PRIMARY KEY       AUTO_INCREMENT,
  name VARCHAR(256) NOT NULL DEFAULT ''
  COMMENT '姓名'
);
INSERT INTO t_user (name) VALUE ('master庫');
DROP DATABASE IF EXISTS javacode2018_slave;
CREATE DATABASE IF NOT EXISTS javacode2018_slave;
USE javacode2018_slave;
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user (
  id   INT PRIMARY KEY       AUTO_INCREMENT,
  name VARCHAR(256) NOT NULL DEFAULT ''
  COMMENT '姓名'
);
INSERT INTO t_user (name) VALUE ('slave庫');

spring 配置類

@1:啟用讀寫分離

masterDs()方法:定義主庫數(shù)據(jù)源

slaveDs()方法:定義從庫數(shù)據(jù)源

dataSource():定義讀寫分離路由數(shù)據(jù)源

后面還有 2 個方法用來定義 JdbcTemplate 和事務(wù)管理器,方法中都通過@Qualifier(“dataSource”)限定了注入的 bean 名稱為 dataSource:即注入了上面 dataSource()返回的讀寫分離路由數(shù)據(jù)源。

package com.javacode2018.readwritesplit.demo1;

import com.javacode2018.readwritesplit.base.DsType;
import com.javacode2018.readwritesplit.base.EnableReadWrite;
import com.javacode2018.readwritesplit.base.ReadWriteDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

@EnableReadWrite //@1
@Configuration
@ComponentScan
public class MainConfig {
    //主庫數(shù)據(jù)源
    @Bean
    public DataSource masterDs() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018_master?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //從庫數(shù)據(jù)源
    @Bean
    public DataSource slaveDs() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/javacode2018_slave?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    //讀寫分離路由數(shù)據(jù)源
    @Bean
    public ReadWriteDataSource dataSource() {
        ReadWriteDataSource dataSource = new ReadWriteDataSource();
        //設(shè)置主庫為默認的庫,當路由的時候沒有在datasource那個map中找到對應(yīng)的數(shù)據(jù)源的時候,會使用這個默認的數(shù)據(jù)源
        dataSource.setDefaultTargetDataSource(this.masterDs());
        //設(shè)置多個目標庫
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DsType.MASTER, this.masterDs());
        targetDataSources.put(DsType.SLAVE, this.slaveDs());
        dataSource.setTargetDataSources(targetDataSources);
        return dataSource;
    }

    //JdbcTemplate,dataSource為上面定義的注入讀寫分離的數(shù)據(jù)源
    @Bean
    public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    //定義事務(wù)管理器,dataSource為上面定義的注入讀寫分離的數(shù)據(jù)源
    @Bean
    public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

UserService

這個類就相當于我們平時寫的 service,我是為了方法,直接在里面使用了 JdbcTemplate 來操作數(shù)據(jù)庫,真實的項目操作 db 會放在 dao 里面。

getUserNameById 方法:通過 id 查詢 name。

insert 方法:插入數(shù)據(jù),這個內(nèi)部的所有操作都會走主庫,為了驗證是不是查詢也會走主庫,插入數(shù)據(jù)之后,我們會調(diào)用 this.userService.getUserNameById(id, DsType.SLAVE)方法去執(zhí)行查詢操作,第二個參數(shù)故意使用 SLAVE,如果查詢有結(jié)果,說明走的是主庫,否則走的是從庫,這里為什么需要通過 this.userService 來調(diào)用 getUserNameById?

this.userService 最終是個代理對象,通過代理對象訪問其內(nèi)部的方法,才會被讀寫分離的攔截器攔截。

package com.javacode2018.readwritesplit.demo1;
import com.javacode2018.readwritesplit.base.DsType;
import com.javacode2018.readwritesplit.base.IService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Component
public class UserService implements IService {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private UserService userService;
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public String getUserNameById(long id, DsType dsType) {
        String sql = "select name from t_user where id=?";
        List<String> list = this.jdbcTemplate.queryForList(sql, String.class, id);
        return (list != null && list.size() > 0) ? list.get(0) : null;
    }
    //這個insert方法會走主庫,內(nèi)部的所有操作都會走主庫
    @Transactional
    public void insert(long id, String name) {
        System.out.println(String.format("插入數(shù)據(jù){id:%s, name:%s}", id, name));
        this.jdbcTemplate.update("insert into t_user (id,name) values (?,?)", id, name);
        String userName = this.userService.getUserNameById(id, DsType.SLAVE);
        System.out.println("查詢結(jié)果:" + userName);
    }
}

測試用例

package com.javacode2018.readwritesplit.demo1;
import com.javacode2018.readwritesplit.base.DsType;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Demo1Test {
    UserService userService;
    @Before
    public void before() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(MainConfig.class);
        context.refresh();
        this.userService = context.getBean(UserService.class);
    }
    @Test
    public void test1() {
        System.out.println(this.userService.getUserNameById(1, DsType.MASTER));
        System.out.println(this.userService.getUserNameById(1, DsType.SLAVE));
    }
    @Test
    public void test2() {
        long id = System.currentTimeMillis();
        System.out.println(id);
        this.userService.insert(id, "張三");
    }
}

test1 方法執(zhí)行 2 次查詢,分別查詢主庫和從庫,輸出:

master庫
slave庫

是不是很爽,由開發(fā)者自己控制具體走主庫還是從庫。

test2 執(zhí)行結(jié)果如下,可以看出查詢到了剛剛插入的數(shù)據(jù),說明 insert 中所有操作都走的是主庫。

1604905117467
插入數(shù)據(jù){id:1604905117467, name:張三}
查詢結(jié)果:張三

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

相關(guān)文章

  • Struts 2 配置Action詳解

    Struts 2 配置Action詳解

    本篇文章主要介紹了Struts 2 配置Action詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-10-10
  • Java中常用修飾符的使用方法匯總

    Java中常用修飾符的使用方法匯總

    下面小編就為大家?guī)硪黄狫ava中常用修飾符的使用方法匯總。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01
  • Java后臺實現(xiàn)微信支付和微信退款

    Java后臺實現(xiàn)微信支付和微信退款

    這篇文章主要介紹了Java后臺實現(xiàn)微信支付和微信退款,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • Java 數(shù)據(jù)結(jié)構(gòu)之時間復雜度與空間復雜度詳解

    Java 數(shù)據(jù)結(jié)構(gòu)之時間復雜度與空間復雜度詳解

    算法復雜度分為時間復雜度和空間復雜度。其作用: 時間復雜度是度量算法執(zhí)行的時間長短;而空間復雜度是度量算法所需存儲空間的大小
    2021-11-11
  • 如何通過自定義spring?invalidator注解校驗數(shù)據(jù)合法性

    如何通過自定義spring?invalidator注解校驗數(shù)據(jù)合法性

    這篇文章主要介紹了如何通過自定義spring?invalidator注解校驗數(shù)據(jù)合法性,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • 如何使用?Spring?Boot?搭建?WebSocket?服務(wù)器實現(xiàn)多客戶端連接

    如何使用?Spring?Boot?搭建?WebSocket?服務(wù)器實現(xiàn)多客戶端連接

    本文介紹如何使用SpringBoot快速搭建WebSocket服務(wù)器,實現(xiàn)多客戶端連接和消息廣播,WebSocket協(xié)議提供全雙工通信,SpringBoot通過@ServerEndpoint簡化配置,支持實時消息推送,適用于聊天室或通知系統(tǒng)等應(yīng)用場景
    2024-11-11
  • SpringBoot通過Nginx代理獲取真實IP

    SpringBoot通過Nginx代理獲取真實IP

    springboot作為后臺代碼,獲取到的登錄IP是前臺的代理服務(wù)器地址,并不是用戶的真實IP地址,本文主要介紹了SpringBoot通過Nginx代理獲取真實IP,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • Java中StopWatch工具類的用法詳解

    Java中StopWatch工具類的用法詳解

    stopWatch 是org.springframework.util 包下的一個工具類,使用它可直觀的輸出代碼執(zhí)行耗時,以及執(zhí)行時間百分比,下面就跟隨小編一起來看看它的具體用法吧
    2024-12-12
  • mybatis中大批量數(shù)據(jù)插入解析

    mybatis中大批量數(shù)據(jù)插入解析

    這篇文章主要介紹了mybatis中大批量數(shù)據(jù)插入解析,使用Mybatis框架批量插入的3種方法,分別是多次調(diào)用insert方法、foreach標簽、batch模式,本文來詳細說明一下,需要的朋友可以參考下
    2024-01-01
  • MyBatis框架迭代器模式實現(xiàn)原理解析

    MyBatis框架迭代器模式實現(xiàn)原理解析

    這篇文章主要介紹了MyBatis框架迭代器模式實現(xiàn)原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03

最新評論

襄城县| 灵台县| 会泽县| 启东市| 天长市| 合山市| 扬州市| 浮山县| 夹江县| 德钦县| 湖州市| 新安县| 奇台县| 兴隆县| 应用必备| 左云县| 来安县| 湖北省| 汝城县| 北京市| 那曲县| 宜宾县| 新干县| 永修县| 枣强县| 哈巴河县| 佳木斯市| 化德县| 东辽县| 稷山县| 静宁县| 屏南县| 西吉县| 颍上县| 古浪县| 上虞市| 内江市| 五原县| 许昌市| 遂川县| 天峨县|