ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

mybaits+druid+aop 实现读写分离(支持已定义注解读数据切换主从库)

2022-01-21 11:03:06  阅读:152  来源: 互联网

标签:admin druid 读数据 annotation mybaits org import com public


项目结构

             注释:通过druid+mybaits 实现读写分离,支持一主多送。支持自定义注解,实现部分从主库读取数据

 

 

 

1.依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

  

2.yaml文件配置

spring:
    datasource:
    poolPreparedStatements: "true"
    druid:
      stat-view-servlet:
        login-password: "123"
        login-username: "admin"
        enabled: "true"
    useGlobalDataSourceStat: "true"
    slave2:
      url: "jdbc:mysql://localhost:3306/db03?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
      username: "root"
      type: "com.alibaba.druid.pool.DruidDataSource"
    master:
      type: "com.alibaba.druid.pool.DruidDataSource"
      url: "jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
      username: "root"
    slave1:
      username: "root"
      url: "jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC"
      type: "com.alibaba.druid.pool.DruidDataSource"
    maxWait: "60000"
    web-stat-filter:
      exclusions: "\"*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*\""
    stat-view-servlet:
      url-pattern: "/druid/*"
      reset-enable: "false"
    validationQuery: "SELECT 1 FROM DUAL"
    minEvictableIdleTimeMillis: "300000"
    filters: "stat,wall,log4j"
    testOnBorrow: "false"
    testOnReturn: "false"
    initialSize: "5"
    maxActive: "20"
    maxPoolPreparedStatementPerConnectionSize: "20"
    testWhileIdle: "true"
    timeBetweenEvictionRunsMillis: "60000"
    minIdle: "5"
    url-pattern: "/*"    

 

3.自定义注解master(用于有些读写都需要主数据库)

package com.hc.admin.annotation;


import java.lang.annotation.*;

/**
 * 自定义注解(用该注解标注的就读主库)
 *
 * @author summer.chou
 * @date 2021/01/21
 *
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Master {
}

4.DataSourceConfig

package com.hc.admin.dbconfig;


import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
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+druid(实现读写分离)
 * 简单的读写分离并不使用这套架构,可以用nacos+seata 实现分表分库的读写分离)
 *
 * @author summmer.chou
 * @date 2021/01/21
 */
@Configuration
@Slf4j
public class DataSourceConfig {

    /*
     * druid
     * */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        log.info(targetDataSources.toString());
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        return myRoutingDataSource;


    }

}

5.DBContextHolder

package com.hc.admin.dbconfig;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 获取当前线程所用数据类型
 *
 * @author summer.chou
 * @date 2022/01/21
 */
@Slf4j
@Component
public class DBContextHolder {
    /*
        * contextHolder 是线程变量,因为每个请求是一个线程,所以通过这样来区分使用哪个库
          determineCurrentLookupKey是重写的AbstractRoutingDataSource的方法,
          主要是确定当前应该使用哪个数据源的key,因为AbstractRoutingDataSource 中保存的多个数据源是通过Map的方式保存的
        * */
    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);
        log.info("切换到master");
    }

    public static void slave() {
        //  轮询
        int index = counter.getAndIncrement() % 2;
        if (counter.get() > 9999) {
            counter.set(-1);
        }
        if (index == 0) {
            set(DBTypeEnum.SLAVE1);
            log.info("切换到slave1");
        } else {
            set(DBTypeEnum.SLAVE2);
            log.info("切换到slave2");
        }
    }


}

6.DBTypeEnum

package com.hc.admin.dbconfig;


/**
 * 数据源枚举分类
 *
 * @author summmer.chou
 * @date 2022/01/21
 */
public enum DBTypeEnum {
    MASTER, SLAVE1, SLAVE2
}

7.MybatisConfig

package com.hc.admin.dbconfig;


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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

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


/**
 * MyBatis配置
 *
 * @author summmer.chou
 * @date 2020/01/21
 */
@EnableTransactionManagement
@Configuration
public class MybatisConfig {

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

    /*由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。*/
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        //我采取的是注解式sql,如果加上扫描,但包下无mapper.xml会报错
        //sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));

        return sqlSessionFactoryBean.getObject();
    }

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

}

8.MyRoutingDataSource

package com.hc.admin.dbconfig;


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

import javax.annotation.Nullable;

/**
 * 获取路由key
 *
 * @author summmer.chou
 * @date 2022/01/21
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {


    @Override
    @Nullable
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

9.LogAndValidAop

package com.hc.admin.aspect;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.hc.admin.dbconfig.DBContextHolder;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;


/**
 * @Description  1.数据库读写分离
 *               2.自定义注解LogAnnotation+aop  打印请求路劲 +响应参数 (可截取注解参数,做全局日志持久化操作)
 * @Author summer.chou
 * @Date 2021/1/20
 */
@Component
@Aspect
public class LogAndValidAop {


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


    //切点强制
    //主写从读,部分需要从主数据库读的使用自定义注解
    @Pointcut("!@annotation(com.hc.admin.annotation.Master) " +
            "&& (execution(* com.hc.admin.service..*.select*(..)) " +
            "|| execution(* com.hc.admin.service..*.get*(..)))")
    public void readPointcut() {

    }

    //删除需要指定方法名开头的
    @Pointcut("@annotation(com.hc.admin.annotation.Master) " +
            "|| execution(* com.hc.admin.service..*.insert*(..)) " +
            "|| execution(* com.hc.admin.service..*.add*(..)) " +
            "|| execution(* com.hc.admin.service..*.update*(..)) " +
            "|| execution(* com.hc.admin.service..*.edit*(..)) " +
            "|| execution(* com.hc.admin.service..*.delete*(..)) " +
            "|| execution(* com.hc.admin.service..*.remove*(..))")
    public void writePointcut() {

    }

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

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


    //定义切点
    @Pointcut("@annotation(com.hc.admin.annotation.LogAnnotation)")
    public void pointCutRestDef() {
    }




    //自定义打印日志
    @Around("pointCutRestDef()")
    public Object processRst(ProceedingJoinPoint point) throws Throwable {
        Object returnValue = null;
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = sra.getRequest();
        Object[] args = point.getArgs();
        final List<Object> params = new ArrayList<>();
        //获取非GET请求的参数
        if (!"GET".equalsIgnoreCase(request.getMethod())) {
            for (int i = 0; i < args.length; i++) {
                Object object = args[i];
                if (object instanceof HttpServletResponse) {
                    continue;
                }
                if (object instanceof HttpServletRequest) {
                    continue;
                }
                params.add(object);
            }
        } else {
            Enumeration names = request.getParameterNames();
            JSONObject object = new JSONObject();
            while (names.hasMoreElements()) {
                String name = (String) names.nextElement();
                String value = request.getParameter(name);
                object.put(name, value);

            }
            params.add(object);
        }
        Long startTime = System.currentTimeMillis();
        try {
            //获取返回值
            returnValue = point.proceed(point.getArgs());
        } catch (Exception e) {
            // 请求异常处理
            throw e;
        }
        String url = request.getRequestURI();//获取接口路劲
        String method = request.getMethod();

        Long endTime = System.currentTimeMillis();
        StringBuffer logString = new StringBuffer();
        logString.append("\n" + " 请求方法 method :" + method);
        logString.append("\n" + " 响应时间 time:" + (endTime - startTime) + "ms");
        logString.append("\n" + " 请求路径 url:" + url);
        logString.append("\n" + " 请求参数 param:" + JSONObject.toJSONString(params));
        logString.append("\n" + " 返回结果 json:" + objTtoJson(returnValue));
        logger.info(logString.toString());
        //todo LogAnnotation获取注解参数值 用来拓展全局日志
        // MethodSignature signature = (MethodSignature) point.getSignature();
        //Method method = signature.getMethod();
        // LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        //添加日志
        return returnValue;
    }

    public String objTtoJson(Object returnValue) {
        JSONObject object = new JSONObject();
        JSONObject json = (JSONObject) JSON.toJSON(returnValue);

        for (Map.Entry<String, Object> entry : json.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            object.put(key, value);
        }
        return object.toJSONString();
    }
}

 

标签:admin,druid,读数据,annotation,mybaits,org,import,com,public
来源: https://www.cnblogs.com/summer-chou/p/15829384.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有