读写分离实现 | 第二节 springboot2+mybatis+mysql配置

注意:该项目基于上一节mysql主从配置

一. 主要pom依赖

注意:有些依赖是我自己添加用的,不是必须的

	<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <groupId>com.lzr</groupId>
    <artifactId>study-readwrite-separation</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>读写分离项目</name>
    <description>mysql 读写分离项目 测试</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-beanutils/commons-beanutils -->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.7.0</version>
        </dependency>
        <!--分页工具类-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!--mysql驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.35</version>
        </dependency>
        <!-- Hikaricp数据库连接池 -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
        </dependency>
        <!-- 使用Hikaricp数据库连接池所需的jdbc包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- 提供了大量mybatis操作的方法 -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--用于编译jsp-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <!--<scope>provided</scope>-->
        </dependency>
        <!-- 二维码工具包 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-base</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-web</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>cn.afterturn</groupId>
            <artifactId>easypoi-annotation</artifactId>
            <version>4.0.0</version>
        </dependency>
        <!-- 热更新依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Jsoup是用于解析HTML,就类似XML解析器用于解析XML -->
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.2</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.4.2.RELEASE</version>
                <configuration>
                    <mainClass>com.lzr.ReadWriteSeparationApplication</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.5</version>
                <configuration>
                    <!-- 是否覆盖 -->
                    <overwrite>true</overwrite>
                    <!--允许移动生成的文件 -->
                    <verbose>true</verbose>
                    <!-- 自动生成的配置,${basedir}表示项目根目录 ,configurationFile默认在resource目录下-->
                    <configurationFile>${basedir}/src/main/resources/mybatis/mybatis-generator.xml</configurationFile>
                </configuration>
                <dependencies>
                    <!--mysql驱动包-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>5.1.35</version>
                        <scope>runtime</scope>
                    </dependency>
                    <dependency>
                        <groupId>tk.mybatis</groupId>
                        <artifactId>mapper</artifactId>
                        <version>3.5.3</version>
                    </dependency>
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.3.5</version>
                    </dependency>
                </dependencies>
            </plugin>
            <!-- 忽略无web.xml警告 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
        <!-- 注意:如果有这个,则不能使用mybatis-generator生成,必须先注释下面的resources配置才行 -->
        <resources>
            <resource>
                <directory>src/main/webapp</directory>
                <targetPath>META-INF/resources</targetPath>
                <includes>
                    <include>**/**</include>
                </includes>
            </resource>
            <!--<resource>-->
            <!--<directory>src/main/java</directory>-->
            <!--<includes>-->
            <!--<include>**/*.yml</include>-->
            <!--<include>**/*.properties</include>-->
            <!--<include>**/*.xml</include>-->
            <!--</includes>-->
            <!--<filtering>false</filtering>-->
            <!--</resource>-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/**</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

二. application.yml配置

spring:
  # Hikaricp数据库连接池
  datasource:
    # 主配置(端口3306)
    master:
      type: com.zaxxer.hikari.HikariDataSource
      jdbc-url: jdbc:mysql://localhost:3306/readwriteseparation?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
      read-only: false
      connection-test-query: SELECT 1
      idle-timeout: 600000
      max-lifetime: 3000000
      connection-timeout: 3000
      maximum-pool-size: 5
      minimum-idle: 5
    # 从配置(端口3307)(最好设置只读账户)
    slave1:
      type: com.zaxxer.hikari.HikariDataSource
      jdbc-url: jdbc:mysql://localhost:3307/readwriteseparation?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: onlyRead
      password: root
      driver-class-name: com.mysql.jdbc.Driver
      read-only: true
      idle-timeout: 600000
      max-lifetime: 3000000
      connection-timeout: 2000
      maximum-pool-size: 5
      minimum-idle: 5
    # 从配置(端口3307)(最好设置只读账户)
    slave2:
      type: com.zaxxer.hikari.HikariDataSource
      jdbc-url: jdbc:mysql://localhost:3307/readwriteseparation?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
      username: onlyRead
      password: root
      driver-class-name: com.mysql.jdbc.Driver
      read-only: true
      idle-timeout: 600000
      max-lifetime: 3000000
      connection-timeout: 2000
      maximum-pool-size: 5
      minimum-idle: 5
  # Spring boot视图配置
  mvc:
    view:
      prefix:
      suffix: .jsp
  # 数据时间格式处理
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
# 全局配置
server:
  port: 8080
  tomcat:
    uri-encoding: UTF-8
# 配置pageHelper分页插件的内容
pagehelper:
  auto-dialect: mysql
  reasonable: true
  support-methods-arguments: true
  params: count=countSql
# 日志配置文件
logging:
  config: classpath:logback.xml

三. 日志logback.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <contextName>logback</contextName>
    <property name="log.path" value="logs/study-readwriteseparation/logback/" />
    <property name="log.file" value="logs/study-readwriteseparation/logback.log" />

    <!-- 彩色日志 -->
    <!-- 彩色日志依赖的渲染类 -->
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />

    <!-- 彩色日志格式 -->
    <property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %clr(%contextName) [%thread] %clr(%-5level) %logger{36} - %msg%n" />

    <!--输出到控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level>
            </filter> -->
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!--输出到文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.file}</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}%d{yyyy-MM-dd_HH}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <!-- 指定日志文件的上限大小,例如设置为1GB的话,那么到了这个值,就会删除旧的日志 -->
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="info">
        <appender-ref ref="console" />
        <appender-ref ref="file" />
    </root>

    <!-- dao层日志为debug,可以打印执行的sql语句 -->
    <logger name="com.lzr.dao" level="DEBUG"/>

</configuration>

四. 代码配置

注意:我这里只贴出主要代码

1. 切面处理

package com.lzr.config.datasourceconfig;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

/**
 * 使用切面处理
 * 如果在service层设置数据源
 * 必须在事务AOP之前执行,所以实现PriorityOrdered,其中order的值越小,越先执行
 * 如果一旦开始切换到写库,则之后的读都会走写库
 * 注:AOP ,内部方法之间互相调用时,如果是this.xxx()这形式,不会触发AOP拦截
 */
@Aspect
@Component
public class DataSourceAop implements Ordered {

    @Before("@annotation(com.lzr.annotation.Slave)")
    public void read() {
        DBContextHolder.slave();
    }

    @Before("@annotation(com.lzr.annotation.Master)")
    public void write() {
        DBContextHolder.master();
    }

    /**
     * 值越小,越优先执行
     * 需要优于事务的执行
     * 在启动类中加上了@EnableTransactionManagement(order = 10)
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }
}

2. 数据源配置

package com.lzr.config.datasourceconfig;

import com.lzr.response.enums.DBTypeEnum;
import lombok.extern.log4j.Log4j2;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置
 */
@Log4j2
@Configuration
public class DataSourceConfig {

	// 也可以new HikariDataSource(),但是DataSourceBuilder.create().build()会去寻找依赖
    // 当我们修改连接池的时候,就不需要修改本地的java代码了
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

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

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

    @Bean
    public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                          @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                          @Qualifier("slave2DataSource") DataSource slave2DataSource) {
        log.info("开始配置多数据源");
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
        targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
        targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
        MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
        // 默认执行数据源
        myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
        // 目标执行数据源集合
        myRoutingDataSource.setTargetDataSources(targetDataSources);
        log.info("多数据源配置完成");
        return myRoutingDataSource;
    }

}

3. 上下文key值处理设置类

package com.lzr.config.datasourceconfig;


import com.lzr.response.enums.DBTypeEnum;
import lombok.extern.log4j.Log4j2;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * 上下文key值处理设置类
 */
@Log4j2
public class DBContextHolder {

    // 初始化数字
    private static final Integer initNum = new Integer(0);

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

    private static final AtomicInteger atomicInteger = new AtomicInteger(initNum);

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

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

    public static void master() {
        set(DBTypeEnum.MASTER);
        log.info("切换到master");
    }

    public static void slave() {
        //  处理两个从库切换方式
        int index = atomicInteger.getAndIncrement();
        if (index>>10 > 1) {// 最大请求2048次
            atomicInteger.set(initNum);
        }
        if (index>>1 == 0) {
            set(DBTypeEnum.SLAVE1);
            log.info("切换到"+DBTypeEnum.SLAVE1.name());
        }else {
            set(DBTypeEnum.SLAVE2);
            log.info("切换到"+DBTypeEnum.SLAVE2.name());
        }
    }

}

4. mybatis 配置类

package com.lzr.config.datasourceconfig;

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

/**
 * mybatis 配置类
 */
@EnableTransactionManagement
@Configuration
public class MyBatisConfig {

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

    /**
     * 工厂设置
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));// 映射文件
        sqlSessionFactoryBean.setTypeAliasesPackage("com.lzr.model,com.lzr.vo,com.lzr.dto");// 别名扫描包
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setCallSettersOnNulls(true);// 为空时也设置数据
        configuration.setMapUnderscoreToCamelCase(true);// 开启驼峰转换
        sqlSessionFactoryBean.setConfiguration(configuration);
        return sqlSessionFactoryBean.getObject();
    }

    /**
     * 事务设置
     * @return
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

5. 对应的key值获取配置类

获取当前路由数据源key

package com.lzr.config.datasourceconfig;

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

/**
 * 获取database对应的key值
 * DataSourceConfig中配置的key
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {
    /**
     * 在DBContextHolder中获取当前请求对应的database的key值
     * @return
     */
    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }

}

6. 两个注解Master与Slave

6.1 自定义Master注解

package com.lzr.annotation;

/**
 * 走主库
 * @author lzr
 * @date 2019/12/13 0013 15:57
 */
public @interface Master {
}

6.2 自定义Slave注解

package com.lzr.annotation;

/**
 * 走从库
 * @author lzr
 * @date 2019/12/13 0013 15:57
 */
public @interface Slave {
}

7. 自定义枚举类型

package com.lzr.response.enums;

/**
 * 主从库 枚举类型 可以当做别名(也可以使用application.yml设置别名)
 */
public enum DBTypeEnum {

    MASTER, SLAVE1, SLAVE2;

}

8. 启动类

package com.lzr;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import tk.mybatis.spring.annotation.MapperScan;

/**
 * @author lzr
 * @date 2019/12/11 0011 17:43
 */
@SpringBootApplication
@MapperScan(basePackages = "com.lzr.dao")
@EnableTransactionManagement(order = 10)// 配置事务优先级为10 (不设置order,则默认最大integer数值)
public class ReadWriteSeparationApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReadWriteSeparationApplication.class,args);
    }

}

9. service中测试图

在这里插入图片描述
当然,如果你不加注解,则会走默认库。
由于在类上加上了事务注解,所以在自定义aop中需要设置拦截order,需要在事务之前处理好当前线程所要走的库,不然,会先连接默认库。

五. 大致项目结构图

注意:此图非完整项目结构,如果需要,则拉取下面的git路径
在这里插入图片描述

六. 项目git地址

git地址:读写分离项目git地址
若有疑问,请在评论区留言,谢谢

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值