详解Spring Boot + Mybatis 实现动态数据源

动态数据源

在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库。又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据源方案进行解决。接下来,我们就来讲解如何实现动态数据源,以及在过程中剖析动态数据源背后的实现原理。

实现案例

本教程案例基于 Spring Boot + Mybatis + MySQL 实现。

数据库设计

首先需要安装好MySQL数据库,新建数据库 example,创建example表,用来测试数据源,SQL脚本如下:

CREATE TABLE `example` (
 `pk` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
 `message` varchar(100) NOT NULL,
 `create_time` datetime NOT NULL COMMENT '创建时间',
 `modify_time` datetime DEFAULT NULL COMMENT '生效时间',
 PRIMARY KEY (`pk`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='测试用例表'

添加依赖

添加Spring Boot,Spring Aop,Mybatis,MySQL相关依赖。

pom.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>1.3.1</version>
  </dependency>
  <!-- spring aop -->
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  <dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.8</version>
  </dependency>

自定义配置文件

新建自定义配置文件resource/config/mysql/db.properties,添加数据源:

#数据库设置
spring.datasource.example.jdbc-url=jdbc:mysql://localhost:3306/example?characterEncoding=UTF-8
spring.datasource.example.username=root
spring.datasource.example.password=123456
spring.datasource.example.driver-class-name=com.mysql.jdbc.Driver

启动类

启动类添加 exclude = {DataSourceAutoConfiguration.class}, 以禁用数据源默认自动配置。

数据源默认自动配置会读取 spring.datasource.* 的属性创建数据源,所以要禁用以进行定制。

DynamicDatasourceApplication.java:

package com.main.example.dynamic.datasource;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class
})
public class DynamicDatasourceApplication {

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

}

数据源配置类

创建一个数据源配置类,主要做以下几件事情:

1. 配置 dao,model(bean),xml mapper文件的扫描路径。

2. 注入数据源配置属性,创建数据源。

3. 创建一个动态数据源,装入数据源。

4. 将动态数据源设置到SQL会话工厂和事务管理器。

如此,当进行数据库操作时,就会通过我们创建的动态数据源去获取要操作的数据源了。

DbSourceConfig.java:

package com.main.example.config.dao;

import com.main.example.common.DataEnum;
import com.main.example.common.DynamicDataSource;
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.PropertySource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

//数据库配置统一在config/mysql/db.properties中
@Configuration
@PropertySource(value = "classpath:config/mysql/db.properties")
public class DbSourceConfig {
  private String typeAliasesPackage = "com.main.example.bean.**.*";

  @Bean(name = "exampleDataSource")
  @ConfigurationProperties(prefix = "spring.datasource.example")
  public DataSource exampleDataSource() {
    return DataSourceBuilder.create().build();
  }

  /*
   * 动态数据源
   * dbMap中存放数据源名称与数据源实例,数据源名称存于DataEnum.DbSource中
   * setDefaultTargetDataSource方法设置默认数据源
   */
  @Bean(name = "dynamicDataSource")
  public DataSource dynamicDataSource() {
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    //配置多数据源
    Map<Object, Object> dbMap = new HashMap();
    dbMap.put(DataEnum.DbSource.example.getName(), exampleDataSource());
    dynamicDataSource.setTargetDataSources(dbMap);

    // 设置默认数据源
    dynamicDataSource.setDefaultTargetDataSource(exampleDataSource());

    return dynamicDataSource;
  }

  /*
   * 数据库连接会话工厂
   * 将动态数据源赋给工厂
   * mapper存于resources/mapper目录下
   * 默认bean存于com.main.example.bean包或子包下,也可直接在mapper中指定
   */
  @Bean(name = "sqlSessionFactory")
  public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(dynamicDataSource());
    sqlSessionFactory.setTypeAliasesPackage(typeAliasesPackage); //扫描bean
    PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    sqlSessionFactory.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));  // 扫描映射文件

    return sqlSessionFactory;
  }

  @Bean
  public PlatformTransactionManager transactionManager() {
    // 配置事务管理, 使用事务时在方法头部添加@Transactional注解即可
    return new DataSourceTransactionManager(dynamicDataSource());
  }
}

动态数据源类

我们上一步把这个动态数据源设置到了SQL会话工厂和事务管理器,这样在操作数据库时就会通过动态数据源类来获取要操作的数据源了。

动态数据源类集成了Spring提供的AbstractRoutingDataSource类,AbstractRoutingDataSource 中获取数据源的方法就是 determineTargetDataSource,而此方法又通过 determineCurrentLookupKey 方法获取查询数据源的key。

所以如果我们需要动态切换数据源,就可以通过以下两种方式定制:

1. 覆写 determineCurrentLookupKey 方法

通过覆写 determineCurrentLookupKey 方法,从一个自定义的 DbSourceContext.getDbSource() 获取数据源key值,这样在我们想动态切换数据源的时候,只要通过  DbSourceContext.setDbSource(key)  的方式就可以动态改变数据源了。这种方式要求在获取数据源之前,要先初始化各个数据源到 DbSourceContext 中,我们案例就是采用这种方式实现的,所以要将数据源都事先初始化到DynamicDataSource 中。

2. 可以通过覆写 determineTargetDataSource,因为数据源就是在这个方法创建并返回的,所以这种方式就比较自由了,支持到任何你希望的地方读取数据源信息,只要最终返回一个 DataSource 的实现类即可。比如你可以到数据库、本地文件、网络接口等方式读取到数据源信息然后返回相应的数据源对象就可以了。

DynamicDataSource.java:

package com.main.example.common;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

  @Override
  protected Object determineCurrentLookupKey() {
    return DbSourceContext.getDbSource();
  }

}

数据源上下文

动态数据源的切换主要是通过调用这个类的方法来完成的。在任何想要进行切换数据源的时候都可以通过调用这个类的方法实现切换。比如系统登录时,根据用户信息调用这个类的数据源切换方法切换到用户对应的数据库。完整代码如下:

DbSourceContext.java:

package com.main.example.common;

import org.apache.log4j.Logger;

public class DbSourceContext {
  private static Logger logger = Logger.getLogger(DbSourceContext.class);

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

  public static void setDbSource(String source) {
    logger.debug("set source ====>" + source);
    dbContext.set(source);
  }

  public static String getDbSource() {
    logger.debug("get source ====>" + dbContext.get());
    return dbContext.get();
  }

  public static void clearDbSource() {
    dbContext.remove();
  }
}

注解式数据源

到这里,在任何想要动态切换数据源的时候,只要调用DbSourceContext.setDbSource(key)  就可以完成了。

接下来我们实现通过注解的方式来进行数据源的切换,原理就是添加注解(如@DbSource(value="example")),然后实现注解切面进行数据源切换。

创建一个动态数据源注解,拥有一个value值,用于标识要切换的数据源的key。

DbSource.java:

package com.main.example.config.dao;

import java.lang.annotation.*;

/**
 * 动态数据源注解
 * @author
 * @date April 12, 2019
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbSource {
  /**
   * 数据源key值
   * @return
   */
  String value();
}

创建一个AOP切面,拦截带 @DataSource 注解的方法,在方法执行前切换至目标数据源,执行完成后恢复到默认数据源。

DynamicDataSourceAspect.java:

package com.main.example.config.dao;

import com.main.example.common.DbSourceContext;
import org.apache.log4j.Logger;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 动态数据源切换处理器
 * @author linzhibao
 * @date April 12, 2019
 */
@Aspect
@Order(-1) // 该切面应当先于 @Transactional 执行
@Component
public class DynamicDataSourceAspect {
  private static Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);
  /**
   * 切换数据源
   * @param point
   * @param dbSource
   */
  //@Before("@annotation(dbSource)") 注解在对应方法,拦截有@DbSource的方法
  //注解在类对象,拦截有@DbSource类下所有的方法
  @Before("@within(dbSource)")
  public void switchDataSource(JoinPoint point, DbSource dbSource) {
      // 切换数据源
      DbSourceContext.setDbSource(dbSource.value());
  }

  /**
   * 重置数据源
   * @param point
   * @param dbSource
   */
  //注解在类对象,拦截有@DbSource类下所有的方法
  @After("@within(dbSource)")
  public void restoreDataSource(JoinPoint point, DbSource dbSource) {
    // 将数据源置为默认数据源
    DbSourceContext.clearDbSource();
  }
}

到这里,动态数据源相关的处理代码就完成了。

编写用户业务代码

接下来编写用户查询业务代码,用来进行测试,Dao层只需添加一个查询接口即可。

ExampleDao.java:

package com.main.example.dao;

import com.main.example.common.DataEnum;
import com.main.example.config.dao.DbSource;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

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

@Component("exampleDao")
//切换数据源注解,以DataEnum.DbSource中的值为准
@DbSource("example")
public class ExampleDao extends DaoBase {
  private static final String MAPPER_NAME_SPACE = "com.main.example.dao.ExampleMapper";

  public List<String> selectAllMessages() {
    return selectList(MAPPER_NAME_SPACE, "selectAllMessages");
  }
}

Controler代码:

TestExampleDao.java:

package com.main.example.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class TestExampleDao {
  @Autowired
  ExampleDao exampleDao;

  @RequestMapping(value = "/test/example")
  public List<String> selectAllMessages() {
    try {
      List<String> ldata = exampleDao.selectAllMessages();
      if(ldata == null){System.out.println("*********it is null.***********");return null;}
      for(String d : ldata) {
        System.out.println(d);
      }
      return ldata;
    }catch(Exception e) {
      e.printStackTrace();
    }

    return new ArrayList<>();
  }
}

ExampleMapper.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.main.example.dao.ExampleMapper">
  <select id="selectAllMessages" resultType="java.lang.String">
    SELECT
    message
    FROM example
  </select>

</mapper>

测试效果

启动系统,访问 http://localhost:80/test/example">http://localhost:80/test/example,分别测试两个接口,成功返回数据。

可能遇到的问题

1.报错:java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName

原因:

spring boot从1.X升级到2.X版本之后,一些配置及用法有了变化,如果不小心就会碰到“jdbcUrl is required with driverClassName.”的错误

解决方法:

在1.0 配置数据源的过程中主要是写成:spring.datasource.url 和spring.datasource.driverClassName。

而在2.0升级之后需要变更成:spring.datasource.jdbc-url和spring.datasource.driver-class-name即可解决!

 2.自定义配置文件

自定义配置文件需要在指定配置类上加上@PropertySource标签,例如:

@PropertySource(value = "classpath:config/mysql/db.properties")

若是作用于配置类中的方法,则在方法上加上@ConfigurationProperties,例如:

@ConfigurationProperties(prefix = "spring.datasource.example")

配置项前缀为spring.datasource.example

若是作用于配置类上,则在类上加上@ConfigurationProperties(同上),并且在启动类上加上@EnableConfigurationProperties(XXX.class)

3.多数据源

需要在启动类上取消自动装载数据源,如:

@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class
})

以上所述是小编给大家介绍的Spring Boot + Mybatis 实现动态数据源详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解SpringBoot和Mybatis配置多数据源

    目前业界操作数据库的框架一般是 Mybatis,但在很多业务场景下,我们需要在一个工程里配置多个数据源来实现业务逻辑.在SpringBoot中也可以实现多数据源并配合Mybatis框架编写xml文件来执行SQL.在SpringBoot中,配置多数据源的方式十分便捷, 下面开始上代码: 在pom.xml文件中需要添加一些依赖 <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.b

  • springboot + mybatis配置多数据源示例

    在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)DatabaseType列出所有的数据源的key---key 2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法 3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用Da

  • 详解springboot+mybatis多数据源最简解决方案

    说起多数据源,一般都来解决那些问题呢,主从模式或者业务比较复杂需要连接不同的分库来支持业务.我们项目是后者的模式,网上找了很多,大都是根据jpa来做多数据源解决方案,要不就是老的spring多数据源解决方案,还有的是利用aop动态切换,感觉有点小复杂,其实我只是想找一个简单的多数据支持而已,折腾了两个小时整理出来,供大家参考. 废话不多说直接上代码吧 配置文件 pom包就不贴了比较简单该依赖的就依赖,主要是数据库这边的配置: mybatis.config-locations=classpath:

  • springboot 动态数据源的实现方法(Mybatis+Druid)

    Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingDataSource实现动态路由.今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨. 实现方式 方式1的实现(核心代码): @Configuration @MapperScan(basePackages = "com.goofly.test1", sqlSessionTemplateRef = "test1SqlSess

  • 详解Spring Boot + Mybatis 实现动态数据源

    动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据源方案进行解决.接下来,我们就来讲解如何实现动态数据源,以及在过程中剖析动态数据源背后的实现原理. 实现案例 本教程案例基于 Spring Boot + Mybatis + MySQL 实现. 数据库设计 首先需要安装好MySQL数据库,新建数据库 example,创建example表,用来测

  • Spring Boot + Mybatis 实现动态数据源案例分析

    动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据源方案进行解决.接下来,我们就来讲解如何实现动态数据源,以及在过程中剖析动态数据源背后的实现原理. 实现案例 本教程案例基于 Spring Boot + Mybatis + MySQL 实现. 数据库设计 首先需要安装好MySQL数据库,新建数据库 master,slave,分别创建用户表,用

  • 详解spring boot mybatis全注解化

    本文重点给大家介绍spring boot mybatis 注解化的实例代码,具体内容大家参考下本文: pom.xml <!-- 引入mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version

  • 详解spring boot jpa整合QueryDSL来简化复杂操作

    前言 使用过spring data jpa的同学,都很清楚,对于复杂的sql查询,处理起来还是比较复杂的,而本文中的QueryDSL就是用来简化JPA操作的. Querydsl定义了一种常用的静态类型语法,用于在持久域模型数据之上进行查询.JDO和JPA是Querydsl的主要集成技术.本文旨在介绍如何使用Querydsl与JPA组合使用.JPA的Querydsl是JPQL和Criteria查询的替代方法.QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查

  • 详解Spring Boot中使用Flyway来管理数据库版本

    如果没有读过上面内容的读者,有兴趣的可以一阅.在上面的使用JdbcTemplate一文中,主要通过spring提供的JdbcTemplate实现对用户表的增删改查操作.在实现这个例子的时候,我们事先在MySQL中创建了用户表.创建表的过程我们在实际开发系统的时候会经常使用,但是一直有一个问题存在,由于一个系统的程序版本通过git得到了很好的版本控制,而数据库结构并没有,即使我们通过Git进行了语句的版本化,那么在各个环境的数据库中如何做好版本管理呢?下面我们就通过本文来学习一下在Spring B

  • 详解Spring Boot使用系统参数表提升系统的灵活性

    目录 一.使用系统参数表的好处 二.系统参数表的表结构 三.系统参数表在项目中的使用 3.1.Entity类 3.2.Dao类 3.3.Service类 3.4.ServiceImpl类 3.5.全局配置服务类 3.6.启动时加载 3.7.在服务实现类中访问系统参数 一.使用系统参数表的好处 ​​以数据库表形式存储的系统参数表比配置文件(.properties文件或.yaml文件)要更灵活,因为无需重启系统就可以动态更新. ​系统参数表可用于存储下列数据: 表字段枚举值,如下列字段: `ques

  • 详解Spring与Mybatis的整合方法(基于Eclipse的搭建)

    项目工程总览: 项目路径建的包不是唯一,只要之后配置的路径映射正确即可 Emp.java <properties> <spring.version>5.1.5.RELEASE</spring.version> <mybatis.version>3.4.6</mybatis.version> <log4j.version>1.2.17</log4j.version> </properties> <depen

  • 详解Spring与Mybatis整合方法(基于IDEA中的Maven整合)

    项目结构 项目路径可以自己定义,只要路径映射正确就可以 pom.xml <properties> <spring.version>5.1.5.RELEASE</spring.version> <mybatis.version>3.4.6</mybatis.version> <log4j.version>1.2.17</log4j.version> </properties> <dependencies&g

  • 详解spring boot starter redis配置文件

    spring-boot-starter-Redis主要是通过配置RedisConnectionFactory中的相关参数去实现连接redis service. RedisConnectionFactory是一个接口,有如下4个具体的实现类,我们通常使用的是JedisConnectionFactory. 在spring boot的配置文件中redis的基本配置如下: # Redis服务器地址 spring.redis.host=192.168.0.58 # Redis服务器连接端口 spring.

  • 实例详解Spring Boot实战之Redis缓存登录验证码

    本章简单介绍redis的配置及使用方法,本文示例代码在前面代码的基础上进行修改添加,实现了使用redis进行缓存验证码,以及校验验证码的过程. 1.添加依赖库(添加redis库,以及第三方的验证码库) <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency&

随机推荐