侧边栏壁纸
  • 累计撰写 59 篇文章
  • 累计创建 102 个标签
  • 累计收到 5 条评论

目 录CONTENT

文章目录

SpringBoot+MyBatisPlus配置多数据源读写分离

Sir丶雨轩
2019-10-21 / 0 评论 / 3 点赞 / 650 阅读 / 8999 字

首先呢,我们这里使用MySQL的数据库,可以简单配置一下主从备份来实现两个数据库的数据同步

参考文章

项目的配置目录大概是这样的作为参考

第一步、定义一个枚举类声明当前的数据源类型

package com.yuxuan66.ehi.workcloud.config.db;
/**
 * 数据源类型枚举
 * @author Sir丶雨轩
 * @since 1.0
 */
public enum DBTypeEnum {

    MASTER, SLAVE;
}

第二步、定义我们的数据源路由

package com.yuxuan66.ehi.workcloud.config.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;
/**
 * @author Sir丶雨轩
 * @since 1.0
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }}

第三步、配置所有的数据源,包含一个数据源的路由

package com.yuxuan66.ehi.workcloud.config.db;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
 * 数据源配置,用来实现读写分离
 *
 * @author Sir丶雨轩
 * @since 1.0
 */
@Configuration
public class DataSourceConfig {

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

    /**
     * 主数据库
     * @return DataSource
     */
    @Bean("masterDataSource")
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().type(type).build();
    }

    /**
     * 从数据库
     * @return DataSource
     */
    @Bean("slaveDataSource")
    @ConfigurationProperties("spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().type(type).build();
    }

    /**
     * 数据源路由
     * @param masterDataSource 主数据库
     * @param slaveDataSource 从数据库
     * @return DataSource
     */
    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;
    }}

第四步、数据源的上下文管理切换

package com.yuxuan66.ehi.workcloud.config.db;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @author Sir丶雨轩
 * @since 1.0
 */
@Slf4j
public class DBContextHolder {

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

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

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

    public static DBTypeEnum get() {
        return contextHolder.get();
    }

    public static void master() {
        set(DBTypeEnum.MASTER);
        if(log.isDebugEnabled()){
            log.debug("数据源切换至master");
        }
    }

    public static void slave() {
        set(DBTypeEnum.SLAVE);
        if(log.isDebugEnabled()){
            log.debug("数据源切换至slave");
        }
    }}

第五步、配置SqlSessionFactory

package com.yuxuan66.ehi.workcloud.config.db;
/**
 * @author Sir丶雨轩
 * @since 1.0
 */
@Configuration
@MapperScan("com.yuxuan66.ehi.workcloud.**.mapper*")
public class MybatisPlusConfig {
    /**
     * xml文件所在路径
     */
    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;
    /**
     * 别名扫描包名
     */
    @Value("${mybatis-plus.type-aliases-package}")
    private String typeAliasesPackage;
    /**
     * 是否显示banner
     */
    @Value("${mybatis-plus.global-config.banner}")
    private Boolean banner;
    /**
     * 是否开启转驼峰
     */
    @Value("${mybatis-plus.configuration.map-underscore-to-camel-case}")
    private Boolean mapUnderscoreToCamelCase;
    /**
     * 数据源路由
     */
    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;

    /**
     * 使用MyBatis Plus的sqlSessionFactory代替
     *
     * @return sqlSessionFactory
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
        MybatisConfiguration mybatisConfiguration = new MybatisConfiguration();
        mybatisConfiguration.setMapUnderscoreToCamelCase(mapUnderscoreToCamelCase);
        mybatisConfiguration.setObjectWrapperFactory(new MapWrapperFactory());
        sqlSessionFactoryBean.setConfiguration(mybatisConfiguration);
        GlobalConfig config = new GlobalConfig();
        config.setBanner(banner);
        sqlSessionFactoryBean.setGlobalConfig(config);
        sqlSessionFactoryBean.setPlugins(new Interceptor\[\]{new PaginationInterceptor()});
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 事务配置
     *
     * @return 事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager() {
        DataSourceTransactionManager tx = new DataSourceTransactionManager();
        tx.setDataSource(myRoutingDataSource);
        return tx;
    }}

第六步、使用Aop和注解来控制数据源的切换

package com.yuxuan66.ehi.workcloud.config.db;
/**
 * @author Sir丶雨轩
 * @since 1.0
 */
@Aspect
@Component
public class DataSourceAop {

    /**
     * 使用从库查询
     */
    @Pointcut("@annotation(com.yuxuan66.ehi.workcloud.config.db.anno.Slave) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.select*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.list*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.query*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.find*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.get*(..))")
    public void readPointcut() {

    }

    /**
     * 使用主库插入更新
     */
    @Pointcut("@annotation(com.yuxuan66.ehi.workcloud.config.db.anno.Master) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.login(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.insert*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.add*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.update*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.edit*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.delete*(..)) " +
            "|| execution(* com.yuxuan66.ehi.workcloud..service..*.remove*(..))")
    public void writePointcut() {

    }

    @Before("readPointcut()")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }}

第七步、定义注解

package com.yuxuan66.ehi.workcloud.config.db.anno;
/**
 * 强制使用读数据库
 * @author Sir丶雨轩
 * @since 1.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Master {}

package com.yuxuan66.ehi.workcloud.config.db.anno;
/**
 * 强制使用从数据库
 *
 * @author Sir丶雨轩
 * @since 1.0
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Slave {}

参考文章 https://www.cnblogs.com/cjsblog/p/9712457.html

3

评论区