第八章springboot+mybatis+多数据源

     2022-03-20     389

关键词:

在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源。

代码结构:

技术分享

简要原理:

1)DatabaseType列出所有的数据源的key---key

2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法

3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用DatabaseContextHolder获取当前线程的DatabaseType

4)MyBatisConfig中生成2个数据源DataSource的bean---value

5)MyBatisConfig中将1)和4)组成的key-value对写入到DynamicDataSource动态数据源的targetDataSources属性(当然,同时也会设置2个数据源其中的一个为DynamicDataSource的defaultTargetDataSource属性中)

6)将DynamicDataSource作为primary数据源注入到SqlSessionFactory的dataSource属性中去,并且该dataSource作为transactionManager的入参来构造DataSourceTransactionManager

7)使用的时候,在dao层或service层先使用DatabaseContextHolder设置将要使用的数据源key,然后再调用mapper层进行相应的操作,建议放在dao层去做(当然也可以使用spring aop+自定注解去做)

注意:在mapper层进行操作的时候,会先调用determineCurrentLookupKey()方法获取一个数据源(获取数据源:先根据设置去targetDataSources中去找,若没有,则选择defaultTargetDataSource),之后在进行数据库操作。

 

1、假设有两个数据库,配置如下

application.properties

技术分享
1 #the first datasource
 2 jdbc.driverClassName = com.mysql.jdbc.Driver
 3 jdbc.url = jdbc:mysql://xxx:3306/mytestdb?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
 4 jdbc.username = root
 5 jdbc.password = 123
 6 
 7 #the second datasource
 8 jdbc2.driverClassName = com.mysql.jdbc.Driver
 9 jdbc2.url = jdbc:mysql://xxx:3306/mytestdb2?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
10 jdbc2.username = root
11 jdbc2.password = 123
View Code

说明:在之前的配置的基础上,只增加了上述的第二个数据源。

 

2、DatabaseType

技术分享
package com.xxx.firstboot.common.datasource;

/**
 * 列出所有的数据源key(常用数据库名称来命名)
 * 注意:
 * 1)这里数据源与数据库是一对一的
 * 2)DatabaseType中的变量名称就是数据库的名称
 */
public enum DatabaseType {
    mytestdb,mytestdb2
}
View Code

作用:列举数据源的key。

 

3、DatabaseContextHolder

技术分享
package com.xxx.firstboot.common.datasource;

/**
 * 作用:
 * 1、保存一个线程安全的DatabaseType容器
 */
public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
    
    public static void setDatabaseType(DatabaseType type){
        contextHolder.set(type);
    }
    
    public static DatabaseType getDatabaseType(){
        return contextHolder.get();
    }
}
View Code

作用:构建一个DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法

 

4、DynamicDataSource

技术分享
package com.xxx.firstboot.common.datasource;

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

/**
 * 动态数据源(需要继承AbstractRoutingDataSource)
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    protected Object determineCurrentLookupKey() {
        return DatabaseContextHolder.getDatabaseType();
    }
}
View Code

作用:使用DatabaseContextHolder获取当前线程的DatabaseType

 

5、MyBatisConfig

技术分享
package com.xxx.firstboot.common;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.xxx.firstboot.common.datasource.DatabaseType;
import com.xxx.firstboot.common.datasource.DynamicDataSource;

/**
 * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
 * 2)创建SqlSessionFactory 3)配置事务管理器,除非需要使用事务,否则不用配置
 */
@Configuration // 该注解类似于spring配置文件
@MapperScan(basePackages = "com.xxx.firstboot.mapper")
public class MyBatisConfig {

    @Autowired
    private Environment env;

    /**
     * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
     */
    @Bean
    public DataSource myTestDbDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("jdbc.driverClassName"));
        props.put("url", env.getProperty("jdbc.url"));
        props.put("username", env.getProperty("jdbc.username"));
        props.put("password", env.getProperty("jdbc.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean
    public DataSource myTestDb2DataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", env.getProperty("jdbc2.driverClassName"));
        props.put("url", env.getProperty("jdbc2.url"));
        props.put("username", env.getProperty("jdbc2.username"));
        props.put("password", env.getProperty("jdbc2.password"));
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource,
            @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.mytestdb, myTestDbDataSource);
        targetDataSources.put(DatabaseType.mytestdb2, myTestDb2DataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(myTestDbDataSource);// 默认的datasource设置为myTestDbDataSource

        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(ds);// 指定数据源(这个必须有,否则报错)
        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
        fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
        fb.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//

        return fb.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

}
View Code

作用:

  • 通过读取application.properties文件生成两个数据源(myTestDbDataSource、myTestDb2DataSource)
  • 使用以上生成的两个数据源构造动态数据源dataSource
    • @Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@Autowire注解报错(一般用于多数据源的情况下)
    • @Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个DataSource类型的实例,需要指定名称注入)
    • @Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
  • 通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)

 

6、使用

ShopMapper:

技术分享
package com.xxx.firstboot.mapper;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;

import com.xxx.firstboot.domain.Shop;

public interface ShopMapper {

    @Select("SELECT * FROM t_shop WHERE id = #{id}")
    @Results(value = { @Result(id = true, column = "id", property = "id"),
                       @Result(column = "shop_name", property = "shopName") })
    public Shop getShop(@Param("id") int id);

}
View Code

ShopDao:

技术分享
package com.xxx.firstboot.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.xxx.firstboot.common.datasource.DatabaseContextHolder;
import com.xxx.firstboot.common.datasource.DatabaseType;
import com.xxx.firstboot.domain.Shop;
import com.xxx.firstboot.mapper.ShopMapper;

@Repository
public class ShopDao {
    @Autowired
    private ShopMapper mapper;

    /**
     * 获取shop
     */
    public Shop getShop(int id) {
        DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2);
        return mapper.getShop(id);
    }
}
View Code

注意:首先设置了数据源的key,然后调用mapper(在mapper中会首先根据该key从动态数据源中查询出相应的数据源,之后取出连接进行数据库操作)

ShopService:

技术分享
package com.xxx.firstboot.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.xxx.firstboot.dao.ShopDao;
import com.xxx.firstboot.domain.Shop;

@Service
public class ShopService {

    @Autowired
    private ShopDao dao;

    public Shop getShop(int id) {
        return dao.getShop(id);
    }
}
View Code

ShopController:

技术分享
package com.xxx.firstboot.web;

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

import com.xxx.firstboot.domain.Shop;
import com.xxx.firstboot.service.ShopService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping("/shop")
@Api("shopController相关api")
public class ShopController {

    @Autowired
    private ShopService service;

    @ApiOperation("获取shop信息,测试多数据源")
    @RequestMapping(value = "/getShop", method = RequestMethod.GET)
    public Shop getShop(@RequestParam("id") int id) {
        return service.getShop(id);
    }

}
View Code

补:其实DatabaseContextHolder和DynamicDataSource完全可以合为一个类

 

参考:

http://www.cnblogs.com/lzrabbit/p/3750803.html

 

遗留:在实际开发中,一个dao类只会用到一个数据源,如果dao类中的方法很多的话,每一个方法前边都要添加一个设置数据源的一句话,代码有些冗余,可以使用AOP切面。

 

springboot+mybatis+druid配置多数据源(mysql+postgre)

springboot+mybatis+Druid配置多数据源(mysql+postgre)引入pom依赖设置application多数据源config配置db1config配置(主数据库配置)db2config配置(其他数据库)事务处理mapper层springboot+mybatis+Druid配置多数据源(mysql+postgre)参考资料:第八章springboot+mybatis+... 查看详情

springboot第八章springboot打包(代码片段)

第八章SpringBoot打包SpringBoot可以打包为war或jar文件。以两种方式发布应用。8.1SpringBoot打包为war使用SpringBoot的maven插件将Springboot程序打成war包,单独部署在tomcat中运行(上线部署常用)。1.创建了一个jsp应用2.修改pom.xml... 查看详情

python第八章:多任务--小白博客(代码片段)

多线程threading多线程特点:#线程的并发是利用cpu上下文的切换(是并发,不是并行)#多线程执行的顺序是无序的#多线程共享全局变量#线程是继承在进程里的,没有进程就没有线程#GIL全局解释器锁#只要在进行耗时的IO操作的时... 查看详情

第八章:手工建库

管理多组用户环境多租户架构使Oracle数据库可以作为多租户容器数据库(CDB)运行,该数据库包括零个,一个或多个客户创建的可插拔数据库(PDB);一、创建和配置CDB#######createdatabasestep1>确定ORACLE_SIDexportORACLE_SID=mynewdbstep2>... 查看详情

第八章:数组

第八章:数组数组的概述什么是数组数组是一个变量,存储在相同数据类型的变量的一组类型。数组的基本要素标识符:数组的名称,用于区分不同数组。数组元素:向数组中存放的数据元素下标:对元素进行编号,从0开始,... 查看详情

第八章

元信息多表关系反向查找ORM操作进阶操作其它操作高效率关联表models自带验证models内置钩子from,choicesForm类From内置钩子验证obj.is_valid()错误信息1,元信息classUserInfo(models.Model):nid=models.AutoField(primary_key=True)username=models.CharField(max_leng 查看详情

第八章:集合

第八章:集合 数组和集合的区别数组:一旦声明了数组长度无法改变,只能保持指定的数据类型,元素的数据类型必须相同,不能混排。集合:长度和动态扩容,元素类型可以是任何类型,可以混合使用。 Collection接口List接口List接... 查看详情

第八章

一、       什么是数组及其作用?定义:具有相同数据类型的一个集合作用:存储连续的具有相同类型的数据二、       java中如何声明和定义数组l 声明和定义的语法: &... 查看详情

数据结构——第八章排序

基本算法如下: 1.插入排序 直接插入排序折半插入排序shell排序 2.交换排序 冒泡排序快速排序 3.选择排序 简单选择排序树形选择排序堆排序 4.归并排序 二路归并排序多路归并排序 5.基数排序&nb... 查看详情

数据结构第八章小结

数据结构第八章小结一、知识点1、基本概念排序:按关键字非递增或非递减顺序对一组记录重新排序的操作。(1)内部排序与外部排序。内部排序:整个排序过程不需要访问外存便能完成外部排序:参加排序的记录数量很大,... 查看详情

数据结构-第八章学习小结

 第八章的内容是排序,看上去很简单,实际上它有很多不同的分类。我觉得全章最难的部分就是算法的理解和时间效率(分最好情况和最坏情况)的分析上。 首先排序整体上可以分成两类:内部排序和外部排序。顾名思... 查看详情

第八章网络的时代—网络开发

8.5轻量级的数据交换格式—JSON8.5.1客户端与服务器端的数据交互在Android应用开发中,尤其是网络应用的开发,我们经常需要从网络上获取数据,而不仅仅从本地数据库或者本地文件中取得数据,这个时候就涉及到客户端与服务... 查看详情

数据结构第八章:栈与队列

一、栈        1、概念              数据的插入和删除都是在同一端,不能在其他任何位置,这种逻辑结构称之为栈。        2、特性              后进先出。或者说先进后出        3、分类 ... 查看详情

数据结构第八章学习总结

一、本章知识思维导图  二、第八章知识小结1.插入排序(1)直接插入排序对于待排序列中的元素,在已排序列中顺序查找插入位置,将待排元素插入并移动。  (2)折半插入排序基于直接插入排序,查找插入位... 查看详情

文件系统管理(第八章)

1.linux文件系统类型1.1日志文件系统 ext2及之前的文件系统由于是通过索引节点表来关联硬盘上的数据块,所以如果数据正在写入时断电或系统崩溃很可能导致当前的文件系统崩溃,为了避免这种情况,在ext3开始的文件系统支... 查看详情

第八章java中的并发工具类(代码片段)

等待多线程完成的CountDownLatchcountDownLatch允许一个或多个线程等待其他线程完成操作。publicclassCountDownLatchTeststaticCountDownLatchcountDownLatch=newCountDownLatch(2);publicstaticvoidmain(String[]args)throwsInterruptedExcepti 查看详情

第八章网络的时代—网络开发

8.5轻量级的数据交换格式—JSON8.5.1client与server端的数据交互在Android应用开发中,尤其是网络应用的开发,我们常常须要从网络上获取数据,而不只从本地数据库或者本地文件里取得数据,这个时候就涉及到client与server端的数据... 查看详情

第八章学习小结(代码片段)

第八章数据结构小结数据结构这一章主要讲的是关于排序的各种方法,由于不同应用对于时间复杂度与空间复杂度的要求不同,用的排序方法也不同,主要的排序方法有插入排序,交换排序,选择排序与归并排序,其中插入排序... 查看详情