• 第八章 springboot + mybatis + 多数据源


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

    代码结构:

    简要原理:

    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
    复制代码

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

    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:

    View Code

    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 }
    View Code

    注意:首先设置了数据源的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 }
    View Code

    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 }
    View Code

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

    参考:

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

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

    具体的实现方式见 第九章 springboot + mybatis + 多数据源 (AOP实现)

    很多朋友反映遇到数据源循环依赖的问题,可以试一下将MyBatisConfig中的相关代码换成这样试试

    View Code

    或者看看评论区已经解决了问题的朋友的方案。

     
    https://www.cnblogs.com/java-zhao/p/5413845.html
    循环引用解决方法  https://blog.csdn.net/kkgbn/article/details/79054809
  • 相关阅读:
    程序员修炼之道:从小工到专家--读书摘录
    代码规范--捡拾(SQL语句)
    新博客,新生活
    如何用Eclipse+maven创建servlet 3.0 web 项目
    swift -- 枚举
    swift -- 函数
    控制语句 for while if switch
    swift -- 字符串
    swift -- 集合
    swift -- 字典
  • 原文地址:https://www.cnblogs.com/tiancai/p/9115925.html
Copyright © 2020-2023  润新知