Mybatis Multiple Data Sources Read-Write Separation (Annotation Implementation)

Keywords: Java Spring Mybatis Druid

Mybatis Multiple Data Sources Read-Write Separation (Annotation Implementation)

First, we need to build two libraries for testing. I use master_test and slave_test libraries here. Both libraries have the same table (lazy, happy), table structure.

Table name t_user

| Field Name | Type | Note|
| :------: | :------: | :------: |
| id | int | Primary key self-increasing ID|
| name | varchar | name|


Two different data are added to the table to facilitate testing.
The main database record name is xiaobin, and the slave database record name is xiaoliu.
Start using Springboot to integrate mybatis, first introduce the pom file

   <?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.4.RELEASE</version>
   </parent>
   <groupId>com.xiaobin</groupId>
   <artifactId>mysql_master_slave</artifactId>
   <version>1.0-SNAPSHOT</version>

   <properties>
       <java.version>1.8</java.version>
       <lombok.version>1.18.6</lombok.version>
       <mybatis.version>1.3.2</mybatis.version>
       <lombox.version>1.18.6</lombox.version>
   </properties>

   <dependencies>
       <!-- Add to web Starting coordinates -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-web</artifactId>
       </dependency>
       <!-- Add to lombok Tool coordinates -->
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombok.version}</version>
       </dependency>
       <!-- Add to springboot Test coordinates -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-test</artifactId>
       </dependency>
       <!-- Add to lombox Test coordinates -->
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
           <version>${lombox.version}</version>
       </dependency>
       <!-- Add to mybatis Dependent coordinates -->
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>${mybatis.version}</version>
       </dependency>
       <!-- Add to mysql Driver coordinates -->
       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-jdbc</artifactId>
       </dependency>
       <!-- Add to druid Data source coordinates -->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid-spring-boot-starter</artifactId>
           <version>1.1.10</version>
       </dependency>
       <!-- Add to AOP coordinate -->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-aop</artifactId>
       </dependency>

   </dependencies>
</project>

Dynamic Data Source Configuration

The data source used here is druid, which realizes switching between data sources by using @DataSource custom annotation and configuring Aop for switching.
application.yml configuration file

spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        druid:
          xiaobin-master: # Master data source
            driverClassName: com.mysql.jdbc.Driver
            username: root
            password: root
            url: jdbc:mysql://localhost:3306/master_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
          xiaobin-slave: # From data sources
            driverClassName: com.mysql.jdbc.Driver
            username: root
            password: root
            url: jdbc:mysql://localhost:3306/slave_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
mybatis:
  mapper-locations: classpath:mapper/*.xml

Multiple Data Source Configuration Class

package com.xiaobin.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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 org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * Creation time: 2019/9/22 11:42
 * Note: Multiple Data Source Configuration Information
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

@Configuration
@Component
public class DynamicDataSourceConfig {


    @Bean
    @ConfigurationProperties("spring.datasource.druid.xiaobin-master")
    public DataSource  xiaobinMasterDataSource(){
        return DruidDataSourceBuilder.create().build();
    }


    @Bean
    @ConfigurationProperties("spring.datasource.druid.xiaobin-slave")
    public DataSource  xiaobinSlaveDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource xiaobinMasterDataSource, DataSource xiaobinSlaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("xiaobin-master",xiaobinMasterDataSource);
        targetDataSources.put("xiaobin-slave", xiaobinSlaveDataSource);
        return new DynamicDataSource(xiaobinMasterDataSource, targetDataSources);
    }

}

Dynamic Data Source Switching Class

package com.xiaobin.config;

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

import javax.sql.DataSource;
import java.util.Map;

/**
 * Creation time: 2019/9/22 11:51
 * Note: Dynamic Data Sources
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

public class DynamicDataSource  extends AbstractRoutingDataSource {

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

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static String getDataSource() {
        return contextHolder.get();
    }

    public static void clearDataSource() {
        contextHolder.remove();
    }
}

Custom @DataSource annotation

Add this annotation to Dao that needs to switch data

package com.xiaobin.annotation;

import java.lang.annotation.*;

/**
 * Creation time: 2019/9/22 11:53
 * Note: Custom Data Source Selection Annotation
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String name() default "";
}

Aop Section Class Configuration

package com.xiaobin.aspect;

import com.xiaobin.annotation.DataSource;
import com.xiaobin.config.DynamicDataSource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * Creation time: 2019/9/22 11:54
 * Remarks:
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.xiaobin.annotation.DataSource)")
    public void dataSourcePointCut() {

    }


    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();


        DataSource dataSource = method.getAnnotation(DataSource.class);
        if(dataSource == null){
            DynamicDataSource.setDataSource("xiaobin-master");
        }else {
            DynamicDataSource.setDataSource(dataSource.name());
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSource.clearDataSource();
        }
    }
}

Start configuration annotation information, important (otherwise running will report errors)

package com.xiaobin;

import com.xiaobin.config.DynamicDataSourceConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;

/**
 * Creation time: 2019/9/22 11:17
 * Remarks:
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class})
@MapperScan(basePackages = "com.xiaobin.mapper")
@Import({DynamicDataSourceConfig.class})
public class StartApp {
    public static void main(String[] args) {
        SpringApplication.run(StartApp.class);
    }
}

Test controller

package com.xiaobin.api;

import com.xiaobin.Entity.TUser;
import com.xiaobin.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * Creation time: 2019/9/22 12:08
 * Remarks:
 * Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.
 **/

@RestController
@RequestMapping
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/{name}/list")
    public List<TUser> list(@PathVariable("name")String name){
        if(name.equals("master")){
            return userMapper.queryAllWithMaster();
        }else{
            return userMapper.queryAllWithSlave();
        }
    }
}

Design sketch

More path value transfer, master-slave data source switching

directory structure

Source address (database needs to be created by itself) https://gitee.com/MyXiaoXiaoB...

Binong Self-taught Exchange Group: 260532022. Welcome to join us. It's a pleasure to share learning.

Posted by M. Abdel-Ghani on Thu, 03 Oct 2019 00:26:17 -0700