springboot2.0+mybatis实现读写分离

一、application.yml配置文件

server:
  port: 8080
  servlet:
    context-path: /cm
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      master:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: username
        password: password
        url: jdbc:mysql://xxxxx:xxxx/test?autoReconnect=true&useCompression=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT
        initial-size: 1
        min-idle: 1
        max-active: 50
        max-wait: 60000
      slave:
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: xxxx
        password: xxxxxx
        url: jdbc:mysql://xxxx:xxxx/test?autoReconnect=true&useCompression=true&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT
        initial-size: 1
        min-idle: 1
        max-active: 50
        max-wait: 60000
mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml

 二、编写配置类DataSourceConfig,配置多个数据源

package com.cm.server.busi.config;

import com.cm.server.busi.enums.DataSourceTypeEnum;
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;

/**
 * DataSourceConfig
 *
 * @author admin
 * @date 2021-10-27 17:19
 */
@Configuration
public class DataSourceConfig {

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

    @Bean(name = "masterDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")
    public DataSource slaveDataSource(){
        return DataSourceBuilder.create().type(dataSourceType).build();
    }

    /**
     *
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Bean
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slaveDataSource") DataSource slaveDataSource){
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceTypeEnum.master, masterDataSource);
        targetDataSources.put(DataSourceTypeEnum.slave, slaveDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }
}

三、mybatis配置

package com.cm.server.busi.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
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;

/**
 * MybatisConfig
 *
 * @author admin
 * @date 2021-10-27 17:26
 */
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
@AutoConfigureAfter({ DataSourceConfig.class })
public class MybatisConfig {

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

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(routingDataSource);
        // 下面的不可少,这个有了,就不用配置文件里的
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }


    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(routingDataSource);
    }
}

四、编写AbstractRoutingDataSource的实现类,并实现determineCurrentLookupKey方法,达到动态切换数据源的效果

package com.cm.server.busi.config;

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

/**
 * DynamicDataSource
 *
 * @author admin
 * @date 2021-10-27 17:18
 */
public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * 利用DatabaseContextHolder 获取当前线程的 datasourcetype
     * 动态数据源(需要继承AbstractRoutingDataSource)
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDataBaseType();
    }
}

五、通过ThreadLocal将数据源设置到每个线程上下文中

package com.cm.server.busi.config;

import com.cm.server.busi.enums.DataSourceTypeEnum;

/**
 * DatabaseContextHolder
 *
 * @author admin
 * @date 2021-10-27 17:16
 */
public class DatabaseContextHolder {

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

    /**
     * 设置数据源
     *
     * @param dataBaseType
     */
    public static void setDataBaseType(DataSourceTypeEnum dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }
        contextHolder.set(dataBaseType);
    }

    /**
     * 取得当前数据源
     *
     * @return
     */
    public static DataSourceTypeEnum getDataBaseType() {
        return contextHolder.get() == null ? DataSourceTypeEnum.master : (DataSourceTypeEnum) contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDataBaseType() {
        contextHolder.remove();
    }
}

六、通过自动义注解,实现AOP拦截,达到切换数据源的目的。@EnableAspectJAutoProxy(exposeProxy=true, proxyTargetClass=true)

SpringBoot中,默认使用的就是CGLIB方式来创建代理。在它的配置文件中,spring.aop.proxy-target-class默认是true ,即proxyTargetClass=true,如果改为false则使用JDK动态代理机制(代理所有实现了的接口)。

TIPS:spring boot 2.0 以后默认使用Cglib 动态代理,之前版本有接口就用 JDK动态代理,没有接口就用 Cglib 动态代理,spring默认使用JDK动态代理,没有接口就报错。

1、Jdk动态代理:利用拦截器(必须实现InvocationHandler接口)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理

2、 Cglib动态代理:利用ASM框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来进行代理

package com.cm.server.busi.annotation;

import com.cm.server.busi.config.DatabaseContextHolder;
import com.cm.server.busi.enums.DataSourceTypeEnum;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * DataSourceAop
 *
 * @author admin
 * @date 2021-10-27 18:05
 */
@Aspect
@Component
// @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
public class DataSourceAop {

    @Pointcut("@annotation(com.cm.server.busi.annotation.ReadDataSource) && execution(* com.cm.server.busi.service.impl..*.*(..))")
    public void readPointcut(){}
    @Pointcut("@annotation(com.cm.server.busi.annotation.MasterDataSource) && execution(* com.cm.server.busi.service.impl..*.*(..))")
    public void writePointcut(){}

    @Before("readPointcut()")
    public void readBefore(JoinPoint joinPoint) {
        DatabaseContextHolder.setDataBaseType(DataSourceTypeEnum.slave);
        System.out.println("USE DATASOURCE SLAVE");
    }

    @Before("writePointcut()")
    public void writeBefore(JoinPoint joinPoint) {
        DatabaseContextHolder.setDataBaseType(DataSourceTypeEnum.master);
        System.out.println("USE DATASOURCE MASTER");
    }

    @After("readPointcut() || writePointcut()")
    public void after() {
        DatabaseContextHolder.clearDataBaseType();
    }

}

访问只读库注解

package com.cm.server.busi.annotation;

import java.lang.annotation.*;

/**
 *
 * @author 
 * @date 2021/10/27
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ReadDataSource {
}

访问读写库注解

package com.cm.server.busi.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * MasterDataSource
 *
 * @author admin
 * @date 2021-10-27 18:03
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MasterDataSource {
}

附上dataType枚举类

package com.cm.server.busi.enums;

/**
 *
 * @author
 * @date 2021/10/27
 */
public enum DataSourceTypeEnum {

    master,slave;
}

7、最后在业务层代码加上相应的注解即可

package com.cm.server.busi.service.impl;

import com.cm.server.busi.annotation.MasterDataSource;
import com.cm.server.busi.annotation.ReadDataSource;
import com.cm.server.busi.mapper.CmServerBusiMapper;
import com.cm.server.busi.model.DataVo;
import com.cm.server.busi.service.ICmServerBusiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * CmServerBusiServiceImpl
 *
 * @author admin
 * @date 2021-10-27 10:48
 */
@Service
public class CmServerBusiServiceImpl implements ICmServerBusiService {
    @Autowired
    private CmServerBusiMapper cmServerBusiMapper;

    @Override
    @ReadDataSource
    public Map<String, Object> selectDataInfo() {
        return cmServerBusiMapper.selectDataInfo();
    }

    @Override
    @MasterDataSource
    public void updateDataInfo(DataVo dataVo) {
        cmServerBusiMapper.updateDataInfo(dataVo);
    }
}

八、测试

 参考:SpringBoot+MyBatis+MySQL读写分离 - 废物大师兄 - 博客园

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值