SpringBoot2.x+MybatisPlus+HikariCP多数据源动态配置

SpringBoot2.x+MybatisPlus+HikariCP多数据源动态配置

环境
SpringBoot 2.7.0
MybatisPlus 3.5.2
项目源码地址

一、准备工作

准备三个数据库 testdb testdb1 testdb2

类似主服务器 、从服务器、从服务器

创建一个user

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
                        `id` int NOT NULL AUTO_INCREMENT,
                        `name` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
                        `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
                        PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` (`id`, `name`, `create_time`) VALUES (1, '小明', '2022-08-11 14:54:51');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

然后每个数据库插入不同的数据,方便区分是否成功。

# testdb
INSERT INTO `user` (`id`, `name`, `create_time`) VALUES (1, '小明', '2022-08-11 14:54:51');
# testdb1
INSERT INTO `user` (`id`, `name`, `create_time`) VALUES (1, '小明1', '2022-08-11 14:54:51');
# testdb2
INSERT INTO `user` (`id`, `name`, `create_time`) VALUES (1, '小明2', '2022-08-11 14:54:51');

Springboot项目开始

新建一个Springboot项目,可以使用start.spring.io或者idea创建项目。

项目目录如下:
在这里插入图片描述
pom.xml 文件

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
数据源路由器

AbstractRoutingDatasource 需要知道要路由到哪个实际 DataSource 的信息。该信息通常称为上下文。在示例中,我们将使用 DBTypeEnum 的概念作为我们的上下文,并具有以下实现:
DBTypeEnum.java

public enum DBTypeEnum {
    MAIN, CLIENT_A, CLIENT_B;
}

新建一个 DBContextHolder.java

package com.wumeng.dynamicmultidatabase.config;

/**
 * @author wumeng 2022/8/11 3:02 下午
 */
public class DBContextHolder {
    private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();

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

    public static DBTypeEnum getCurrentDb() {
        return contextHolder.get();
    }

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

MultiRoutingDataSource.java

package com.wumeng.dynamicmultidatabase.config;

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

/**
 * @author wumeng 2022/8/11 3:03 下午
 */
public class MultiRoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.getCurrentDb();
    }
}

配置文件 PersistenceConfiguration.java

package com.wumeng.dynamicmultidatabase.config;

import com.zaxxer.hikari.HikariDataSource;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wumeng 2022/8/11 3:13 下午
 */

@Configuration
//@Component
public class PersistenceConfiguration {

//    @Primary
    @Bean(name = "mainDataSource")
    @ConfigurationProperties("app.datasource.main")
    public DataSource mainDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "clientADataSource")
    @ConfigurationProperties("app.datasource.clienta")
    public DataSource clientADataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Bean(name = "clientBDataSource")
    @ConfigurationProperties("app.datasource.clientb")
    public DataSource clientBDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @Primary
    @Bean(name = "multiRoutingDataSource")
    public DataSource multiRoutingDataSource() {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.MAIN, mainDataSource());
        targetDataSources.put(DBTypeEnum.CLIENT_A, clientADataSource());
        targetDataSources.put(DBTypeEnum.CLIENT_B, clientBDataSource());
        MultiRoutingDataSource multiRoutingDataSource = new MultiRoutingDataSource();
        multiRoutingDataSource.setDefaultTargetDataSource(mainDataSource());
        multiRoutingDataSource.setTargetDataSources(targetDataSources);
        return multiRoutingDataSource;
    }

}

application.properties
server.port=8081

# mapper
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
# 数据库下划线自动转驼峰标示关闭
mybatis-plus.configuration.map-underscore-to-camel-case=true
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

logging.level.com.wumeng.service.modules.report.dao=error


app.datasource.main.driver-class-name=com.mysql.cj.jdbc.Driver
app.datasource.main.jdbc-url=jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
app.datasource.main.username=root
app.datasource.main.password=12345678


app.datasource.clienta.driver-class-name=com.mysql.cj.jdbc.Driver
app.datasource.clienta.jdbc-url=jdbc:mysql://localhost:3306/testdb1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
app.datasource.clienta.username=root
app.datasource.clienta.password=12345678


app.datasource.clientb.driver-class-name=com.mysql.cj.jdbc.Driver
app.datasource.clientb.jdbc-url=jdbc:mysql://localhost:3306/testdb1?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
app.datasource.clientb.username=root
app.datasource.clientb.password=12345678

Mybatis 实体类代码

/modules/user/dao/user.java

package com.wumeng.dynamicmultidatabase.modules.user.dao;

import lombok.Data;

import java.sql.Timestamp;

/**
 * @author wumeng 2022/6/8 4:49 下午
 */
@Data
public class User {
    private Integer id;
    private String name;
    private Timestamp create_time;
}

UserMapper.java

package com.wumeng.dynamicmultidatabase.modules.user.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.wumeng.dynamicmultidatabase.modules.user.dao.User;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Component;

import java.sql.Timestamp;
import java.util.Map;


/**
 * @author wumeng 2022/6/8 5:18 下午
 */
@Component
public interface UserMapper extends BaseMapper<User> {
    @Insert("insert into User(id,name,create_time) values(#{id},#{name},#{create_time})")
    int add(User User);

    @Update("update User set name=#{name},create_time=#{create_time} where id=#{id}")
    int update(User User);

    //@Delete("delete from User where sno=#{sno}")
    void deleteById(int id);

    @Select("select * from User where id=#{id}")
    @Results(id = "User",value= {
            @Result(property = "id", column = "id", javaType = int.class),
            @Result(property = "name", column = "name", javaType = String.class),
            @Result(property = "create_time", column = "create_time", javaType = Timestamp.class)
    })
    User queryUserById(int id);

    User queryUserByState(String state);

    Map<String,Object> getDynamicUser(String tableName,String columns);

}

UserService.java

package com.wumeng.dynamicmultidatabase.modules.user.service;

import com.wumeng.dynamicmultidatabase.modules.user.dao.User;

/**
 * @author wumeng 2022/6/8 5:23 下午
 */
public interface UserService {
    int add(User user);
    int update(User user);
    void deleteById(int id);
    User queryUserById(int id);
}

UserServiceImpl.java

package com.wumeng.dynamicmultidatabase.modules.user.service.impl;

import com.wumeng.dynamicmultidatabase.modules.user.dao.User;
import com.wumeng.dynamicmultidatabase.modules.user.mapper.UserMapper;
import com.wumeng.dynamicmultidatabase.modules.user.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public int add(User user) {
        return userMapper.add(user);
    }

    @Override
    public int update(User user) {
        return userMapper.update(user);
    }

    @Override
    public void deleteById(int id) {
        userMapper.deleteById(id);
    }

    @Override
    public User queryUserById(int id) {
        return userMapper.queryUserById(id);
    }
}

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wumeng.dynamicmultidatabase.modules.user.mapper.UserMapper">

    <select id="deleteById" parameterType="java.lang.String">
        delete from student where id = #{id}
    </select>

    <select id="queryUserByState" resultType="java.util.List">
        SELECT * FROM User
        <where>
            <if test="state != null">
                and state = #{state}
            </if>
        </where>
    </select>

    <select id="getDynamicUser" resultType="java.util.Map" parameterType="java.lang.String" statementType="STATEMENT">
        select
            ${columns}
        from ${tableName}
    </select>


</mapper>

UserController.java接口

package com.wumeng.dynamicmultidatabase.modules.user.controller;

import com.wumeng.dynamicmultidatabase.config.DBContextHolder;
import com.wumeng.dynamicmultidatabase.config.DBTypeEnum;
import com.wumeng.dynamicmultidatabase.modules.user.dao.User;
import com.wumeng.dynamicmultidatabase.modules.user.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.sql.Timestamp;


/**
 * @author wumeng 2022/6/8 4:35 下午
 */
@RestController
@RequestMapping("/user")
public class UserController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());


    @Autowired
    private UserService userService;

    // query_user?id=1 -> main DB
    // query_user?id=1&client=client-a -> Client A DB
    // query_user?id=1&client=client-b -> Client B DB
    @RequestMapping(value = "/query_user", method = RequestMethod.GET)
    public User queryUserById(int id, String client) {

        switch (client) {
            case "client-a":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_A);
                break;
            case "client-b":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_B);
                break;
        }
        logger.info("查询id = " + id + " client = " + client);
        return this.userService.queryUserById(id);
    }

    @RequestMapping(value = "/deleteUser", method = RequestMethod.GET)
    public String deleteUserById(int id, String client) {
        switch (client) {
            case "client-a":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_A);
                break;
            case "client-b":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_B);
                break;
        }
        this.userService.deleteById(id);
        logger.info("删除id" + id);
        return "删除成功";
    }

    @GetMapping("/addUser/{id}")
    public String addUser(@PathVariable(value = "id") Integer id, @RequestParam(value = "name") String name, @RequestParam(value = "client") String client) {

        switch (client) {
            case "client-a":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_A);
                break;
            case "client-b":
                DBContextHolder.setCurrentDb(DBTypeEnum.CLIENT_B);
                break;
        }
        User user = new User();
        user.setName(name);
        user.setCreate_time(new Timestamp(System.currentTimeMillis()));
        int addCode = this.userService.add(user);
        logger.info("id " + id + "add code " + addCode);
        return "添加成功";
    }


}

运行项目,调用接口,就可以查看变化了。

http://localhost:8081/user/query_user?id=1&client=

{"id":1,"name":"小明","create_time":"2022-08-11T14:54:51.000+00:00"}

http://localhost:8081/user/query_user?id=1&client=client-a

{"id":1,"name":"小明1","create_time":"2022-08-11T14:54:51.000+00:00"}

http://localhost:8081/user/query_user?id=1&client=client-b

{"id":1,"name":"小明2","create_time":"2022-08-11T14:54:51.000+00:00"}

### Spring BootMyBatis Plus 实现多数据源配置 #### 添加依赖和配置文件 为了在Spring Boot项目中使用MyBatis Plus实现多数据源配置,首先需要添加必要的Maven依赖。这包括`spring-boot-starter-jdbc`, `mybatis-plus-boot-starter`以及其他可能需要用到的数据源连接池库,如HikariCP。 ```xml <dependencies> <!-- spring boot jdbc --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- mybatis plus starter --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatis.plus.version}</version> </dependency> <!-- 数据库驱动, 如MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- HikariCP 连接池 --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> </dependencies> ``` 接着,在`application.yml`或`application.properties`中定义各个数据源的具体参数[^1]。 对于YAML格式: ```yaml spring: datasource: master: url: jdbc:mysql://localhost:3306/db_master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/db_slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DataSource ``` #### 创建动态数据源类 创建一个名为DynamicDataSource的类用于管理多个数据源之间的切换逻辑,并将其注册到Spring容器内作为默认的数据源组件。 ```java @Configuration public class DataSourceConfig { @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean(name = "dynamicDataSource") public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceEnum.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceEnum.SLAVE.name(), slaveDataSource); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); // 设置候选数据源 dynamicDataSource.setDefaultTargetDataSource(masterDataSource); // 默认数据源 return dynamicDataSource; } } ``` 这里假设已经有一个枚举类型`DataSourceEnum`用来表示不同的数据源名称[^3]。 #### 编写自定义注解与AOP切面 为了让业务层能够方便地指定要使用的具体数据源,可以编写一个简单的自定义注解@DS并配合AOP技术实现在方法调用前自动设置当前线程上下文中应使用的数据源。 ```java // 自定义注解 DS.java import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @Documented public @interface DS { String value(); // 指定数据源key } // AOP 切面处理 AspectDSRouter.java @Component @Aspect @Slf4j public class AspectDSRouter { private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>(); @Around("@annotation(com.example.DS)") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature)point.getSignature(); Method method = signature.getMethod(); DS ds = method.getDeclaredAnnotation(DS.class); if(ds != null){ String originalDs = getDataSource(); setDataSource(ds.value()); try{ return point.proceed(); }finally { setDataSource(originalDs); } } return point.proceed(); } protected void setDataSource(String dsName){ log.debug("Switch to [" + dsName + "] data source"); contextHolder.set(dsName); } protected String getDataSource(){ return contextHolder.get(); } } ``` 最后一步是在全局范围内拦截SQL执行请求之前读取当前线程绑定的数据源标识符,并据此调整实际使用的物理连接对象[^4]。 通过上述步骤即可完成基本的多数据源配置工作。当然,根据项目的复杂度和个人需求还可以进一步优化和完善此方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WMSmile

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值