亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot+MyBatis+AOP實(shí)現(xiàn)讀寫分離的示例代碼

 更新時(shí)間:2021年11月12日 11:02:08   作者:Java后端何哥  
高并發(fā)這個(gè)階段,肯定是需要做MySQL讀寫分離的。本文主要介紹了SpringBoot+MyBatis+AOP實(shí)現(xiàn)讀寫分離的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言:高并發(fā)這個(gè)階段,肯定是需要做MySQL讀寫分離的。實(shí)際上大部分的互聯(lián)網(wǎng)網(wǎng)站或者App,其實(shí)都是讀多寫少。所以針對(duì)這個(gè)情況,就是寫一個(gè)主庫(kù),但是主庫(kù)掛多個(gè)從庫(kù),然后從多個(gè)從庫(kù)來(lái)讀,那不就可以支撐更高的讀并發(fā)壓力了嗎?

一、 MySQL 讀寫分離

1.1、如何實(shí)現(xiàn) MySQL 的讀寫分離?

其實(shí)很簡(jiǎn)單,就是基于主從復(fù)制架構(gòu)。簡(jiǎn)單來(lái)說(shuō),就搞一個(gè)主庫(kù),掛多個(gè)從庫(kù),然后我們就單單只是寫主庫(kù),然后主庫(kù)會(huì)自動(dòng)把數(shù)據(jù)給同步到從去,多個(gè)從庫(kù)用于讀。

讀寫分離就是對(duì)于一條SQL該選擇哪一個(gè)數(shù)據(jù)庫(kù)去執(zhí)行,至于誰(shuí)來(lái)做選擇數(shù)據(jù)庫(kù)這件事,有兩個(gè),要么使用中間件幫我們做,要么程序自己做。一般來(lái)說(shuō),讀寫分離有兩種實(shí)現(xiàn)方式。第一種是依靠中間件MyCat或Sharding-JDBC,也就是說(shuō)應(yīng)用程序連接到中間件,中間件幫我們做SQL分離,去選擇指定的數(shù)據(jù)源;第二種是應(yīng)用程序自己去做分離。這里我用程序自己來(lái)做,主要是利用Spring提供的路由數(shù)據(jù)源,以及AOP。

 1.2、MySQL 主從復(fù)制原理?

主庫(kù)將變更寫入 binlog 日志,然后從庫(kù)連接到主庫(kù)之后,從庫(kù)有一個(gè) IO 線程,將主庫(kù)的 binlog 日志拷貝到自己本地,寫入一個(gè) relay 中繼日志中。接著從庫(kù)中有一個(gè) SQL 線程會(huì)從中繼日志讀取 binlog,然后執(zhí)行 binlog 日志中的內(nèi)容,也就是在自己本地再次執(zhí)行一遍 SQL,這樣就可以保證自己跟主庫(kù)的數(shù)據(jù)是一樣的。

 

mysql-master-slave

 這里有一個(gè)非常重要的一點(diǎn),就是從庫(kù)同步主庫(kù)數(shù)據(jù)的過(guò)程是串行化的,也就是說(shuō)主庫(kù)上并行的操作,在從庫(kù)上會(huì)串行執(zhí)行。所以這就是一個(gè)非常重要的點(diǎn)了,由于從庫(kù)從主庫(kù)拷貝日志以及串行執(zhí)行 SQL 的特點(diǎn),在高并發(fā)場(chǎng)景下,從庫(kù)的數(shù)據(jù)一定會(huì)比主庫(kù)慢一些,是有延時(shí)的。所以經(jīng)常出現(xiàn),剛寫入主庫(kù)的數(shù)據(jù)可能是讀不到的,要過(guò)幾十毫秒,甚至幾百毫秒才能讀取到。

而且這里還有另外一個(gè)問(wèn)題,就是如果主庫(kù)突然宕機(jī),然后恰好數(shù)據(jù)還沒(méi)同步到從庫(kù),那么有些數(shù)據(jù)可能在從庫(kù)上是沒(méi)有的,有些數(shù)據(jù)可能就丟失了。

所以 MySQL 實(shí)際上在這一塊有兩個(gè)機(jī)制,一個(gè)是半同步復(fù)制,用來(lái)解決主庫(kù)數(shù)據(jù)丟失問(wèn)題;一個(gè)是并行復(fù)制,用來(lái)解決主從同步延時(shí)問(wèn)題。

這個(gè)所謂半同步復(fù)制,也叫 semi-sync 復(fù)制,指的就是主庫(kù)寫入 binlog 日志之后,就會(huì)將強(qiáng)制此時(shí)立即將數(shù)據(jù)同步到從庫(kù),從庫(kù)將日志寫入自己本地的 relay log 之后,接著會(huì)返回一個(gè) ack 給主庫(kù),主庫(kù)接收到至少一個(gè)從庫(kù)的 ack 之后才會(huì)認(rèn)為寫操作完成了。

所謂并行復(fù)制,指的是從庫(kù)開(kāi)啟多個(gè)線程,并行讀取 relay log 中不同庫(kù)的日志,然后并行重放不同庫(kù)的日志,這是庫(kù)級(jí)別的并行。

1.3、MySQL 主從同步延時(shí)問(wèn)題(精華)

線上會(huì)發(fā)現(xiàn),每天總有那么一些數(shù)據(jù),我們期望更新一些重要的數(shù)據(jù)狀態(tài),但在高峰期時(shí)候卻沒(méi)更新。用戶跟客服反饋,而客服就會(huì)反饋給我們。

(1) 主從同步延遲的原因

一個(gè)服務(wù)器開(kāi)放N個(gè)鏈接給客戶端來(lái)連接的,這樣有會(huì)有大并發(fā)的更新操作, 但是從服務(wù)器的里面讀取binlog的線程僅有一個(gè),當(dāng)某個(gè)SQL在從服務(wù)器上執(zhí)行的時(shí)間稍長(zhǎng)或者由于某個(gè)SQL要進(jìn)行鎖表就會(huì)導(dǎo)致,主服務(wù)器的SQL大量積壓,未被同步到從服務(wù)器里。這就導(dǎo)致了主從不一致, 也就是主從延遲。

(2) 主從同步延遲的解決辦法

一般來(lái)說(shuō),如果主從延遲較為嚴(yán)重,有以下解決方案:

  • 分庫(kù):將一個(gè)主庫(kù)拆分為多個(gè)主庫(kù),每個(gè)主庫(kù)的寫并發(fā)就減少了幾倍,此時(shí)主從延遲可以忽略不計(jì)。
  • 需要走主庫(kù)的強(qiáng)制走主庫(kù)查詢:如果確實(shí)是存在必須先插入,立馬要求就查詢到,然后立馬就要反過(guò)來(lái)執(zhí)行一些操作,對(duì)這個(gè)查詢?cè)O(shè)置直連主庫(kù)。
  • 業(yè)務(wù)層面妥協(xié),重寫代碼寫代碼的同學(xué)要慎重,插入數(shù)據(jù)時(shí)立馬查詢可能查不到。是否操作完之后馬上要馬上進(jìn)行讀取?

二、SpringBoot+AOP+MyBatis實(shí)現(xiàn)MySQL讀寫分離

代碼環(huán)境是 SpringBoot+MyBatis+AOP。想要讀寫分離就需要配置多個(gè)數(shù)據(jù)源,在進(jìn)行寫操作是選擇寫的數(shù)據(jù)源(主庫(kù)),讀操作時(shí)選擇讀的數(shù)據(jù)源(從庫(kù))。

2.1、AbstractRoutingDataSource

SpringBoot提供了AbstractRoutingDataSource類根據(jù)用戶定義的規(guī)則選擇當(dāng)前的數(shù)據(jù)源,這樣我們可以在執(zhí)行查詢之前,設(shè)置使用的數(shù)據(jù)源。實(shí)現(xiàn)可動(dòng)態(tài)路由的數(shù)據(jù)源,在每次數(shù)據(jù)庫(kù)查詢操作前執(zhí)行。它的抽象方法 determineCurrentLookupKey() 決定使用哪個(gè)數(shù)據(jù)源

想要讀寫分離就需要配置多個(gè)數(shù)據(jù)源,在進(jìn)行寫操作是選擇寫的數(shù)據(jù)源,讀操作時(shí)選擇讀的數(shù)據(jù)源。其中有兩個(gè)關(guān)鍵點(diǎn):

  • 如何切換數(shù)據(jù)源
  • 如何根據(jù)不同的方法選擇正確的數(shù)據(jù)源

2.2、如何切換數(shù)據(jù)源

 通常用 springboot 時(shí)都是使用它的默認(rèn)配置,只需要在配置文件中定義好連接屬性就行了,但是現(xiàn)在我們需要自己來(lái)配置了,spring 是支持多數(shù)據(jù)源的,多個(gè) datasource 放在一個(gè) HashMapTargetDataSource中,通過(guò)dertermineCurrentLookupKey獲取 key 來(lái)覺(jué)定要使用哪個(gè)數(shù)據(jù)源。因此我們的目標(biāo)就很明確了,建立多個(gè) datasource 放到 TargetDataSource 中,同時(shí)重寫 dertermineCurrentLookupKey 方法來(lái)決定使用哪個(gè) key。

2.3、如何選擇數(shù)據(jù)源

 事務(wù)一般是注解在 Service 層的,因此在開(kāi)始這個(gè) service 方法調(diào)用時(shí)要確定數(shù)據(jù)源,有什么通用方法能夠在開(kāi)始執(zhí)行一個(gè)方法前做操作呢?相信你已經(jīng)想到了那就是**切面 **。怎么切有兩種辦法:

  • 注解式,定義一個(gè)只讀注解,被該數(shù)據(jù)標(biāo)注的方法使用讀庫(kù)
  • 方法名,根據(jù)方法名寫切點(diǎn),比如 getXXX 用讀庫(kù),setXXX 用寫庫(kù)

三 、代碼實(shí)現(xiàn)

 3.0、工程目錄結(jié)構(gòu)

 3.1、引入Maven依賴

<dependencies>
 
        <!--SpringBoot集成Aop起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
 
        <!--SpringBoot集成WEB起步依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <!--mybatis集成SpringBoot起步依賴-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
 
        <!--MySQL驅(qū)動(dòng)-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
 
        <!--SpringBoot單元測(cè)試依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
 
 </dependencies>

3.2、編寫配置文件,配置主從數(shù)據(jù)源

spring:
  datasource:
      #主數(shù)據(jù)源
      master:
        name: test
        jdbc-url: jdbc:mysql://xxxxxx:3306/test?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8
        username: root
        password: xxxxxx
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          maximum-pool-size: 20
          max-lifetime: 30000
          idle-timeout: 30000
          data-source-properties:
            prepStmtCacheSize: 250
            prepStmtCacheSqlLimit: 2048
            cachePrepStmts: true
            useServerPrepStmts: true
      #從數(shù)據(jù)源
      slave:
        name: test
        jdbc-url: jdbc:mysql://xxxxxx:3306/test?allowMultiQueries=true&useSSL=false&useUnicode=true&characterEncoding=utf-8
        username: root
        password: xxxxxx
        driver-class-name: com.mysql.cj.jdbc.Driver
        hikari:
          maximum-pool-size: 20
          max-lifetime: 30000
          idle-timeout: 30000
          data-source-properties:
            prepStmtCacheSize: 250
            prepStmtCacheSqlLimit: 2048
            cachePrepStmts: true
            useServerPrepStmts: true
 
#MyBatis:
# mapper-locations: classpath:mapper/*.xml
#  type-aliases-package: com.hs.demo.entity

3.3、Enum類,定義主庫(kù)從庫(kù)

定義一個(gè)枚舉類來(lái)代表這三個(gè)數(shù)據(jù)源

package com.hs.demo.config;
 
/**
 * Enum類,定義主庫(kù)從庫(kù)兩個(gè)數(shù)據(jù)源
 */
public enum DBTypeEnum {
 
    MASTER, SLAVE;
}

3.4、ThreadLocal定義數(shù)據(jù)源切換

通過(guò)ThreadLocal將數(shù)據(jù)源綁定到每個(gè)線程上下文中,ThreadLocal 用來(lái)保存每個(gè)線程的是使用讀庫(kù)還是寫庫(kù)。操作結(jié)束后清除該數(shù)據(jù),避免內(nèi)存泄漏。

package com.hs.demo.config;
 
/**
 *ThreadLocal定義數(shù)據(jù)源切換,通過(guò)ThreadLocal將數(shù)據(jù)源綁定到每個(gè)線程上下文中
 */
public class DBContextHolder {
 
    /**
     * ThreadLocal 不是 Thread,是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),對(duì)數(shù)據(jù)存儲(chǔ)后,只有在線程中才可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來(lái)說(shuō)是無(wú)法獲取到數(shù)據(jù)。
     * 大致意思就是ThreadLocal提供了線程內(nèi)存儲(chǔ)變量的能力,這些變量不同之處在于每一個(gè)線程讀取的變量是對(duì)應(yīng)的互相獨(dú)立的,通過(guò)get和set方法就可以得到當(dāng)前線程對(duì)應(yīng)的值。
     */
    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
 
    public static void set(DBTypeEnum dbTypeEnum){
        contextHolder.set(dbTypeEnum);
    }
 
    public static DBTypeEnum get() {
        return contextHolder.get();
    }
 
    public static void master() {
        set(DBTypeEnum.MASTER);
        System.out.println("--------以下操作為master(寫操作)--------");
    }
 
    public static void slave() {
        set(DBTypeEnum.SLAVE);
        System.out.println("--------以下操作為slave(讀操作)--------");
    }
 
    public static void clear() {
        contextHolder.remove();
    }
}

3.5、重寫路由選擇類

重寫 determineCurrentLookupKey 方法,獲取當(dāng)前線程上綁定的路由key。Spring 在開(kāi)始進(jìn)行數(shù)據(jù)庫(kù)操作時(shí)會(huì)通過(guò)這個(gè)方法來(lái)決定使用哪個(gè)數(shù)據(jù)庫(kù)源,因此我們?cè)谶@里調(diào)用上面 DbContextHolder 類的getDbType()方法獲取當(dāng)前操作類別。

  • AbstractRoutingDataSource的getConnection() 方法根據(jù)查找 lookup key 鍵對(duì)不同目標(biāo)數(shù)據(jù)源的調(diào)用,通常是通過(guò)(但不一定)某些線程綁定的事物上下文來(lái)實(shí)現(xiàn)。
  • AbstractRoutingDataSource的多數(shù)據(jù)源動(dòng)態(tài)切換的核心邏輯是:在程序運(yùn)行時(shí),把數(shù)據(jù)源數(shù)據(jù)源通過(guò) AbstractRoutingDataSource 動(dòng)態(tài)織入到程序中,靈活的進(jìn)行數(shù)據(jù)源切換。
  • 基于AbstractRoutingDataSource的多數(shù)據(jù)源動(dòng)態(tài)切換,可以實(shí)現(xiàn)讀寫分離,這么做缺點(diǎn)也很明顯,無(wú)法動(dòng)態(tài)的增加數(shù)據(jù)源。
package com.hs.demo.config;
 
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
 
/**
 *  重寫路由選擇類:獲取當(dāng)前線程上綁定的路由key
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {
 
    /**
     * determineCurrentLookupKey()方法決定使用哪個(gè)數(shù)據(jù)源、
     * 根據(jù)Key獲取數(shù)據(jù)源的信息,上層抽象函數(shù)的鉤子
     */
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

3.6、配置多數(shù)據(jù)源

這里配置了3個(gè)數(shù)據(jù)源,1個(gè)master,1個(gè)slave,1個(gè)路由數(shù)據(jù)源。前2個(gè)數(shù)據(jù)源都是為了生成第3個(gè)數(shù)據(jù)源,而且后續(xù)我們只用這最后一個(gè)路由數(shù)據(jù)源。

package com.hs.demo.config;
 
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
 
/**
 * 增加了 DataSourceConfig 這個(gè)配置文件之后,需要添加Hikari連接池,單數(shù)據(jù)源自動(dòng)裝載時(shí)不會(huì)出這 
 * 樣的問(wèn)題
 *
 * @Configuration 注解,表明這就是一個(gè)配置類,指示一個(gè)類聲明一個(gè)或者多個(gè)@Bean 聲明的方法并且由Spring容器統(tǒng)一管理,以便在運(yùn)行時(shí)為這些bean生成bean的定義和服務(wù)請(qǐng)求的類。
 */
@Configuration
public class DataSourceConfig {
 
    /**
     * 注入主庫(kù)數(shù)據(jù)源
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
 
        return DataSourceBuilder.create().build();
 
        //DataSourceProperties properties放在方法參數(shù)里
//        return DataSourceBuilder.create(properties.getClassLoader())
//                .type(HikariDataSource.class)
//                .driverClassName(properties.getDriverClassName())
//                .url(properties.getUrl())
//                .username(properties.getUsername())
//                .password(properties.getPassword())
//                .build();
    }
 
    /**
     * 注入從庫(kù)數(shù)據(jù)源
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
 
        return DataSourceBuilder.create().build();
    }
 
 
    /**
     * 配置選擇數(shù)據(jù)源
     * @param masterDataSource
     * @param slaveDataSource
     * @return DataSource
     */
    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource)
    {
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSource.put(DBTypeEnum.SLAVE, slaveDataSource);
 
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        //找不到用默認(rèn)數(shù)據(jù)源
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        //可選擇目標(biāo)數(shù)據(jù)源
        myRoutingDataSource.setTargetDataSources(targetDataSource);
 
        return myRoutingDataSource; 
    }
}

3.7、配置Mybatis指定數(shù)據(jù)源

修改SqlSessionFactory 和事務(wù)管理器

package com.hs.demo.config;
 
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
import javax.sql.DataSource;
 
/**
 * 配置Mybatis指定數(shù)據(jù)源:SqlSessionFactory和事務(wù)管理器
 */
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
 
    /**
     * 注入自己重寫的數(shù)據(jù)源
     */
    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;
 
    /**
     * 配置SqlSessionFactory
     * @return SqlSessionFactory
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception
    {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
 
        //ResourcePatternResolver(資源查找器)定義了getResources來(lái)查找資源
        //PathMatchingResourcePatternResolver提供了以classpath開(kāi)頭的通配符方式查詢,否則會(huì)調(diào)用ResourceLoader的getResource方法來(lái)查找
//        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocation));
 
        return sqlSessionFactoryBean.getObject();
    }
 
 
    /**
     * 事務(wù)管理器,不寫則事務(wù)不生效:事務(wù)需要知道當(dāng)前使用的是哪個(gè)數(shù)據(jù)源才能進(jìn)行事務(wù)處理
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
 
//    /**
//     * 當(dāng)自定義數(shù)據(jù)源,用戶必須覆蓋SqlSessionTemplate,開(kāi)啟BATCH處理模式
//     *
//     * @param sqlSessionFactory
//     * @return
//     */
//    @Bean
//    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
//        return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
//    }
 
}

3.8、AOP切面實(shí)現(xiàn)數(shù)據(jù)源切換

通過(guò)Aop的前置通知來(lái)設(shè)置要使用的路由key(數(shù)據(jù)源)

package com.hs.demo.config;
 
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
 
/**
 * 默認(rèn)情況下,所有的查詢都走從庫(kù),插入/修改/刪除走主庫(kù)。我們通過(guò)方法名來(lái)區(qū)分操作類型(CRUD)
 *
 * 切面不能建立在DAO層,事務(wù)是在service開(kāi)啟的,到dao層再切換數(shù)據(jù)源,那事務(wù)就廢了
 *
 */
@Aspect
@Component
public class DataSourceAop {
 
    /**
     * 第一個(gè)”*“符號(hào)	表示返回值的類型任意;
     * com.sample.service.impl	AOP所切的服務(wù)的包名,即,我們的業(yè)務(wù)部分
     * 包名后面的”..“	表示當(dāng)前包及子包
     * 第二個(gè)”*“	表示類名,*即所有類。此處可以自定義,下文有舉例
     * .*(..)	表示任何方法名,括號(hào)表示參數(shù),兩個(gè)點(diǎn)表示任何參數(shù)類型
     */
    @Pointcut("!@annotation(com.hs.demo.config.Master) " +
            "&& (execution(* com.hs.demo.service.*.select*(..)) " +
            "|| execution(* com.hs.demo.service..*.find*(..)))")
    public void readPointcut() {
 
    }
 
    @Pointcut("@annotation(com.hs.demo.config.Master) " +
            "|| execution(* com.hs.demo.service..*.save*(..)) " +
            "|| execution(* com.hs.demo.service..*.add*(..)) " +
            "|| execution(* com.hs.demo.service..*.insert*(..)) " +
            "|| execution(* com.hs.demo.service..*.update*(..)) " +
            "|| execution(* com.hs.demo.service..*.edit*(..)) " +
            "|| execution(* com.hs.demo..*.delete*(..)) " +
            "|| execution(* com.hs.demo..*.remove*(..))")
    public void writePointcut() {
 
    }
 
    @Before("readPointcut()")
    public void read(JoinPoint jp) {
 
        //獲取當(dāng)前的方法信息
        MethodSignature methodSignature = (MethodSignature) jp.getSignature();
        Method method = methodSignature.getMethod();
 
        //判斷方法上是否存在注解@Master
        boolean present = method.isAnnotationPresent(Master.class);
        if (!present)
        {
            //如果不存在,默認(rèn)走從庫(kù)讀
            DBContextHolder.slave();
        }
        else
        {
            //如果存在,走主庫(kù)讀
            DBContextHolder.master();
        }
 
    }
 
    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }
 
 
    /**
     * 另一種寫法:if...else...  判斷哪些需要讀從數(shù)據(jù)庫(kù),其余的走主數(shù)據(jù)庫(kù)
     */
//    @Before("execution(* com.cjs.example.service.impl.*.*(..))")
//    public void before(JoinPoint jp) {
//        String methodName = jp.getSignature().getName();
//
//        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
//            DBContextHolder.slave();
//        }else {
//            DBContextHolder.master();
//        }
//    }
 
}

3.9、如果有強(qiáng)制走主庫(kù)的操作,可以定義注解

package com.hs.demo.config;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
/**
 * 有時(shí)候主從延遲,需要強(qiáng)制讀主庫(kù)的注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Master
{
    //設(shè)置數(shù)據(jù)源類型
   //String value();
}

3.10、自行定義CRUD讀寫操作

(1)UserEntity

package com.hs.demo.entity;
 
import lombok.Data;
 
/**
 * @author heshi
 * @date 2021/10/20 15:14
 */
 
@Data
public class UserEntity {
 
    private Integer user_id;
    private String account;
    private String nickname;
    private String password;
    private String headimage_url;
    private String introduce;
}

(2)UserMapper

package com.hs.demo.mapper;
 
import com.hs.demo.entity.UserEntity;
import org.apache.ibatis.annotations.*;
 
import java.util.List;
 
/**
 * Spring通過(guò)@Mapper注解實(shí)現(xiàn)動(dòng)態(tài)代理,mybatis會(huì)自動(dòng)創(chuàng)建Dao接口的實(shí)現(xiàn)類代理對(duì)象注入IOC容器進(jìn)行管理,這樣就不用編寫Dao層的實(shí)現(xiàn)類
 *
 */
@Mapper
public interface UserMapper {
 
    @Select("SELECT * FROM user")  //使用@Select、@Insert等注解方式來(lái)實(shí)現(xiàn)對(duì)應(yīng)的持久化操作,使得我們可以不配置XML格式的Mapper文件
    List<UserEntity> findAll();
 
    @Insert("insert into user(account,nickname,password) values(#{account}, #{nickname}, #{password})")
    int insert(UserEntity user);
 
    @Update("UPDATE user SET account=#{account},nickname=#{nickname} WHERE id =#{id}")
    void update(UserEntity user);
 
    @Delete("DELETE FROM user WHERE id =#{id}")
    void delete(Long id);
}

(3)UserService(重要)

package com.hs.demo.service;
 
import com.hs.demo.entity.UserEntity;
import com.hs.demo.mapper.UserMapper;
import com.hs.demo.mysql.Master;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
 
/**
 * @author heshi
 * @date 2021/10/21 10:36
 */
 
@Service
public class UserService {
 
    @Autowired
    UserMapper userMapper;
 
//    @Master
    public List<UserEntity> findAll()
    {
        List<UserEntity> userEntities = userMapper.findAll();
        return userEntities;
    }
 
    public int insertUser(UserEntity user)
    {
        int i = userMapper.insert(user);
        return i;
    }
 
//    void update(UserEntity user);
//
//    void delete(Long id);
 
}

  (4)  UserController

package com.hs.demo.controller;
 
import com.hs.demo.entity.UserEntity;
import com.hs.demo.service.UserService;
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 UserController
{
    @Autowired
    UserService userService;
 
    @RequestMapping("/listUser")
    public List<UserEntity>  listUser()
    {
        List<UserEntity> users = userService.findAll();
        return users;
    }
 
    @RequestMapping("/insertUser")
    public void insertUser()
    {
       UserEntity userEntity = new UserEntity();
       userEntity.setAccount("22222");
       userEntity.setNickname("hshshs");
       userEntity.setPassword("123");
       userService.insertUser(userEntity);
    }
}

運(yùn)行結(jié)果如下圖所示

 

 總結(jié):通過(guò)AOP來(lái)確定所使用數(shù)據(jù)源類型,然后通過(guò)路由來(lái)進(jìn)行數(shù)據(jù)源選擇。

參考鏈接:

springboot實(shí)現(xiàn)讀寫分離(基于Mybatis,mysql)

springboot實(shí)現(xiàn)mysql的讀寫分離

SpringBoot AOP Mysql主從復(fù)制

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

相關(guān)文章

  • SpringBoot整合JPA方法及配置解析

    SpringBoot整合JPA方法及配置解析

    這篇文章主要介紹了SpringBoot整合JPA方法及配置過(guò)程,JPA是Java Persistence API的簡(jiǎn)稱,中文名Java持久層API,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05
  • Java中ShardingSphere 數(shù)據(jù)分片的實(shí)現(xiàn)

    Java中ShardingSphere 數(shù)據(jù)分片的實(shí)現(xiàn)

    其實(shí)很多人對(duì)分庫(kù)分表多少都有點(diǎn)恐懼,我們今天用ShardingSphere 給大家演示數(shù)據(jù)分片,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Java算法實(shí)現(xiàn)楊輝三角的講解

    Java算法實(shí)現(xiàn)楊輝三角的講解

    今天小編就為大家分享一篇關(guān)于Java算法實(shí)現(xiàn)楊輝三角的講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • Java調(diào)用Oracle存儲(chǔ)過(guò)程詳解

    Java調(diào)用Oracle存儲(chǔ)過(guò)程詳解

    這篇文章主要介紹了Java調(diào)用Oracle存儲(chǔ)過(guò)程詳解的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • java集合中的list詳解

    java集合中的list詳解

    這篇文章主要介紹了java集合中的list詳解,還是比較不錯(cuò)的,這里分享給大家,需要的朋友可以參考下。
    2017-11-11
  • Activiti開(kāi)發(fā)環(huán)境的配置

    Activiti開(kāi)發(fā)環(huán)境的配置

    本篇文章主要內(nèi)容介紹了Activiti開(kāi)發(fā)環(huán)境的配置,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • SpringBoot之QueryDsl嵌套子查詢問(wèn)題

    SpringBoot之QueryDsl嵌套子查詢問(wèn)題

    這篇文章主要介紹了SpringBoot之QueryDsl嵌套子查詢問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Java AQS的實(shí)現(xiàn)原理詳解

    Java AQS的實(shí)現(xiàn)原理詳解

    這篇文章主要借助了ReentrantLock來(lái)帶大家搞清楚AQS的實(shí)現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下
    2023-04-04
  • 詳解如何實(shí)現(xiàn)OpenAPI開(kāi)發(fā)動(dòng)態(tài)處理接口的返回?cái)?shù)據(jù)

    詳解如何實(shí)現(xiàn)OpenAPI開(kāi)發(fā)動(dòng)態(tài)處理接口的返回?cái)?shù)據(jù)

    這篇文章主要為大家介紹了OpenAPI開(kāi)發(fā)動(dòng)態(tài)處理接口的返回?cái)?shù)據(jù)如何實(shí)現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-04-04
  • spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題

    spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題

    這篇文章主要介紹了spring?controller層引用service報(bào)空指針異常nullpointExceptio問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02

最新評(píng)論