spring boot结合Mybatis基于mysql 实现读写分离和主从同步(一)

一:读写分离

1.简介

在早期项目开发过程中,我们都是把数据存储在单个数据库中,这样无论是对数据库的读还是写都是对单个数据库此操作。这样带来的问题是巨大的:

  • 单个数据库服务器挂了,数据库里面所有的数据都挂了

  • 所有的读写请求都是对单个数据库操作,数据库服务器压力巨大

基于上述原因,我们就需要将对数据库服务器的读写操作分离,也就是读写分离。具体原理图如下:

  • 主数据库与多个从数据库实现了主从复制

  • 当应用发起对数据库的写操作时,那么就去操作主数据库

  • 当应用发起对数据库的读操作时,那么通过负载均衡算法去访问从数据库。

  • 系统一般来说时“读多写少”,因此这样在一定程度上减轻了数据库的压力。

 

2.实现方式

实现读写分离大致有两种方式:

  • 利用中间件进行读写分离

    例如:Mycat,Oneproxy等等。

    这些中间基本上都可以实现数据库的读写分离,分库分表等其他诸多功能,但是如果只是想实现读写分离,中间件反而显得有点臃肿

    优缺点:

    • 代码层面不需要任何改动,该怎么去写就怎么写

    • 应用不再直接操作数据库,直接操作中间件,通过中间件去操作数据库

    • 访问运维人员进行维护。

    • 配置比较繁琐,同时如果修改了数据库,同时也要修改中间件

  • 在应用层面利用aop去实现读写分离(重点介绍)

    所谓的读写分离,就是让不同的请求去操作不同的数据库,那么其实就可以在访问数据库之前,先判断该请求是什么请求,

    读请求就让它访问从数据库,写请求就访问主数据库。

    优缺点:

    • 省略了中间件配置步骤,简化开发时间

    • 思想间件,实现起来比较容易

    • 在代码层面修改,运维人员不好修改

    • 如果增加数据库,需要修改代码

3.原理

在这里我们重点去掌握怎么在代码层面去实现读写分离。

其实原理在上面的介绍中已经提到过了,总而言之就是动态切换数据源。具体原理如下:

  • 在调用业务层方法之前先判断该方法对数据库的操作

    • 如果是写操作那么将数据源切换成主数据库

    • 如果是读操作就将数据源切换到从数据库。

这样我们就实现了读写分离。具体原理图如下:

 

4.实现步骤

  • 准备工作

    • 搭建mysql的主从复制,这里我们就搭建一主一从,在第二大章会介绍。

    • 熟悉mybatis通用mapper

    • 在主数据库创建数据库以及表

pom.xml:

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
   <!--
        SpringBoot 版本
   -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.15.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.readwite</groupId>
    <artifactId>application</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>application</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--
            mysql 驱动包
        -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--
            lombok 依赖包
        -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!---
          单元测试
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--
          aop依赖
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
      

        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.version}</version>
        </dependency>

        <!-- 阿里巴巴druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!--
            日志依赖
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
  • 搭建环境
    • 在application.yml增加以下内容
spring:
  mvc:
    view:
      prefix: /
      suffix: .html
  servlet: #配置文件大小
    multipart:
      max-file-size: 20Mb
      max-request-size: 20Mb

#============================#
#==== database settings =====#
#============================#
  resources:
    static-locations: classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,classpath:/itstyle/, file:/
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
        #配置主数据库负责写
        master:
          #数据库连接结合实际情况
          url: jdbc:mysql://101.200.***.***:3306/caiji?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
          username: root
          password: ****
          driver-class-name: com.mysql.jdbc.Driver
          connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
          initialSize: 5
          minIdle: 5
          maxActive: 20
          maxWait: 60000
          timeBetweenEvictionRunsMillis: 60000
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          poolPreparedStatements: true
          maxPoolPreparedStatementsPerConnectionSize: 20
          useGlobalDataSourceStat: true
          #
        slave:
          url: jdbc:mysql://47.94.***.***:3306/caiji?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
          username: root
          password: ***
          driver-class-name: com.mysql.jdbc.Driver
          connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
          initialSize: 5
          minIdle: 5
          maxActive: 20
          maxWait: 60000
          timeBetweenEvictionRunsMillis: 60000
          minEvictableIdleTimeMillis: 300000
          validationQuery: SELECT 1 FROM DUAL
          testWhileIdle: true
          testOnBorrow: false
          testOnReturn: false
          poolPreparedStatements: true
          maxPoolPreparedStatementsPerConnectionSize: 20
          useGlobalDataSourceStat: true
  • 建立自定义注解@Read,@Write来用于面向切面的形式来动态切换数据源
/**
* @author zm
* @version 1.0.0
* @date 2020/7/28 21:51
* @desription 操作数据库只读注解
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Read {
}
/**
* @author zm
* @version 1.0.0
* @date 2020/7/28 21:52
* @desription 操作数据库只写注解
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface Write {
}

  • 因为我们只有一个主库,一个从库,因此我们定义两个枚举对象来表示数据库类型

    • 创建枚举类,定义MASTER,SLAVE两个枚举对象

/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 17:04
* @desription
*/
public enum DBTypeEnum {
    /**
     * 表示主数据库
     */
    MASTER,


    /**
     * 表示从数据库
     */
    SLAVE;


}
  • 创建一个动态切换数据源的工具类
package com.whut.ns.caiji.config.db;


import com.whut.my.caiji.ns.domain.db.DBTypeEnum;


/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 17:06
* @desription 动态切换数据源的工具类
*/
public class DynamicSwitchDBTypeUtil {
    /**
     * 用来存储代表数据源的对象
     *  如果是里面存储是MASTER,代表当前线程正在使用主数据库
     *  如果是里面存储的是SLAVE,代表当前线程正在使用从数据库
     */
    public static final ThreadLocal<DBTypeEnum>  CONTEXT_HAND = new ThreadLocal<>();




    /**
     * 切换当前线程要使用的数据源
     * @param dbTypeEnum
     */
    public static void set(DBTypeEnum dbTypeEnum) {
        CONTEXT_HAND.set(dbTypeEnum);
        System.out.println("切换数据源:" + dbTypeEnum);
    }


    /**
     * 切换到主数据库
     */
    public static void master() {
        set(DBTypeEnum.MASTER);
    }


    /**
     * 切换到从数据库
     */
    public static void slave() {
        /*
            目前我们只有一个从数据库,可以直接设置
            但是如果我们拥有多个从数据库那么就需要
            考虑怎么使用什么样的算法去负载均衡从数据库
         */
        set(DBTypeEnum.SLAVE);
    }


    /**
     * 移除当前线程使用的数据源
     */
    public static void remove() {
        CONTEXT_HAND.remove();
    }


    /**
     * 获取当前线程使用的枚举值
     * @return
     */
    public static DBTypeEnum get() {


        return CONTEXT_HAND.get();
    }


}

 

  • 编写AbstractRoutingDataSource的实现类
    • 在SpringBoot中提供了AbstractRoutingDataSource,用户可以根据自己定义的规则去选择当前要使用的数据源,我们利用这个特性,在调用业务层方法之前去扫描注解,如果方法上是read注解我们就切换到从数据库,否则切换到主数据库。
    • 实现动态的数据源,是由该里面的抽象方法determineCurrentLookupKey决定,具体源码如下图所示:

 

 部分解释如下:

package org.springframework.jdbc.datasource.lookup;


import java.sql.Connection;import java.sql.SQLException;import java.util.HashMap;import java.util.Map;


import javax.sql.DataSource;


import org.springframework.beans.factory.InitializingBean;import org.springframework.jdbc.datasource.AbstractDataSource;import org.springframework.lang.Nullable;import org.springframework.util.Assert;


public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    /**
        用来存储数据源
        map的key-value解释如下:
        key: 数据源的key值
        value: 表示数据源
    */
    @Nullable
    private Map<Object, Object> targetDataSources;


    /**
        默认的数据源
    */
    @Nullable
    private Object defaultTargetDataSource;


    private boolean lenientFallback = true;


    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();


    @Nullable
    private Map<Object, DataSource> resolvedDataSources;


    @Nullable
    private DataSource resolvedDefaultDataSource;




    /**
     * 设置数据源,具体使用哪一个数据源由determineCurrentLookupKey()方法返回
     * 的key决定
     */
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }


    /**
     *设置默认的数据源
     */
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        this.defaultTargetDataSource = defaultTargetDataSource;
    }


    
    /**
     * 决定使用数据源的方法
     * 从源码可知:
            1.调用 determineCurrentLookupKey 获取key值
            2.拿到key值后再从map里面获取数据源,然后返回
     */
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        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;
    }


    /**
     * 抽象方法,返回数据源的key值,由开发者自己去实现
     * 要实现切花数据源就是重写此方法
     */
    @Nullable
    protected abstract Object determineCurrentLookupKey();


}
  • 创建该类的实现类,并实现determineCurrentLookupKey方法
package com.whut.ns.caiji.config.db;


import com.whut.my.caiji.ns.domain.db.DBTypeEnum;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 17:13
* @desription
*/


/**
* 决定返回哪个数据源的key
*/
public class RouttingDataSource extends AbstractRoutingDataSource   {


    private DataSource masterDataSource;
    private DataSource slaveDataSource;




    public RouttingDataSource() {
    }


    public RouttingDataSource(DataSource masterDataSource,DataSource slaveDataSource) {
        this.masterDataSource = masterDataSource;
        this.slaveDataSource = slaveDataSource;
    }


    @Override
    protected Object determineCurrentLookupKey() {


        /**
         * 返回当前线程正在使用的代表数据库的枚举对象
         */


        return   DynamicSwitchDBTypeUtil.get();
    }


    @Override
    public void afterPropertiesSet() {
        super.afterPropertiesSet();


        //todo 做读数据库的负载均衡
    }


    /**
     * 连接失败的时候的处理方法,再次切换数据源
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {
        try {
            return super.getConnection();
        } catch (SQLException e) {
            if (DynamicSwitchDBTypeUtil.get() == DBTypeEnum.SLAVE) {
                //从数据库连接失败
                return masterDataSource.getConnection();
            }else if(
                    DynamicSwitchDBTypeUtil.get() == DBTypeEnum.MASTER
            ){
                return slaveDataSource.getConnection();
            }
            throw e;
        }
    }
}
  • 配置数据源
package com.whut.ns.caiji.config.db;



import com.whut.my.caiji.ns.domain.db.DBTypeEnum;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 17:19
* @desription
*/
@Configuration
public class DataSourceConfig {


    @Value("${spring.datasource.type}")
    private Class<? extends DataSource> dataSourceType;


    /**
     * 将创建的master数据源存入Spring容器中,并且注入内容
     * key值为方法名
     * @return master数据源
     */
    @Bean(name = "masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource masterDataSource() {


        return DataSourceBuilder
                .create()
                .type(dataSourceType)
                .build();
    }


    /**
     * 将创建的slave数据源存入Spring容器中,并且注入内容
     * key值为方法名
     * @return slave数据源
     */
    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder
                .create()
                .type(dataSourceType)
                .build();
    }


    /**
     * 决定最终要使用的数据源
     * @return
     */
    @Bean
    public DataSource targetDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource
    ) {


        // 用来存放主数据源和从数据源
        Map<Object, Object> targetDataSource = new HashMap<>();


        // 往map中添加主数据源
        targetDataSource.put(DBTypeEnum.MASTER,masterDataSource);


        // 往map中添加从数据源
        targetDataSource.put(DBTypeEnum.SLAVE,slaveDataSource);


        // 创建 routtingDataSource 用来实现动态切换
        RouttingDataSource routtingDataSource = new RouttingDataSource();




        // 设置默认的数据源
        routtingDataSource.setDefaultTargetDataSource(masterDataSource);


        // 绑定所有的数据源
        routtingDataSource.setTargetDataSources(targetDataSource);



        return routtingDataSource;
    }


}

  • 配置Mybatis

        因为我们已经有了多个数据源,因此我们就需要去配置mybatis的SqlSessionFactory.

        那么问题来了,为什么之前我们在SpringBoot整合mybatis的时候不需要配置,这是因为之前整合的时候只有一个数据源,SpringBoot底部已经帮我们做好了了封装,所以我们不要配置。

        而现在有多个数据源我们就需要手动配置了。新建一个配置类,如下:

package com.whut.ns.caiji.config.db;


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.core.io.support.PathMatchingResourcePatternResolver;
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;


/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 17:43
* @desription
*/
@Configuration
@EnableTransactionManagement
public class MybatisConfig {




    /**
     * 注入先前配置的数据源
     */
    @Resource(name = "targetDataSource")
    private DataSource dataSource;


    /**
     * 配置SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        // 创建SqlSessionFactoryBean对象
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);


        factoryBean.setTypeAliasesPackage("com.whut.ns.caiji.dao");


        //配置多数据源mapper文件存放路径
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/*Mapper.xml"));


        return factoryBean.getObject();
    }




    /**
     * 配置事务管理
     * @return
     * @throws Exception
     */
    @Bean
    public PlatformTransactionManager transactionManager(){
        return new DataSourceTransactionManager(dataSource);
    }



}
  • 配置AOP

        经过上面的配置我们基本上配置好了读写分离大部分解释,但是现在存在的问题是

        程序如何得知哪些方法上加了read或者write注解。即使知道了哪些方法上加了注解

        难道我们需要每一个方法都去切换数据源吗,那样效率太低了。

        我们可以利用aop思想,配置切入点和通知,在调用每个方法之前去判断,然后切换。就跟提交事务原理一样。

        配置如下:

        新建一个AOP配置类:

package com.whut.ns.caiji.aop;


import com.whut.ns.caiji.config.db.DynamicSwitchDBTypeUtil;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;


/**
* @author Administrator
* @version 1.0.0
* @date 2020/7/28 21:53
* @desription
*/
@Aspect
@Component
public class DataSourceAOP {




    /**
     * 只要加了@Read注解的方法就是一个切入点
     */
    @Pointcut("@annotation(com.whut.ns.caiji.annotation.Read)")
    public void readPointcut() {}


    /**
     * 只要加了@Write注解的方法就是一个切入点
     */
    @Pointcut("@annotation(com.whut.ns.caiji.annotation.Write)")
    public void writePointcut() {}


    /**
     * 配置前置通知,如果是readPoint就切换数据源为从数据库
     */
    @Before("readPointcut()")
    public void readAdvise() {
        DynamicSwitchDBTypeUtil.slave();
    }


    /**
     * 配置前置通知,如果是writePoint就切换数据源为主数据库
     */
    @Before("writePointcut()")
    public void writeAdvise() {


        DynamicSwitchDBTypeUtil.master();
    }


}

  • 使用
@Read
public String preDeleteOperationLogs(List<Long> operationIds){
    return null;
}


@Override
@Write
public boolean deleteOperationLogs(List<Long> operationIds) {


    int flag = dao.deleteOperationLogs(operationIds);


    return flag > 0;
}

 

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Cloud可以通过使用动态数据源实现数据库读写分离,具体步骤如下: 1. 引入相关依赖 在Spring Boot项目的pom.xml文件中引入以下依赖: ``` <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- Druid 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.21</version> </dependency> <!-- HikariCP 数据库连接池 --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency> <!-- Spring Data JPA --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> ``` 2. 配置数据源 在application.properties或application.yml文件中配置数据源,例如: ``` # 主数据源 spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/masterdb?useUnicode=true&characterEncoding=utf-8 spring.datasource.master.username=root spring.datasource.master.password=123456 spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver # 从数据源 spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/slavedb?useUnicode=true&characterEncoding=utf-8 spring.datasource.slave.username=root spring.datasource.slave.password=123456 spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver ``` 3. 配置动态数据源 创建动态数据源配置类,例如: ``` @Configuration public class DynamicDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Bean public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.MASTER, masterDataSource); targetDataSources.put(DataSourceType.SLAVE, slaveDataSource); return new DynamicDataSource(masterDataSource, targetDataSources); } } ``` 其中,masterDataSource()和slaveDataSource()方法分别返回主数据源和从数据源,而dynamicDataSource()方法则创建一个动态数据源,该数据源包含主数据源和从数据源,且可以根据具体的业务需求动态切换数据源。 4. 创建数据源切换类 创建数据源切换类,例如: ``` public class DataSourceContextHolder { private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>(); public static void setDataSourceType(DataSourceType dataSourceType) { contextHolder.set(dataSourceType); } public static DataSourceType getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } } ``` 该类使用ThreadLocal存储数据源类型,setDataSourceType()方法设置当前线程使用的数据源类型,getDataSourceType()方法获取当前线程使用的数据源类型,clearDataSourceType()方法清空当前线程使用的数据源类型。 5. 创建数据源切面类 创建数据源切面类,例如: ``` @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.demo.annotation.DataSource)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); DataSource dataSource = method.getAnnotation(DataSource.class); if (dataSource == null) { DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER); } else { DataSourceContextHolder.setDataSourceType(dataSource.value()); } try { return point.proceed(); } finally { DataSourceContextHolder.clearDataSourceType(); } } } ``` 该类使用@Aspect注解声明为切面类,使用@Pointcut注解定义切点,使用@Around注解定义环绕通知,在方法执行前根据注解中指定的数据源类型切换数据源,在方法执行后清空数据源类型。 6. 根据具体业务需求使用动态数据源 在需要使用动态数据源的地方,使用@DataSource注解指定数据源类型,例如: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @DataSource(DataSourceType.MASTER) public int addUser(User user) { return userMapper.addUser(user); } @Override @DataSource(DataSourceType.SLAVE) public User getUserById(int id) { return userMapper.getUserById(id); } } ``` 以上就是使用Spring Cloud实现数据库读写分离的步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值