Springboot integrates mybatis (using baomidou's mybatis-plus-boot-starter) to achieve multi-data source switching

Based on mybatis multi-data source switching, what needs to be prepared first to realize it?

1. Pom dependency

2. yml configuration parameters

3. Code aspect

(1) DataSource ready to connect

(2) aspectj (section), used to dynamically switch data sources

pom dependency

<?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">    <parent>        <artifactId>spring-boot-group</artifactId>        <groupId>com.wolf.boy</groupId>        <version>1.0-SNAPSHOT</version>    </parent>    <modelVersion>4.0.0</modelVersion>     <artifactId>spring-boot-mybatis-d2</artifactId>    <properties>        <mybatis-plus.version>3.4.2</mybatis-plus.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-aop</artifactId>        </dependency>        <dependency>            <groupId>com.baomidou</groupId>            <artifactId>mybatis-plus-boot-starter</artifactId>            <version>${mybatis-plus.version}</version>        </dependency>        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>druid-spring-boot-starter</artifactId>        </dependency>        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <scope>runtime</scope>        </dependency>        <dependency>            <groupId>cn.hutool</groupId>            <artifactId>hutool-all</artifactId>        </dependency>        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-devtools</artifactId>            <scope>runtime</scope>            <optional>true</optional>        </dependency>    </dependencies> </project>

yml parameter configuration

In the yml parameter configuration, configure the configuration of 2 database sources

server:  port: 7777 spring:  datasource:    db1:      jdbc-url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&tinyInt1isBit=false      username: root      password: 123456      type: com.alibaba.druid.pool.DruidDataSource      driver-class-name: com.mysql.cj.jdbc.Driver    db2:      jdbc-url: jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&tinyInt1isBit=false      username: root      password: 123456      type: com.alibaba.druid.pool.DruidDataSource      driver-class-name: com.mysql.cj.jdbc.Driver

Code

DataSourceConfig data source configuration

package com.wolf.boy.config; /** * Created by user on 2021/6/7. */ import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;import com.wolf.boy.config.holder.DynamicDataSource;import org.apache.ibatis.session.SqlSessionFactory;import org.mybatis.spring.SqlSessionFactoryBean;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 org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource;import java.util.HashMap;import java.util.Map; /** * 多数据源配置类 */@Configurationpublic class DataSourceConfig {    //数据源1    @Bean(name = "datasource1")    @ConfigurationProperties(prefix = "spring.datasource.db1") // application.yml中对应属性的前缀    public DataSource dataSource1() {        return DataSourceBuilder.create().build();    }     //数据源2    @Bean(name = "datasource2")    @ConfigurationProperties(prefix = "spring.datasource.db2") // application.yml中对应属性的前缀    public DataSource dataSource2() {        return DataSourceBuilder.create().build();    }    //====以上部分,先准备好2个传统的数据源====         /**     * 动态数据源: 通过AOP在不同数据源之间动态切换     *     * @return     */    @Primary    @Bean(name = "dynamicDataSource")    public DataSource dynamicDataSource() {        //自定义动态数据源类#定义一个动态数据源:继承AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法        DynamicDataSource dynamicDataSource = new DynamicDataSource();        // 默认数据源        dynamicDataSource.setDefaultTargetDataSource(dataSource1());        //配置多数据源(这个信息在选取datasource的时候,被determineCurrentLookupKey方法调用)        Map<Object, Object> dsMap = new HashMap();        dsMap.put("datasource1", dataSource1());        dsMap.put("datasource2", dataSource2());        dynamicDataSource.setTargetDataSources(dsMap);        return dynamicDataSource;    }     /* 配置@Transactional注解事物    *    * @return    */    @Bean    public PlatformTransactionManager transactionManager() {        //把动态数据源给到事务管理器,进行事务管理        return new DataSourceTransactionManager(dynamicDataSource());    }}

DynamicDataSource Custom dynamic data source class.

The main thing is to inherit AbstractRoutingDataSource and rewrite the determineCurrentLookupKey method to dynamically obtain the dynamicDataSource data source and start setting

//Configure multiple data sources (this information is called by the determineCurrentLookupKey method when selecting datasource)
        Map<Object, Object> dsMap = new HashMap();
        dsMap.put("datasource1", dataSource1());
        dsMap.put( "datasource2", dataSource2());

These 2 parameters. This is the data source that actually needs to be used in the end;

package com.wolf.boy.config.holder; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 定义一个动态数据源:继承AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法 */public class DynamicDataSource extends AbstractRoutingDataSource {     @Override    protected Object determineCurrentLookupKey() {        System.out.println("数据源为" + DataSourceContextHolder.getDB());        return DataSourceContextHolder.getDB();    }}

DataSourceContextHolder data source context processing class

The main function of this type is to maintain the current processing thread context and the selected data source through ThreadLocal, and then use it together when selecting the data source in the above DynamicDataSource dynamic data source determineCurrentLookupKey method.

package com.wolf.boy.config.holder; /** */public class DataSourceContextHolder {     /**     * 默认数据源     */    public static final String DEFAULT_DS = "datasource1";     private static final ThreadLocal<String> chooseDataSourceName = new ThreadLocal<>();     // 设置数据源名    public static void setDB(String dbType) {        System.out.println("切换到{" + dbType + "}数据源");        chooseDataSourceName.set(dbType);    }     // 获取数据源名    public static String getDB() {        return (chooseDataSourceName.get());    }     // 清除数据源名    public static void clearDB() {        chooseDataSourceName.remove();    }}

DynamicDataSourceAspect custom aspect class based on aspect

The essential:

1. If you want to realize the aspect, you need to add @Aspect annotations

2. The aspect class must be loaded into the spring container, so the @Component annotation needs to be added

package com.wolf.boy.config.aspect; /** * Created by user on 2021/6/7. */ import com.wolf.boy.annotation.DbChoose;import com.wolf.boy.config.holder.DataSourceContextHolder;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.annotation.After;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Before;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * 自定义注解 + AOP的方式实现数据源动态切换。 */@Aspect //切点类@Componentpublic class DynamicDataSourceAspect {     @Pointcut("@annotation(com.wolf.boy.annotation.DbChoose)")    public void pointCut() {     }     /**     * 在之前干的事情     *     * @param point     */    @Before("pointCut()")    public void beforeSwitchDS(JoinPoint point) {        //获得当前访问的class        Class<?> className = point.getTarget().getClass();        //获得访问的方法名        String methodName = point.getSignature().getName();        //得到方法的参数的类型        Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();        String dataSource = DataSourceContextHolder.DEFAULT_DS;        try {            // 得到访问的方法对象            Method method = className.getMethod(methodName, argClass);            // 判断是否存在@DS注解            if (method.isAnnotationPresent(DbChoose.class)) {                DbChoose annotation = method.getAnnotation(DbChoose.class);                // 取出注解中的数据源名                dataSource = annotation.value();            }        } catch (Exception e) {            e.printStackTrace();        }        // 切换数据源        DataSourceContextHolder.setDB(dataSource);        System.out.println("【AOP切点--Before】设置数据源:" + dataSource);    }     @After("pointCut()")    public void afterSwitchDS(JoinPoint point) {        System.out.println("【AOP切点--After】操作干完了,释放当前选择的数据源");        DataSourceContextHolder.clearDB();    }}

DbChoose custom annotation

Used to mark the method of the data source that needs to be used, and then set the name of the data source, obtain the annotation information on the aspect class through the aspect aspect, and then get the value=data source name, and then pass the above

DataSourceContextHolder data source context processing; this puts the current data source that needs to be used for the current execution thread

package com.wolf.boy.annotation; import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target; /** */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface DbChoose {    String value() default "";}

Other implementation codes (in order to test the results)

  • UserController request entry
package com.wolf.boy.controller; import com.wolf.boy.annotation.DbChoose;import com.wolf.boy.entity.User;import com.wolf.boy.service.IUserService;import lombok.AllArgsConstructor;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController; /** */@[email protected]("/user/")@AllArgsConstructorpublic class UserController {     private final IUserService userService;     @GetMapping("goD1")    @DbChoose("datasource1")    public User saveD1() {        User user = new User();        user.setAge(18);        user.setName("张大仙去数据库01");        userService.save(user);        return user;    }     @GetMapping("goD2")    @DbChoose("datasource2")    public User saveD2() {        User user = new User();        user.setAge(20);        user.setName("理大仙去数据库02");        userService.save(user);        return user;    }}
  • User entity class
package com.wolf.boy.entity; import com.baomidou.mybatisplus.annotation.IdType;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable; /** */@[email protected]("user_t")@[email protected] class User implements Serializable {     @TableId(value = "id", type = IdType.AUTO)    private Integer id;    /**     * 名称     */    private String name;    /**     * 年龄     */    private int age;  }
  • Mapper interface class
package com.wolf.boy.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.wolf.boy.entity.User; /** */public interface UserMapper extends BaseMapper<User> {}
  • Service interface and implementation class
package com.wolf.boy.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.wolf.boy.entity.User;import com.wolf.boy.mapper.UserMapper;import com.wolf.boy.service.IUserService;import org.springframework.stereotype.Service; /** */@Servicepublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {}  package com.wolf.boy.service; import com.baomidou.mybatisplus.extension.service.IService;import com.wolf.boy.entity.User; /** */public interface IUserService extends IService<User> {}

Program main start class

package com.wolf.boy; import cn.hutool.json.JSONUtil;import com.wolf.boy.entity.User;import com.wolf.boy.service.IUserService;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.ConfigurableApplicationContext; import java.util.List; /** *///要去掉springboot底层的自动配置数据源@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@MapperScan("com.wolf.boy.mapper")public class MainRun {     public static void main(String[] args) {        ConfigurableApplicationContext run = SpringApplication.run(MainRun.class, args);    }}

test

Browser

输入:http://localhost:7777/user/goD1

输入:http://localhost:7777/user/goD2

Then looking at the database

test table

test01 table

Use different data sources and insert data.

Program print log:

[AOP cut point--Before] set the data source: datasource2
data source is datasource2
[AOP cut point--After] operation is finished, release the currently selected data source

can be seen

  1. The data source is set up through the before of the aspect, and the data source is interpreted through the annotation on the method of the class to be aspected
  2. Then select the data source, read the data source set in step 1 and initialize the set datasoucre
  3. After use, you need to release the information stored in threadlocal