詳解Spring Boot + Mybatis 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源
動(dòng)態(tài)數(shù)據(jù)源
在很多具體應(yīng)用場景的時(shí)候,我們需要用到動(dòng)態(tài)數(shù)據(jù)源的情況,比如多租戶的場景,系統(tǒng)登錄時(shí)需要根據(jù)用戶信息切換到用戶對應(yīng)的數(shù)據(jù)庫。又比如業(yè)務(wù)A要訪問A數(shù)據(jù)庫,業(yè)務(wù)B要訪問B數(shù)據(jù)庫等,都可以使用動(dòng)態(tài)數(shù)據(jù)源方案進(jìn)行解決。接下來,我們就來講解如何實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源,以及在過程中剖析動(dòng)態(tài)數(shù)據(jù)源背后的實(shí)現(xiàn)原理。
實(shí)現(xiàn)案例
本教程案例基于 Spring Boot + Mybatis + MySQL 實(shí)現(xiàn)。
數(shù)據(jù)庫設(shè)計(jì)
首先需要安裝好MySQL數(shù)據(jù)庫,新建數(shù)據(jù)庫 example,創(chuàng)建example表,用來測試數(shù)據(jù)源,SQL腳本如下:
CREATE TABLE `example` ( `pk` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵', `message` varchar(100) NOT NULL, `create_time` datetime NOT NULL COMMENT '創(chuàng)建時(shí)間', `modify_time` datetime DEFAULT NULL COMMENT '生效時(shí)間', PRIMARY KEY (`pk`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='測試用例表'
添加依賴
添加Spring Boot,Spring Aop,Mybatis,MySQL相關(guān)依賴。
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,添加數(shù)據(jù)源:
#數(shù)據(jù)庫設(shè)置 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
啟動(dòng)類
啟動(dòng)類添加 exclude = {DataSourceAutoConfiguration.class}, 以禁用數(shù)據(jù)源默認(rèn)自動(dòng)配置。
數(shù)據(jù)源默認(rèn)自動(dòng)配置會(huì)讀取 spring.datasource.* 的屬性創(chuàng)建數(shù)據(jù)源,所以要禁用以進(jìn)行定制。
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);
}
}
數(shù)據(jù)源配置類
創(chuàng)建一個(gè)數(shù)據(jù)源配置類,主要做以下幾件事情:
1. 配置 dao,model(bean),xml mapper文件的掃描路徑。
2. 注入數(shù)據(jù)源配置屬性,創(chuàng)建數(shù)據(jù)源。
3. 創(chuàng)建一個(gè)動(dòng)態(tài)數(shù)據(jù)源,裝入數(shù)據(jù)源。
4. 將動(dòng)態(tài)數(shù)據(jù)源設(shè)置到SQL會(huì)話工廠和事務(wù)管理器。
如此,當(dāng)進(jìn)行數(shù)據(jù)庫操作時(shí),就會(huì)通過我們創(chuàng)建的動(dòng)態(tài)數(shù)據(jù)源去獲取要操作的數(shù)據(jù)源了。
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;
//數(shù)據(jù)庫配置統(tǒng)一在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();
}
/*
* 動(dòng)態(tài)數(shù)據(jù)源
* dbMap中存放數(shù)據(jù)源名稱與數(shù)據(jù)源實(shí)例,數(shù)據(jù)源名稱存于DataEnum.DbSource中
* setDefaultTargetDataSource方法設(shè)置默認(rèn)數(shù)據(jù)源
*/
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//配置多數(shù)據(jù)源
Map<Object, Object> dbMap = new HashMap();
dbMap.put(DataEnum.DbSource.example.getName(), exampleDataSource());
dynamicDataSource.setTargetDataSources(dbMap);
// 設(shè)置默認(rèn)數(shù)據(jù)源
dynamicDataSource.setDefaultTargetDataSource(exampleDataSource());
return dynamicDataSource;
}
/*
* 數(shù)據(jù)庫連接會(huì)話工廠
* 將動(dòng)態(tài)數(shù)據(jù)源賦給工廠
* mapper存于resources/mapper目錄下
* 默認(rèn)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() {
// 配置事務(wù)管理, 使用事務(wù)時(shí)在方法頭部添加@Transactional注解即可
return new DataSourceTransactionManager(dynamicDataSource());
}
}
動(dòng)態(tài)數(shù)據(jù)源類
我們上一步把這個(gè)動(dòng)態(tài)數(shù)據(jù)源設(shè)置到了SQL會(huì)話工廠和事務(wù)管理器,這樣在操作數(shù)據(jù)庫時(shí)就會(huì)通過動(dòng)態(tài)數(shù)據(jù)源類來獲取要操作的數(shù)據(jù)源了。
動(dòng)態(tài)數(shù)據(jù)源類集成了Spring提供的AbstractRoutingDataSource類,AbstractRoutingDataSource 中獲取數(shù)據(jù)源的方法就是 determineTargetDataSource,而此方法又通過 determineCurrentLookupKey 方法獲取查詢數(shù)據(jù)源的key。
所以如果我們需要?jiǎng)討B(tài)切換數(shù)據(jù)源,就可以通過以下兩種方式定制:
1. 覆寫 determineCurrentLookupKey 方法
通過覆寫 determineCurrentLookupKey 方法,從一個(gè)自定義的 DbSourceContext.getDbSource() 獲取數(shù)據(jù)源key值,這樣在我們想動(dòng)態(tài)切換數(shù)據(jù)源的時(shí)候,只要通過 DbSourceContext.setDbSource(key) 的方式就可以動(dòng)態(tài)改變數(shù)據(jù)源了。這種方式要求在獲取數(shù)據(jù)源之前,要先初始化各個(gè)數(shù)據(jù)源到 DbSourceContext 中,我們案例就是采用這種方式實(shí)現(xiàn)的,所以要將數(shù)據(jù)源都事先初始化到DynamicDataSource 中。
2. 可以通過覆寫 determineTargetDataSource,因?yàn)閿?shù)據(jù)源就是在這個(gè)方法創(chuàng)建并返回的,所以這種方式就比較自由了,支持到任何你希望的地方讀取數(shù)據(jù)源信息,只要最終返回一個(gè) DataSource 的實(shí)現(xiàn)類即可。比如你可以到數(shù)據(jù)庫、本地文件、網(wǎng)絡(luò)接口等方式讀取到數(shù)據(jù)源信息然后返回相應(yīng)的數(shù)據(jù)源對象就可以了。
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();
}
}
數(shù)據(jù)源上下文
動(dòng)態(tài)數(shù)據(jù)源的切換主要是通過調(diào)用這個(gè)類的方法來完成的。在任何想要進(jìn)行切換數(shù)據(jù)源的時(shí)候都可以通過調(diào)用這個(gè)類的方法實(shí)現(xiàn)切換。比如系統(tǒng)登錄時(shí),根據(jù)用戶信息調(diào)用這個(gè)類的數(shù)據(jù)源切換方法切換到用戶對應(yīng)的數(shù)據(jù)庫。完整代碼如下:
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();
}
}
注解式數(shù)據(jù)源
到這里,在任何想要?jiǎng)討B(tài)切換數(shù)據(jù)源的時(shí)候,只要調(diào)用DbSourceContext.setDbSource(key) 就可以完成了。
接下來我們實(shí)現(xiàn)通過注解的方式來進(jìn)行數(shù)據(jù)源的切換,原理就是添加注解(如@DbSource(value="example")),然后實(shí)現(xiàn)注解切面進(jìn)行數(shù)據(jù)源切換。
創(chuàng)建一個(gè)動(dòng)態(tài)數(shù)據(jù)源注解,擁有一個(gè)value值,用于標(biāo)識(shí)要切換的數(shù)據(jù)源的key。
DbSource.java:
package com.main.example.config.dao;
import java.lang.annotation.*;
/**
* 動(dòng)態(tài)數(shù)據(jù)源注解
* @author
* @date April 12, 2019
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbSource {
/**
* 數(shù)據(jù)源key值
* @return
*/
String value();
}
創(chuàng)建一個(gè)AOP切面,攔截帶 @DataSource 注解的方法,在方法執(zhí)行前切換至目標(biāo)數(shù)據(jù)源,執(zhí)行完成后恢復(fù)到默認(rèn)數(shù)據(jù)源。
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;
/**
* 動(dòng)態(tài)數(shù)據(jù)源切換處理器
* @author linzhibao
* @date April 12, 2019
*/
@Aspect
@Order(-1) // 該切面應(yīng)當(dāng)先于 @Transactional 執(zhí)行
@Component
public class DynamicDataSourceAspect {
private static Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);
/**
* 切換數(shù)據(jù)源
* @param point
* @param dbSource
*/
//@Before("@annotation(dbSource)") 注解在對應(yīng)方法,攔截有@DbSource的方法
//注解在類對象,攔截有@DbSource類下所有的方法
@Before("@within(dbSource)")
public void switchDataSource(JoinPoint point, DbSource dbSource) {
// 切換數(shù)據(jù)源
DbSourceContext.setDbSource(dbSource.value());
}
/**
* 重置數(shù)據(jù)源
* @param point
* @param dbSource
*/
//注解在類對象,攔截有@DbSource類下所有的方法
@After("@within(dbSource)")
public void restoreDataSource(JoinPoint point, DbSource dbSource) {
// 將數(shù)據(jù)源置為默認(rèn)數(shù)據(jù)源
DbSourceContext.clearDbSource();
}
}
到這里,動(dòng)態(tài)數(shù)據(jù)源相關(guān)的處理代碼就完成了。
編寫用戶業(yè)務(wù)代碼
接下來編寫用戶查詢業(yè)務(wù)代碼,用來進(jìn)行測試,Dao層只需添加一個(gè)查詢接口即可。
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")
//切換數(shù)據(jù)源注解,以DataEnum.DbSource中的值為準(zhǔn)
@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>
測試效果
啟動(dòng)系統(tǒng),訪問 http://localhost:80/test/example">http://localhost:80/test/example,分別測試兩個(gè)接口,成功返回?cái)?shù)據(jù)。

可能遇到的問題
1.報(bào)錯(cuò):java.lang.IllegalArgumentException: jdbcUrl is required with driverClassName

原因:
spring boot從1.X升級到2.X版本之后,一些配置及用法有了變化,如果不小心就會(huì)碰到“jdbcUrl is required with driverClassName.”的錯(cuò)誤
解決方法:
在1.0 配置數(shù)據(jù)源的過程中主要是寫成:spring.datasource.url 和spring.datasource.driverClassName。
而在2.0升級之后需要變更成:spring.datasource.jdbc-url和spring.datasource.driver-class-name即可解決!
2.自定義配置文件
自定義配置文件需要在指定配置類上加上@PropertySource標(biāo)簽,例如:
@PropertySource(value = "classpath:config/mysql/db.properties")
若是作用于配置類中的方法,則在方法上加上@ConfigurationProperties,例如:
@ConfigurationProperties(prefix = "spring.datasource.example")
配置項(xiàng)前綴為spring.datasource.example
若是作用于配置類上,則在類上加上@ConfigurationProperties(同上),并且在啟動(dòng)類上加上@EnableConfigurationProperties(XXX.class)
3.多數(shù)據(jù)源
需要在啟動(dòng)類上取消自動(dòng)裝載數(shù)據(jù)源,如:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
以上所述是小編給大家介紹的Spring Boot + Mybatis 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- 通過springboot+mybatis+druid配置動(dòng)態(tài)數(shù)據(jù)源
- SpringBoot整合MyBatisPlus配置動(dòng)態(tài)數(shù)據(jù)源的方法
- springboot 動(dòng)態(tài)數(shù)據(jù)源的實(shí)現(xiàn)方法(Mybatis+Druid)
- Spring Boot + Mybatis 實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源案例分析
- spring boot + mybatis實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源實(shí)例代碼
- Spring Boot + Mybatis多數(shù)據(jù)源和動(dòng)態(tài)數(shù)據(jù)源配置方法
- SpringBoot Mybatis動(dòng)態(tài)數(shù)據(jù)源切換方案實(shí)現(xiàn)過程
相關(guān)文章
Java 自定義動(dòng)態(tài)數(shù)組方式
這篇文章主要介紹了Java自定義動(dòng)態(tài)數(shù)組方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03
Spring實(shí)現(xiàn)內(nèi)置監(jiān)聽器
這篇文章主要介紹了Spring 實(shí)現(xiàn)自定義監(jiān)聽器案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧,希望能給你帶來幫助2021-07-07
Java concurrency集合之ConcurrentHashMap_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java concurrency集合之ConcurrentHashMap的相關(guān)資料,需要的朋友可以參考下2017-06-06

