Spring Boot 读写分离

Spring Boot 读写分离简单实现:

一、数据源配置
此处只简单配置两个数据源,master(主-写)和slave(从-读)
@PropertySource读取指定目录下配置文件

import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置类
 * 此处默认两个数据库,master(写)、slave(读)
 */
@Configuration
@PropertySource("classpath:config/${spring.profiles.active}/db.properties")
public class DataSourceConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceConfig.class);

    @Primary
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix="spring.datasource.master")
    public DataSource masterDataSource() {
        LOGGER.debug("[DataSourceConfig] Init DataSource Master");
        return DataSourceBuilder.create().build();
    }


    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix="spring.datasource.slave")
    public DataSource slaveDataSource() {
    	LOGGER.debug("[DataSourceConfig] Init DataSource Slave");
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceContextHolder.DbType.MASTER, masterDataSource);
        targetDataSources.put(DataSourceContextHolder.DbType.SLAVE, slaveDataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }
}

db.properties配置文件

spring.datasource.master.jdbc-url = jdbc:mysql://127.0.0.1:3306/db1
spring.datasource.master.username = admin
spring.datasource.master.password = 123456
spring.datasource.master.driverClassName = com.mysql.jdbc.Driver

spring.datasource.slave.jdbc-url = jdbc:mysql://127.0.0.1:3306/db2
spring.datasource.slave.username = admin
spring.datasource.slave.password = 123456
spring.datasource.slave.driverClassName = com.mysql.jdbc.Driver

二、线程上下文
用于保存当前线程本地变量,存放数据源类型

/**
 * 线程上下文 <br>
 * 用于保存线程本地变量DbType(用于切换数据源)
 * 如果是异步线程操作,需要用InheritableThreadLocal (需要保存父线程的变量)
 *
 * @see [相关类/方法](可选)
 * @since [产品/模块版本](可选)
 */
public class DataSourceContextHolder {

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

    public static ThreadLocal<DbType> getLocal() {
        return contextHolder;
    }

    private DataSourceContextHolder () {

    }

    public static DbType getDbType() {
        return contextHolder.get() == null ? DbType.MASTER : contextHolder.get();
    }

    public static void setDbType(DbType dbType) {
        contextHolder.set(dbType);
    }

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

    public enum DbType {
        MASTER,
        SLAVE
    }
}

三、继承AbstractRoutingDataSource ,实现动态数据源配置

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

/**
 * 多数据源切换 <br>
 *
 * @see [相关类/方法](可选)
 * @since [产品/模块版本](可选)
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {

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

四、使用AOP实现数据源的切换
此处使用aop,对dao层的方法进行切面,根据方法名query,update等进行读写数据源的切换

import com.study.demo.config.DataSourceContextHolder;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 数据源切面,用于在执行sql前进行读写数据源切换 <br>
 * 〈功能详细描述〉
 *
 * @see [相关类/方法](可选)
 * @since [产品/模块版本](可选)
 */
@Aspect
@Order(-1)  // 保证该AOP在@Transactional之前执行
@Component
public class DataSourceAop {

    private static Logger logger = LoggerFactory.getLogger(DataSourceAop.class);

    @Pointcut("execution(* com.study.demo.dao..*.*(..))")
    public void cutPoint() {}

    @Before("execution(* com.study.demo.dao..*.query*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.setDbType(DataSourceContextHolder.DbType.MASTER);
        logger.debug("[DataSourceConfig] DataSource Covert To Read");
    }

    @Before("execution(* com.study.demo.dao..*.insert*(..))" +
         "|| execution(* com.study.demo.dao..*.update*(..))" +
         "|| execution(* com.study.demo.dao..*.delete*(..))")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.setDbType(DataSourceContextHolder.DbType.SLAVE);
        logger.debug("[DataSourceConfig] DataSource Covert To Write");
    }

    // ☆☆☆☆☆ 也可以获取参数中路由字段进行切换数据源 ☆☆☆☆☆

    @Before(value="cutPoint()")
    public void setDynamicDataSource(JoinPoint point){
        logger.debug("[DataSourceConfig] rout...");
    }
}

五、Mybatis配置

import com.study.demo.interceptor.DynamicSQLInterceptor;
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.transaction.annotation.EnableTransactionManagement;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * MyBatis 配置类 <br>
 *
 * 为动态数据源绑定SqlSessionFactory
 *
 * @see [相关类/方法](可选)
 * @since [产品/模块版本](可选)
 */
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:config/sqlMap/*.xml"));
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
        DynamicSQLInterceptor interceptor = new DynamicSQLInterceptor();
        sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
        return sqlSessionFactory;
    }
}

对于分表操作,可以简单对Mybatis进行拦截,根据路由字段简单实现SQL的修改(表号),详细在下一篇进行简单demo示例。

发布了40 篇原创文章 · 获赞 31 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/weixin_38422258/article/details/94588548