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

代码结构:

简要原理:

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. 1 #the first datasource
  2. 2 jdbc.driverClassName = com.mysql.jdbc.Driver
  3. 3 jdbc.url = jdbc:mysql://xxx:3306/mytestdb?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
  4. 4 jdbc.username = root
  5. 5 jdbc.password = 123
  6. 6
  7. 7 #the second datasource
  8. 8 jdbc2.driverClassName = com.mysql.jdbc.Driver
  9. 9 jdbc2.url = jdbc:mysql://xxx:3306/mytestdb2?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
  10. 10 jdbc2.username = root
  11. 11 jdbc2.password = 123

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

2、DatabaseType

  1. package com.xxx.firstboot.common.datasource;
  2.  
  3. /**
  4. * 列出所有的数据源key(常用数据库名称来命名)
  5. * 注意:
  6. * 1)这里数据源与数据库是一对一的
  7. * 2)DatabaseType中的变量名称就是数据库的名称
  8. */
  9. public enum DatabaseType {
  10. mytestdb,mytestdb2
  11. }

作用:列举数据源的key。

3、DatabaseContextHolder

  1. package com.xxx.firstboot.common.datasource;
  2.  
  3. /**
  4. * 作用:
  5. * 1、保存一个线程安全的DatabaseType容器
  6. */
  7. public class DatabaseContextHolder {
  8. private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
  9.  
  10. public static void setDatabaseType(DatabaseType type){
  11. contextHolder.set(type);
  12. }
  13.  
  14. public static DatabaseType getDatabaseType(){
  15. return contextHolder.get();
  16. }
  17. }

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

4、DynamicDataSource

  1. package com.xxx.firstboot.common.datasource;
  2.  
  3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  4.  
  5. /**
  6. * 动态数据源(需要继承AbstractRoutingDataSource)
  7. */
  8. public class DynamicDataSource extends AbstractRoutingDataSource {
  9. protected Object determineCurrentLookupKey() {
  10. return DatabaseContextHolder.getDatabaseType();
  11. }
  12. }

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

5、MyBatisConfig

  1. package com.xxx.firstboot.common;
  2.  
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import java.util.Properties;
  6.  
  7. import javax.sql.DataSource;
  8.  
  9. import org.apache.ibatis.session.SqlSessionFactory;
  10. import org.mybatis.spring.SqlSessionFactoryBean;
  11. import org.mybatis.spring.annotation.MapperScan;
  12. import org.springframework.beans.factory.annotation.Autowired;
  13. import org.springframework.beans.factory.annotation.Qualifier;
  14. import org.springframework.context.annotation.Bean;
  15. import org.springframework.context.annotation.Configuration;
  16. import org.springframework.context.annotation.Primary;
  17. import org.springframework.core.env.Environment;
  18. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  19. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  20.  
  21. import com.alibaba.druid.pool.DruidDataSourceFactory;
  22. import com.xxx.firstboot.common.datasource.DatabaseType;
  23. import com.xxx.firstboot.common.datasource.DynamicDataSource;
  24.  
  25. /**
  26. * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
  27. * 2)创建SqlSessionFactory 3)配置事务管理器,除非需要使用事务,否则不用配置
  28. */
  29. @Configuration // 该注解类似于spring配置文件
  30. @MapperScan(basePackages = "com.xxx.firstboot.mapper")
  31. public class MyBatisConfig {
  32.  
  33. @Autowired
  34. private Environment env;
  35.  
  36. /**
  37. * 创建数据源(数据源的名称:方法名可以取为XXXDataSource(),XXX为数据库名称,该名称也就是数据源的名称)
  38. */
  39. @Bean
  40. public DataSource myTestDbDataSource() throws Exception {
  41. Properties props = new Properties();
  42. props.put("driverClassName", env.getProperty("jdbc.driverClassName"));
  43. props.put("url", env.getProperty("jdbc.url"));
  44. props.put("username", env.getProperty("jdbc.username"));
  45. props.put("password", env.getProperty("jdbc.password"));
  46. return DruidDataSourceFactory.createDataSource(props);
  47. }
  48.  
  49. @Bean
  50. public DataSource myTestDb2DataSource() throws Exception {
  51. Properties props = new Properties();
  52. props.put("driverClassName", env.getProperty("jdbc2.driverClassName"));
  53. props.put("url", env.getProperty("jdbc2.url"));
  54. props.put("username", env.getProperty("jdbc2.username"));
  55. props.put("password", env.getProperty("jdbc2.password"));
  56. return DruidDataSourceFactory.createDataSource(props);
  57. }
  58.  
  59. /**
  60. * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
  61. * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
  62. */
  63. @Bean
  64. @Primary
  65. public DynamicDataSource dataSource(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource,
  66. @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) {
  67. Map<Object, Object> targetDataSources = new HashMap<>();
  68. targetDataSources.put(DatabaseType.mytestdb, myTestDbDataSource);
  69. targetDataSources.put(DatabaseType.mytestdb2, myTestDb2DataSource);
  70.  
  71. DynamicDataSource dataSource = new DynamicDataSource();
  72. dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
  73. dataSource.setDefaultTargetDataSource(myTestDbDataSource);// 默认的datasource设置为myTestDbDataSource
  74.  
  75. return dataSource;
  76. }
  77.  
  78. /**
  79. * 根据数据源创建SqlSessionFactory
  80. */
  81. @Bean
  82. public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception {
  83. SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
  84. fb.setDataSource(ds);// 指定数据源(这个必须有,否则报错)
  85. // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
  86. fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));// 指定基包
  87. fb.setMapperLocations(
  88. new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));//
  89.  
  90. return fb.getObject();
  91. }
  92.  
  93. /**
  94. * 配置事务管理器
  95. */
  96. @Bean
  97. public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
  98. return new DataSourceTransactionManager(dataSource);
  99. }
  100.  
  101. }

作用:

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

6、使用

ShopMapper:

  1. package com.xxx.firstboot.mapper;
  2.  
  3. import org.apache.ibatis.annotations.Param;
  4. import org.apache.ibatis.annotations.Result;
  5. import org.apache.ibatis.annotations.Results;
  6. import org.apache.ibatis.annotations.Select;
  7.  
  8. import com.xxx.firstboot.domain.Shop;
  9.  
  10. public interface ShopMapper {
  11.  
  12. @Select("SELECT * FROM t_shop WHERE id = #{id}")
  13. @Results(value = { @Result(id = true, column = "id", property = "id"),
  14. @Result(column = "shop_name", property = "shopName") })
  15. public Shop getShop(@Param("id") int id);
  16.  
  17. }

ShopDao:

  1. package com.xxx.firstboot.dao;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Repository;
  5.  
  6. import com.xxx.firstboot.common.datasource.DatabaseContextHolder;
  7. import com.xxx.firstboot.common.datasource.DatabaseType;
  8. import com.xxx.firstboot.domain.Shop;
  9. import com.xxx.firstboot.mapper.ShopMapper;
  10.  
  11. @Repository
  12. public class ShopDao {
  13. @Autowired
  14. private ShopMapper mapper;
  15.  
  16. /**
  17. * 获取shop
  18. */
  19. public Shop getShop(int id) {
  20. DatabaseContextHolder.setDatabaseType(DatabaseType.mytestdb2);
  21. return mapper.getShop(id);
  22. }
  23. }

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

ShopService:

  1. package com.xxx.firstboot.service;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.stereotype.Service;
  5.  
  6. import com.xxx.firstboot.dao.ShopDao;
  7. import com.xxx.firstboot.domain.Shop;
  8.  
  9. @Service
  10. public class ShopService {
  11.  
  12. @Autowired
  13. private ShopDao dao;
  14.  
  15. public Shop getShop(int id) {
  16. return dao.getShop(id);
  17. }
  18. }

ShopController:

  1. package com.xxx.firstboot.web;
  2.  
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.web.bind.annotation.RequestMapping;
  5. import org.springframework.web.bind.annotation.RequestMethod;
  6. import org.springframework.web.bind.annotation.RequestParam;
  7. import org.springframework.web.bind.annotation.RestController;
  8.  
  9. import com.xxx.firstboot.domain.Shop;
  10. import com.xxx.firstboot.service.ShopService;
  11.  
  12. import io.swagger.annotations.Api;
  13. import io.swagger.annotations.ApiOperation;
  14.  
  15. @RestController
  16. @RequestMapping("/shop")
  17. @Api("shopController相关api")
  18. public class ShopController {
  19.  
  20. @Autowired
  21. private ShopService service;
  22.  
  23. @ApiOperation("获取shop信息,测试多数据源")
  24. @RequestMapping(value = "/getShop", method = RequestMethod.GET)
  25. public Shop getShop(@RequestParam("id") int id) {
  26. return service.getShop(id);
  27. }
  28.  
  29. }

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

参考:

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

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

【第八章】 springboot + mybatis + 多数据源的更多相关文章

  1. 第八章 springboot + mybatis + 多数据源(转载)

    本篇博客转发自:http://www.cnblogs.com/java-zhao/p/5413845.html 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构 ...

  2. 第八章 springboot + mybatis + 多数据源2(解决循环引用)

    解决了循环引用 1.application.properties #the first datasource jdbc.names:1,2 jdbc1.driverClassName = com.my ...

  3. 第八章 springboot + mybatis + 多数据源3(使用切面AOP)

    引入 aop包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  4. 第八章 springboot + mybatis + 多数据源

    http://www.cnblogs.com/java-zhao/p/5413845.html

  5. 第九章 springboot + mybatis + 多数据源 (AOP实现)

    在第八章 springboot + mybatis + 多数据源代码的基础上,做两点修改 1.ShopDao package com.xxx.firstboot.dao; import org.spr ...

  6. 【第九章】 springboot + mybatis + 多数据源 (AOP实现)

    在第八章 springboot + mybatis + 多数据源代码的基础上,做两点修改 1.ShopDao package com.xxx.firstboot.dao; import org.spr ...

  7. spring-boot (四) springboot+mybatis多数据源最简解决方案

    学习文章来自:http://www.ityouknow.com/spring-boot.html 配置文件 pom包就不贴了比较简单该依赖的就依赖,主要是数据库这边的配置: mybatis.confi ...

  8. springboot + mybatis + 多数据源

    此文已由作者赵计刚薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1) ...

  9. springboot mybatis 多数据源配置

    首先导入mybatis等包,这里就不多说. 下面是配置多数据源和mybatis,每个数据源对应一套mybatis模板 数据源1: package com.aaaaaaa.config.datasour ...

随机推荐

  1. 泛型T和通配符?的区别

    这里如果是泛型T的话,那么创建该类的时候就需要指定类型,而通配符不需要.

  2. ListView多种item注意以及自己出现的莫名其妙的错误

    如果ListView不懂,请绕路 1.ListView添加多个item必须用到的两个方法 getViewTypeCount一共有多少种item,我这里写的两种 getItemViewType当前pos ...

  3. cube-ui的用法

    .安装:npm install cube-ui -S .修改 .babelrc:(添加到plugins中去) { "plugins": [ ["transform-mod ...

  4. 深度学习之TensorFlow(一)——基本使用

    一.目前主流的深度学习框架Caffe, TensorFlow, MXNet, Torch, Theano比较 库名称 开发语言 速度 灵活性 文档 适合模型 平台 上手难易 Caffe c++/cud ...

  5. redis5.0主从配置

    1.下载 wget http://download.redis.io/releases/redis-5.0.3.tar.gz .tar.gz cd redis- make make test //检查 ...

  6. POJ3087:Shuffle'm Up(模拟)

    http://poj.org/problem?id=3087 Description A common pastime for poker players at a poker table is to ...

  7. openssl version 查看openssl 版本出现openssl: error while loading shared libraries: libssl.so.1.1: cannot open shared object file: No such file or directory,怎么办

    查看openssl版本, 解决办法: ln -s /usr/local/lib64/libssl.so.1.1 /usr/lib64/libssl.so.1.1 ln -s /usr/local/li ...

  8. test examples/test scripts

    //https://www.getpostman.com/docs/v6/postman/scripts/test_examples //Setting an environment variable ...

  9. docker命令及操作

    docker pull 镜像名字 dockers images docker image ls docker image rm 镜像名/镜像ID docker ps docker ps -a dock ...

  10. sql server 中的分区函数用法(partition by 字段)

    partition  by关键字是分析性函数的一部分,它和聚合函数不同的地方在于它能返回一个分组中的多条记录,而聚合函数一般只有一条反映统计值的记录,partition  by用于给结果集分组,如果没 ...