第八章 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
View Code

說明:在之前的配置的基礎上,只增加了上述的第二個數據源。

 

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

作用:列舉數據源的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 }
View Code

作用:構建一個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 }
View Code

作用:使用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 }
View Code

作用:

  • 通過讀取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 }
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中的相關代碼換成這樣試試

1     @Bean
2     public SqlSessionFactory sqlSessionFactory(@Qualifier("myTestDbDataSource") DataSource myTestDbDataSource,
3                                                @Qualifier("myTestDb2DataSource") DataSource myTestDb2DataSource) throws Exception{
4         SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
5         fb.setDataSource(this.dataSource(myTestDbDataSource, myTestDb2DataSource));
6         fb.setTypeAliasesPackage(env.getProperty("mybatis.typeAliasesPackage"));
7         fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("mybatis.mapperLocations")));
8         return fb.getObject();
9     }
View Code

或者看看評論區已經解決了問題的朋友的方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM