Spring Boot多数据源配置/读写分离(Druid+MyBatisPlus)

SpringBoot配置多数据源/读写分离整体步骤

  • Jar包引入 spring boot + druid + mybatis plus(多数据源 + 分页
  • application.yml配置多数据源及mybatis plus mapper配置
  • 新建动态数据源DynamicDataSource(继承AbstractRoutingDataSource),ThreadLocal中获取当前使用哪个数据源
  • 自定义多个Datasource,枚举类对应各个DataSource,及事务集成
  • 编写AOP,增强Mapper方法的调用处,切换具体数据源
  • 测试,数据源切换、插入、分页查询等

项目源码

https://github.com/zc-zangchao/multiple-data-source

POM配置

版本

        <springboot.version>2.2.0.RELEASE</springboot.version>
        <druid.version>1.1.9</druid.version>
        <oracle.version>12.1.0.1.0</oracle.version>
        <mysql-connector.version>8.0.18</mysql-connector.version>
        <mybatis-plus.version>2.1.9</mybatis-plus.version>
        <log4jdbc.version>1.2</log4jdbc.version>

具体jar

			<!--springboot-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${springboot.version}</version>
            </dependency>
            
            <!--druid-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <!-- 多数据源时使用mybatis-plus已经包含了mybatis+pageHelper,所以不需要再引用 mybatis + pageHelper -->
            <!-- 多数据源配置支撑 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>

            <!-- log4jdbc jdbc日志增强 -->
            <dependency>
                <groupId>com.googlecode.log4jdbc</groupId>
                <artifactId>log4jdbc</artifactId>
                <version>${log4jdbc.version}</version>
            </dependency>

            <!-- db connect -->
            <!-- oracle -->
            <dependency>
                <groupId>com.oracle</groupId>
                <artifactId>ojdbc7</artifactId>
                <version>${oracle.version}</version>
            </dependency>

            <!-- mysql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql-connector.version}</version>
            </dependency>

yml配置

# mybatis mapper locations
mybatis-plus:
  mapper-locations: classpath:mapper/*Mapper.xml

spring:
  datasource:
    druid:
      oracle:
        url: jdbc:oracle:thin:@127.0.0.1:1521/db
        username: root
        password: root
        driver-class-name: oracle.jdbc.driver.OracleDriver
        max-active: 10
        max-wait: 10000
      mysql:
        # tcpRcvBuf/tcpSndBuf 缓冲区参数 rewriteBatchedStatements batchUpdate参数
        url: jdbc:log4jdbc:mysql://127.0.0.1:3306/mydb?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&tcpRcvBuf=1048576&tcpSndBuf=1048576&socketTimeout=180000&rewriteBatchedStatements=true&autoReconnect=true
        username: root
        password: root
        # 开源 SQL 日志框架,在大多数情况下极大改善了可读性及调试工作
        driver-class-name: net.sf.log4jdbc.DriverSpy
        max-active: 10
        max-wait: 600000
        # sql监控
        filters: stat
        # 检测池里连接的可用性 开启影响性能 默认false
        test-on-borrow: false
        # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 默认true
        test-while-idle: true
        # 每30秒运行一次空闲连接回收器
        time-between-eviction-runs-millis: 30000
        # 检测语句
        validation-query: "select 1"
      mysql-backup:
        # tcpRcvBuf/tcpSndBuf 缓冲区参数 rewriteBatchedStatements batchUpdate参数
        url: jdbc:mysql://127.0.0.1:3306/mydb?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=false
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
        max-active: 10
        max-wait: 600000
        # sql监控
        filters: stat
        # 检测池里连接的可用性 开启影响性能 默认false
        test-on-borrow: false
        # 指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除 默认true
        test-while-idle: true
        # 每30秒运行一次空闲连接回收器
        time-between-eviction-runs-millis: 30000
        # 检测语句
        validation-query: "select 1"


动态数据源配置

package com.springboot.demo.service.datasource;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

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

package com.springboot.demo.service.datasource;

public class DataSourceContextHolder {
    private DataSourceContextHolder(){}

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

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

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

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

DataSource及事务配置

package com.springboot.demo.service.datasource.type;

public enum EnumDataSourceType {
    ORACLE,

    MYSQL,

    MYSQL_BACKUP;
}

package com.springboot.demo.service.datasource;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.MybatisConfiguration;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.spring.boot.starter.MybatisPlusProperties;
import com.baomidou.mybatisplus.spring.boot.starter.SpringBootVFS;
import com.springboot.demo.service.datasource.type.EnumDataSourceType;
import org.apache.ibatis.plugin.Interceptor;
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.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

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

@Configuration
public class DataSourceConfig {

    @Resource
    private MybatisPlusProperties properties;

    @Bean(name = "dataSourceOracle")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.druid.oracle")
    public DataSource oracleDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceMysql")
    @ConfigurationProperties(prefix = "spring.datasource.druid.mysql")
    public DataSource mysqlDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dataSourceMysqlBackup")
    @ConfigurationProperties(prefix = "spring.datasource.druid.mysql-backup")
    public DataSource mysqlBackupDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(oracleDataSource());
        // 配置多数据源
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(EnumDataSourceType.ORACLE.name(), oracleDataSource());
        dataSourceMap.put(EnumDataSourceType.MYSQL.name(), mysqlDataSource());
        dataSourceMap.put(EnumDataSourceType.MYSQL_BACKUP.name(), mysqlBackupDataSource());
        dynamicDataSource.setTargetDataSources(dataSourceMap);
        return dynamicDataSource;
    }

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        // 开启 PageHelper 的支持
        paginationInterceptor.setLocalPage(true);
        return paginationInterceptor;
    }

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean() {

        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dynamicDataSource());
        // mybatis本身的核心库在springboot打包成jar后有个bug,无法完成别名的扫描
        factory.setVfs(SpringBootVFS.class);

        org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
        if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
            configuration = new MybatisConfiguration();
        }

        factory.setConfiguration(configuration);

        // 分页功能
        factory.setPlugins(new Interceptor[]{ paginationInterceptor()});

        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }

        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }

        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }

        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        return factory;
    }

//    @Bean
//    public SqlSessionFactory sqlSessionFactory() throws Exception {
//        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
//        sqlSessionFactory.setDataSource(dynamicDataSource());
//        sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*Mapper.xml"));
//
//        MybatisConfiguration configuration = new MybatisConfiguration();
//        configuration.setJdbcTypeForNull(JdbcType.NULL);
//        configuration.setMapUnderscoreToCamelCase(true);
//        configuration.setCacheEnabled(false);
//        sqlSessionFactory.setConfiguration(configuration);
//        sqlSessionFactory.setPlugins(new Interceptor[]{
//                paginationInterceptor() //添加分页功能
//        });
//
//        return sqlSessionFactory.getObject();
//    }

    /**
     * 配置@Transactional注解事务
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

}

AOP增强 切换数据源

package com.springboot.demo.service.datasource.annotation;

import com.springboot.demo.service.datasource.type.EnumDataSourceType;

import java.lang.annotation.*;

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {

    EnumDataSourceType value() default EnumDataSourceType.ORACLE;

}

package com.springboot.demo.service.datasource.aspect;

import com.springboot.demo.service.datasource.DataSourceContextHolder;
import com.springboot.demo.service.datasource.annotation.TargetDataSource;
import com.springboot.demo.service.datasource.type.EnumDataSourceType;
import lombok.extern.slf4j.Slf4j;
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
// 默认优先级 最后执行 可调整Order
public class DynamicDataSourceAspect {


    @Before("@annotation(targetDataSource)")
    public void before(JoinPoint point, TargetDataSource targetDataSource) {
        try {
            TargetDataSource annotationOfClass = point.getTarget().getClass().getAnnotation(TargetDataSource.class);
            String methodName = point.getSignature().getName();
            Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
            Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
            TargetDataSource methodAnnotation = method.getAnnotation(TargetDataSource.class);
            methodAnnotation = methodAnnotation == null ? annotationOfClass : methodAnnotation;
            EnumDataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() != null ? methodAnnotation.value() : EnumDataSourceType.ORACLE;
            DataSourceContextHolder.setDataSource(dataSourceType.name());
        } catch (NoSuchMethodException e) {
            log.warn("Aspect targetDataSource inspect exception.", e);
        }
    }

    @After("@annotation(targetDataSource)")
    public void after(JoinPoint point, TargetDataSource targetDataSource) {
        DataSourceContextHolder.clearDataSource();
    }
}

测试验证

验证数据源切换、事务、分页

package com.springboot.demo.web.service;

import com.baomidou.mybatisplus.plugins.pagination.PageHelper;
import com.springboot.demo.service.dao.domain.User;
import com.springboot.demo.service.dao.mapper.UserMapper;
import com.springboot.demo.service.datasource.annotation.TargetDataSource;
import com.springboot.demo.service.datasource.type.EnumDataSourceType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public boolean addUser(User user) {
        int result = userMapper.insert(user);
        if (result <= 0) {
            throw new RuntimeException("exception");
        }
        return result > 0;
    }

    @TargetDataSource(EnumDataSourceType.MYSQL_BACKUP)
    public User queryUserById(String userId) {
        return userMapper.selectByPrimaryKey(userId);
    }

    @TargetDataSource(EnumDataSourceType.MYSQL)
    public List<User> selectAll(int pageNum, int pageSize) {
        // 启动分页
        PageHelper.startPage(pageNum, pageSize);
        return userMapper.selectAll();
    }
}

package com.springboot.demo.web.controller;

import com.springboot.demo.service.dao.domain.User;
import com.springboot.demo.web.model.ResultInfo;
import com.springboot.demo.web.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.websocket.server.PathParam;
import java.util.List;

@RestController
@Slf4j
public class UserController {
    @Resource
    private UserService userService;

    @GetMapping("/user/query")
    public List<User> queryUsers(@PathParam("pageNum") int pageNum, @PathParam("pageSize") int pageSize) {
        log.info("Query users.");
        return userService.selectAll(pageNum, pageSize);
    }

    @GetMapping("/user/queryUserById/{userId}")
    public User queryUserById(@PathVariable String userId) {
        log.info("Query user by Id.");
        return userService.queryUserById(userId);
    }

    @PostMapping("/user/add")
    public ResultInfo addUser(@RequestBody User user) {
        log.info("Add user.");
        userService.addUser(user);
        return new ResultInfo();
    }
}



参考:
Spring Boot 整合 Durid数据库连接池
Spring Boot2.0配置Druid数据库连接池(单数据源、多数据源、数据监控)
使用springboot + druid + mybatisplus完成多数据源配置
数据连接池默认配置带来的坑testOnBorrow=false,cloes_wait 终于解决了
关于连接池参数testWhileIdle,testOnBorrow,testOnReturn的疑问
使用druid连接池带来的坑testOnBorrow=false
MySQL之rewriteBatchedStatements
使用log4jdbc更有效的记录java sql日志

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot应用中使用多数据可以实现分离,提高系统的性能和可用性,同时也可以满足不同业务场景下的需求。下面我们来介绍如何使用Spring Boot数据实现分离。 1. 添加依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> ``` 其中,spring-boot-starter-jdbc是Spring Boot提供的JDBC Starter,用于连接数据库。druid是阿里巴巴开数据库连接池。 2. 配置数据 在application.yml文件中添加数据配置: ``` spring: datasource: master: url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver ``` 其中,我们配置了两个数据:master和slave。分别连接了两个不同的数据库,用于实现分离。 3. 配置数据和事务管理器 在Spring Boot中,我们需要自己配置数据和事务管理器。可以在代码中通过@Primary和@Qualifier注解实现数据的动态切换。 ``` @Configuration public class DataSourceConfig { @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") @Primary public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DynamicDataSource.DataSourceType.MASTER, masterDataSource); targetDataSources.put(DynamicDataSource.DataSourceType.SLAVE, slaveDataSource); return new DynamicDataSource(masterDataSource, targetDataSources); } @Bean(name = "transactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } } ``` 其中,我们定义了DataSourceConfig配置类,用于配置数据和事务管理器。我们通过@Bean注解创建数据对象并取application.yml配置文件中的数据信息。然后通过@Primary注解指定默认的数据为Master数据。最后,我们创建了DynamicDataSource对象,用于动态切换数据,同时创建了事务管理器对象,用于管理事务。 4. 定义数据切换注解 我们可以通过自定义注解来实现动态切换数据。 ``` @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface TargetDataSource { DynamicDataSource.DataSourceType value() default DynamicDataSource.DataSourceType.MASTER; } ``` 其中,@TargetDataSource注解用于标记数据类型,我们通过value()方法指定数据类型,默认为Master数据。 5. 定义动态数据 我们可以通过继承AbstractRoutingDataSource类来实现动态数据的切换。 ``` public class DynamicDataSource extends AbstractRoutingDataSource { private DataSource masterDataSource; private Map<Object, Object> targetDataSources; public DynamicDataSource(DataSource masterDataSource, Map<Object, Object> targetDataSources) { this.masterDataSource = masterDataSource; this.targetDataSources = targetDataSources; super.setDefaultTargetDataSource(masterDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceType(); } public enum DataSourceType { MASTER, SLAVE } } ``` 其中,我们定义了DynamicDataSource类,继承了AbstractRoutingDataSource类。在DynamicDataSource类中,我们重了determineCurrentLookupKey()方法,根据当前线程的数据类型,返回对应的数据。我们通过枚举类型DataSourceType定义了Master和Slave两种数据类型。 6. 实现数据切换 我们可以通过AOP的方式,在方法执行前切换数据。 ``` @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.demo.annotation.TargetDataSource)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); TargetDataSource dataSource = signature.getMethod().getAnnotation(TargetDataSource.class); if (dataSource == null) { DataSourceContextHolder.setDataSourceType(DynamicDataSource.DataSourceType.MASTER); } else { DataSourceContextHolder.setDataSourceType(dataSource.value()); } try { return point.proceed(); } finally { DataSourceContextHolder.clearDataSourceType(); } } } ``` 其中,我们定义了DataSourceAspect切面类,用于切换数据。我们通过@Around注解标记了切点,在方法执行前,根据注解中指定的数据类型,切换数据。执行完毕后,再切换回默认的Master数据。 7. 编业务代码 最后,我们编业务代码,通过@TargetDataSource注解指定数据类型。 ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @TargetDataSource(DynamicDataSource.DataSourceType.MASTER) public void addUser(User user) { userMapper.addUser(user); } @Override @TargetDataSource(DynamicDataSource.DataSourceType.SLAVE) public User getUserById(int id) { return userMapper.getUserById(id); } } ``` 其中,我们定义了UserServiceImpl类,实现了UserService接口。在addUser()方法中,我们指定数据类型为Master数据,在getUserById()方法中,我们指定数据类型为Slave数据。 通过以上步骤,我们就可以实现Spring Boot数据分离了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值