SpringBoot简单多模块框架搭建(2)---Durid数据源读写分离

  • SpringBoot简单多模块框架搭建(1)---先启动再说,我们搭建数据源,然后实现读写分离

  • 大概就是在yml文件中配置durid,自定义注解,自定义数据源(配置durid),最后自定义个sqlSessionFactory(Mybatis)来使用数据源

  • 先来一张项目结构图

 

  • 先搞durid数据源,在base模块下的pom.xml中引入依赖(看上篇base模块的pom.xml),然后在resources的application.yml中添加durid数据源,代码如下:

server:
  context-path: /boot
  port: 8080
  session:
    timeout: 300


spring:
  profiles:
    active: dev

---
#使用---分隔,下面的配置与在application-dev.yml配置一样,没区别,只不过一个写在同个文件里,一个写在不同的文件里
spring:
  profiles: dev
  #数据源配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    write:
      # driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root123
    read:
      # driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root123

#设置日志级别,后面用包名(package)确定生效的区域,默认是INFO
logging:
  level.org.meichao: DEBUG
  • 最后我们写一个durid配置类,来设置一些基础属性

package org.meichao.config;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DruidConfig {

    /**
     * 注册一个StatViewServlet
     * @return
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet(){
        ServletRegistrationBean srb = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
        //添加初始化参数
        //白名单:没有配置或者为空,则允许访问
        srb.addInitParameter("allow","127.0.0.1");
        //黑名单:(共同存在时,deny优先allow)提示:Sorry,you are not permitted to view this page.
        //srb.addInitParameter("deny","");
        srb.addInitParameter("loginUsername","admin");
        srb.addInitParameter("loginPassword","admin");
        //是否能够重置数据源,禁用HTML页面上的"Reset ALL"功能
        srb.addInitParameter("resetEnable","false");
        return srb;
    }

    /**
     * 注册一个filterRegistrationBean
     */
    @Bean
    public FilterRegistrationBean druidStatFilter(){
        FilterRegistrationBean frb = new FilterRegistrationBean(new WebStatFilter());
        //添加过滤规则
        frb.addUrlPatterns("/*");
        //添加要忽略的格式信息
        frb.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return frb;
    }
}
  • 配置好了durid数据源后,我们开始搞读写分离,先新键一个DataSourceType枚举来确定数据库的类型(读库还是写库)

package org.meichao.config.vo;

public enum DataSourceType {

    read("read","从库"),write("write","主库");

    private String type;
    private String name;


    DataSourceType(String type, String name) {
        this.type = type;
        this.name = name;
    }

    public String getType() {
        return type;
    }

    public String getName() {
        return name;
    }
}
  • 然后新建两个注解@Read和@Write

package org.meichao.config.annotation;

import java.lang.annotation.*;

/**
 * Target指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里
 *      METHOD,TYPE分别表示方法和类
 * Retention 指明修饰的注解的生存周期,即会保留到哪个阶段
 *      RUNTIME运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用
 * Document 指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值
 */
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Read {
    String value() default "";
}
package org.meichao.config.annotation;

import java.lang.annotation.*;


@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Write {
    String value() default "";
}
  • 再然后新建一个DataSourceConfig类来定义数据源

package org.meichao.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * 定义数据源
 */
@Configuration
public class DataSourceConfig {

    private Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);

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

    /**
     * 配置写库
     * DataSourceBuilder里有几个默认的数据源,
     *  必须得加@Primary来确定优先执行,不然会报数据源找不到
     * @Primary 当几个类都实现继承了同一个类,且都被@Configuration注释,用@Primary来确定优先执行哪个类
     *  和@Qualifier作用差不多,只不过@qualifier是通过name属性决定执行哪个类
     */
    @Bean(name = "writeDataSource",destroyMethod = "close")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.write")
    public DataSource writeDataSource(){
        logger.info("-----------writeDataSource init----------");
        return DataSourceBuilder.create().type(dataSourceType).build();

    }

    /**
     * 配置读库
     *
     */
    @Bean(name = "readDataSource",destroyMethod = "close")
    @ConfigurationProperties(prefix = "spring.datasource.read")
    public DataSource readDataSource(){
        logger.info("-----------readDataSource init----------");
        return DataSourceBuilder.create().type(dataSourceType).build();

    }
}
  • 再来新建一个数据源切换的处理类DataSourceContextHolder

package org.meichao.config;

import org.meichao.config.vo.DataSourceType;

public class DataSourceContextHolder {

    private static final ThreadLocal<String> local = new ThreadLocal<>();

    public static ThreadLocal<String> getLocal(){
        return local;
    }

    /**
     * 切换到读库
     */
    public static void switch2Read(){
        local.set(DataSourceType.read.getType());
    }
    /**
     * 切换到写库
     */
    public static void switch2Write(){
        local.set(DataSourceType.write.getType());
    }

    /**
     * 返回当前线程的此线程局部变量的副本中的值
     */
    public static String getDataSourceType(){
        return local.get();
    }

}
  • 然后再来构建一个数据源代理,满足我们的数据源动态切换

package org.meichao.config;

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


/**
 * 构建数据源代理,使满足数据源的动态切换
 */
public class DataSourceProxy extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        //获取当前线程中设置的变量值,获取key来决定要使用哪个数据源
        return DataSourceContextHolder.getLocal();
    }

}
  • 终于到我们最重要的环节了,怎么才能让数据源自动切换呢,新建一个AOP来拦截本地线程变量

package org.meichao.config.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.meichao.config.DataSourceContextHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * aop拦截设置本地线程变量
 */
@Aspect
@Component
public class DataSourceAop {

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

    //本地线程
    private static final ThreadLocal<String> local = new ThreadLocal<>();

    /**
     * 数据源切换为读库
     */
    @Before("@annotation(org.meichao.config.annotation.Read)")
    public void setReadDataSourceType(){
        logger.info("dataSource切换到:Read");
        //local.set(DataSourceType.read.getType());
        DataSourceContextHolder.switch2Read();
    }

    /**
     * 数据源切换为写库
     */
    @Before("@annotation(org.meichao.config.annotation.Write)")
    public void setWriteDataSourceType(){
        logger.info("dataSource切换到:Write");
        //local.set(DataSourceType.write.getType());
        DataSourceContextHolder.switch2Write();
    }

}
  • 然后我们配置一下mybatis,就是自定义一个sqlSessionFactory来使用我们配置的数据源,不多BB,看代码 

  • package org.meichao.config;
    
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.meichao.config.vo.DataSourceType;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.annotation.MapperScan;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.AutoConfigureAfter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * mybatis配置
     */
    @Configuration
    @AutoConfigureAfter({DataSourceConfig.class})
    @MapperScan(basePackages = {"org.meichao.*.dao"})
    public class MybatisConfig {
    
        private static Logger logger = LoggerFactory.getLogger(MybatisConfig.class);
    
        @Bean
        public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSourceProxy")
                                                               DataSourceProxy dataSourceProxy) throws Exception {
            logger.info("-----------sqlSessionFactory init----------");
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dataSourceProxy);
            return sqlSessionFactoryBean.getObject();
        }
    
    
        /**
         *  @Qualifier 指定了哪个实现类才是我们需要加载的类,用name来确定
         * @param writeDataSource
         * @param readDataSource
         * @return
         */
        @Bean
        public DataSourceProxy dataSourceProxy(@Qualifier("writeDataSource") DataSource writeDataSource,
                                               @Qualifier("readDataSource") DataSource readDataSource){
            logger.info("-----------dataSourceProxy init----------");
            Map<Object,Object> targetDataSource = new HashMap<>();
            targetDataSource.put(DataSourceType.write.getType(), writeDataSource);
            targetDataSource.put(DataSourceType.read.getType(), readDataSource);
    
            DataSourceProxy dataSourceProxy = new DataSourceProxy();
            //设置默认的数据源
            dataSourceProxy.setDefaultTargetDataSource(writeDataSource);
            //设置数据源
            dataSourceProxy.setTargetDataSources(targetDataSource);
            return dataSourceProxy;
        }
    
    }
    

     

  • 完事,那么问题来了,这样就行了吗,好吧,我们还需要在方法上加上注解,您看:

  • 来一张配置成功后启动项目日志

  • 再来一张使用切换数据库日志(里面其他打印日志后面会写文章,如果有的话)

  • 完结撒花 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值