spring boot 实现多数据源读写分离

多数据源之间的关系

在这里插入图片描述

修改配置文件

datasource:
       slave:
          jdbc-url: jdbc:mysql://${S_MYSQL_HOST}:${MYSQL_PORT:3306}/database?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
          username: root
          password: W212T!@#123qaz
          driver-class-name: com.mysql.cj.jdbc.Driver
       master:
          jdbc-url: jdbc:mysql://${M_MYSQL_HOST}:${MYSQL_PORT:3306}/database?useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT%2B8
          username: root
          password: 121213456
          driver-class-name: com.mysql.cj.jdbc.Driver

自定义DataBase相关类

DataSourceType 数据源枚举类型

public enum DataSourceType {
    MASTER,
    SLAVE
}

DataSourceContextHolder

import java.util.concurrent.atomic.AtomicInteger;

public class DataSourceContextHolder {

    private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();

    private static final AtomicInteger counter = new AtomicInteger(-1);

    public static void setDataSourceType(DataSourceType dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static DataSourceType getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    public static boolean isMaster() {
        return DataSourceType.MASTER.equals(getDataSourceType());
    }

    public static boolean isSlave() {
        return DataSourceType.SLAVE.equals(getDataSourceType());
    }

    public static DataSourceType randomDataSource() {
        int index = counter.getAndIncrement() % DataSourceType.values().length;
        if (counter.get() > DataSourceType.values().length) {
            counter.set(0);
        }
        return DataSourceType.values()[index];
    }
}

RoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class RoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSourceType();
    }
}

DataSourceConfig


import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

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

@Configuration
@ComponentScan("#######需要的这几个bean的包路径########")
@EnableAspectJAutoProxy
public class DataSourceConfig {
    @Primary
    @Bean("masterDataSource")
    @RefreshScope
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource( ) {
        return DataSourceBuilder.create().build();
    }
    @Bean("slaveDataSource")
    @RefreshScope
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "routingDataSource")
    public DataSource routingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER, masterDataSource());
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());

        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(masterDataSource());
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        return new LazyConnectionDataSourceProxy(routingDataSource());
    }
}

DataSourceAspect

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Order(-1)
@Slf4j
public class DataSourceAspect {

    @Before("execution(* com.app.service.*.add*(..)) " +
            "||execution(* com.app.service.*.*insert*(..))" +
            "||execution(* com.app.service.*.*edit*(..))" +
            "||execution(* com.app.service.*.*update*(..)) " +
            "||execution(* com.app.service.*.*save*(..)) " +
            "||execution(* com.app.pc.service.*.*del*(..)) " 
    )
    public void masterMethods() {
        log.error("----------------switchToMasterDataSource---------------");
        DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
    }

    @Before("execution(* com.app.service.*.get*(..)) " +
            "||execution(* com.app.service.*.*select*(..)) " +
            "|| execution(* com.app.service.*.*find*(..)) " +
            "|| execution(* com.app.service.*.*List*(..)) " )
    public void slaveMethods() {
        log.error("----------------switchToSlaveDataSource---------------");
        DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
    }
    
    @After("execution(* com.app.service.*.*(..)) ")
    public void clearDataSource(JoinPoint joinPoint) {
        log.error("----------------clearDataSource---------------");
        DataSourceContextHolder.clearDataSourceType();
    }
}

自定义事务管理TransactionManager

import org.springframework.beans.factory.annotation.Qualifier;
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.sql.DataSource;

@Configuration
@EnableTransactionManagement(order = 1)
public class TransactionManagerConfig {

    @Bean
    public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource  dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

MyBatisConfig 自定义SqlSessionFactory,否则切换数据源只会成为切换数据源枚举字符串【大坑,目前没查到有人说这一点】

这个分两种情况

  1. mybatis

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
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 javax.sql.DataSource;

@Configuration
public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean  sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        //指定xml文件路径
        sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml"));
        return sessionFactoryBean;
    }
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

  1. mybatis-plus
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class MyBatisConfig {

    @Bean
    public MybatisSqlSessionFactoryBean sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean  sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        // 设置其他属性...
        return sessionFactoryBean;
    }
}

亲测有效

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值