Spring Boot 集成Mybatis實(shí)現(xiàn)主從(多數(shù)據(jù)源)分離方案示例
本文將介紹使用Spring Boot集成Mybatis并實(shí)現(xiàn)主從庫(kù)分離的實(shí)現(xiàn)(同樣適用于多數(shù)據(jù)源)。延續(xù)之前的Spring Boot 集成MyBatis。項(xiàng)目還將集成分頁(yè)插件PageHelper、通用Mapper以及Druid。
新建一個(gè)Maven項(xiàng)目,最終項(xiàng)目結(jié)構(gòu)如下:

多數(shù)據(jù)源注入到sqlSessionFactory
POM增加如下依賴(lài):
<!--JSON-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!--mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.1.0</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring-boot-starter</artifactId>
<groupId>org.mybatis.spring.boot</groupId>
</exclusion>
</exclusions>
</dependency>
這里需要注意的是:項(xiàng)目是通過(guò)擴(kuò)展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來(lái)實(shí)現(xiàn)多數(shù)據(jù)源注入的。在mybatis-spring-boot-starter:1.2.0中,該類(lèi)取消了默認(rèn)構(gòu)造函數(shù),因此本項(xiàng)目依舊使用1.1.0版本。需要關(guān)注后續(xù)版本是否會(huì)重新把擴(kuò)展開(kāi)放處理。
之所以依舊使用舊方案,是我個(gè)人認(rèn)為開(kāi)放擴(kuò)展是合理的,相信在未來(lái)的版本中會(huì)回歸。
如果你需要其他方案可參考傳送門(mén)
增加主從庫(kù)配置(application.yml)
druid:
type: com.alibaba.druid.pool.DruidDataSource
master:
url: jdbc:mysql://192.168.249.128:3307/db-test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initial-size: 5
min-idle: 1
max-active: 100
test-on-borrow: true
slave:
url: jdbc:mysql://192.168.249.128:3317/db-test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
initial-size: 5
min-idle: 1
max-active: 100
test-on-borrow: true
創(chuàng)建數(shù)據(jù)源
@Configuration
@EnableTransactionManagement
public class DataSourceConfiguration {
@Value("${druid.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name = "masterDataSource")
@Primary
@ConfigurationProperties(prefix = "druid.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "druid.slave")
public DataSource slaveDataSource1(){
return DataSourceBuilder.create().type(dataSourceType).build();
}
}
將多數(shù)據(jù)源注入到sqlSessionFactory中
前面提到了這里通過(guò)擴(kuò)展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來(lái)實(shí)現(xiàn)多數(shù)據(jù)源注入的
@Configuration
@AutoConfigureAfter({DataSourceConfiguration.class})
public class MybatisConfiguration extends MybatisAutoConfiguration {
private static Log logger = LogFactory.getLog(MybatisConfiguration.class);
@Resource(name = "masterDataSource")
private DataSource masterDataSource;
@Resource(name = "slaveDataSource")
private DataSource slaveDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
return super.sqlSessionFactory(roundRobinDataSouceProxy());
}
public AbstractRoutingDataSource roundRobinDataSouceProxy(){
ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
Map<Object,Object> targetDataResources = new ClassLoaderRepository.SoftHashMap();
targetDataResources.put(DbContextHolder.DbType.MASTER,masterDataSource);
targetDataResources.put(DbContextHolder.DbType.SLAVE,slaveDataSource);
proxy.setDefaultTargetDataSource(masterDataSource);//默認(rèn)源
proxy.setTargetDataSources(targetDataResources);
return proxy;
}
}
實(shí)現(xiàn)讀寫(xiě)分離(多數(shù)據(jù)源分離)
這里主要思路如下:
1-將不同的數(shù)據(jù)源標(biāo)識(shí)記錄在ThreadLocal中
2-通過(guò)注解標(biāo)識(shí)出當(dāng)前的service方法使用哪個(gè)庫(kù)
3-通過(guò)Spring AOP實(shí)現(xiàn)攔截注解并注入不同的標(biāo)識(shí)到threadlocal中
4-獲取源的時(shí)候通過(guò)threadlocal中不同的標(biāo)識(shí)給出不同的sqlSession
標(biāo)識(shí)存放ThreadLocal的實(shí)現(xiàn)
public class DbContextHolder {
public enum DbType{
MASTER,SLAVE
}
private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();
public static void setDbType(DbType dbType){
if(dbType==null)throw new NullPointerException();
contextHolder.set(dbType);
}
public static DbType getDbType(){
return contextHolder.get()==null?DbType.MASTER:contextHolder.get();
}
public static void clearDbType(){
contextHolder.remove();
}
}
注解實(shí)現(xiàn)
/**
* 該注解注釋在service方法上,標(biāo)注為鏈接slaves庫(kù)
* Created by Jason on 2017/3/6.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
Spring AOP對(duì)注解的攔截
@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {
public static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);
@Around("@annotation(readOnlyConnection)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,ReadOnlyConnection readOnlyConnection) throws Throwable {
try {
logger.info("set database connection to read only");
DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
Object result = proceedingJoinPoint.proceed();
return result;
}finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
}
@Override
public int getOrder() {
return 0;
}
}
根據(jù)標(biāo)識(shí)獲取不同源
這里我們通過(guò)擴(kuò)展AbstractRoutingDataSource來(lái)獲取不同的源。它是Spring提供的一個(gè)可以根據(jù)用戶(hù)發(fā)起的不同請(qǐng)求去轉(zhuǎn)換不同的數(shù)據(jù)源,比如根據(jù)用戶(hù)的不同地區(qū)語(yǔ)言選擇不同的數(shù)據(jù)庫(kù)。通過(guò)查看源碼可以發(fā)現(xiàn),它是通過(guò)determineCurrentLookupKey()返回的不同key到sqlSessionFactory中獲取不同源(前面已經(jīng)展示了如何在sqlSessionFactory中注入多個(gè)源)
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
以上就完成了讀寫(xiě)分離(多數(shù)據(jù)源)的配置方案。下面是一個(gè)具體的實(shí)例
使用方式
Entity
@Table(name = "t_sys_dic_type")
public class DicType extends BaseEntity{
String code;
String name;
Integer status;
...
}
Mapper
public interface DicTypeMapper extends BaseMapper<DicType> {
}
Service
@Service
public class DicTypeService {
@Autowired
private DicTypeMapper dicTypeMapper;
@ReadOnlyConnection
public List<DicType> getAll(DicType dicType){
if (dicType.getPage() != null && dicType.getRows() != null) {
PageHelper.startPage(dicType.getPage(), dicType.getRows());
}
return dicTypeMapper.selectAll();
}
}
注意這里的@ReadOnlyConnection注解
Controller
@RestController
@RequestMapping("/dictype")
public class DicTypeController {
@Autowired
private DicTypeService dicTypeService;
@RequestMapping(value = "/all")
public PageInfo<DicType> getALL(DicType dicType){
List<DicType> dicTypeList = dicTypeService.getAll(dicType);
return new PageInfo<>(dicTypeList);
}
}
通過(guò)mvn spring-boot:run啟動(dòng)后,即可通過(guò)http://localhost:9090/dictype/all 獲取到數(shù)據(jù)
后臺(tái)打印出
c.a.d.m.ReadOnlyConnectionInterceptor : set database connection to read only
說(shuō)明使用了從庫(kù)的鏈接獲取數(shù)據(jù)
備注:如何保證多源事務(wù)呢?
1-在讀寫(xiě)分離場(chǎng)景中不會(huì)考慮主從庫(kù)事務(wù),在純讀的上下文上使用@ReadOnlyConnection標(biāo)簽。其他則默認(rèn)使用主庫(kù)。
2-在多源場(chǎng)景中,Spring的@Transaction是可以保證多源的事務(wù)性的。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaWeb中的filter過(guò)濾敏感詞匯案例詳解
敏感詞、文字過(guò)濾是一個(gè)網(wǎng)站必不可少的功能,本篇文章主要介紹了JavaWeb中的filter過(guò)濾敏感詞匯案例,具有一定的參考價(jià)值,有需要的可以了解一下,2016-11-11
SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問(wèn)題(推薦)
這篇文章主要介紹了SpringBoot使用SchedulingConfigurer實(shí)現(xiàn)多個(gè)定時(shí)任務(wù)多機(jī)器部署問(wèn)題,定時(shí)任務(wù)多機(jī)器部署解決方案,方式一拆分,單獨(dú)拆分出來(lái),單獨(dú)跑一個(gè)應(yīng)用,方式二是基于aop攔截處理(搶占執(zhí)行),只要有一個(gè)執(zhí)行,其它都不執(zhí)行,需要的朋友可以參考下2023-01-01
使用log4j2打印mybatis的sql執(zhí)行日志方式
這篇文章主要介紹了使用log4j2打印mybatis的sql執(zhí)行日志方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
Maven本地倉(cāng)庫(kù)的配置以及修改默認(rèn).m2倉(cāng)庫(kù)位置
今天小編就為大家分享一篇關(guān)于Maven本地倉(cāng)庫(kù)的配置以及修改默認(rèn).m2倉(cāng)庫(kù)位置的文章,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-10-10
Java實(shí)現(xiàn)中文字符串與unicode互轉(zhuǎn)工具類(lèi)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)中文字符串與unicode互轉(zhuǎn)的工具類(lèi),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-04-04
利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法示例
這篇文章主要給大家介紹了關(guān)于利用java+mysql遞歸實(shí)現(xiàn)拼接樹(shù)形JSON列表的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起看看吧。2017-08-08
阿里SpringBoot應(yīng)用自動(dòng)化部署實(shí)現(xiàn)IDEA版Jenkins
這篇文章主要為大家介紹了阿里SpringBoot應(yīng)用自動(dòng)化部署實(shí)現(xiàn)IDEA版Jenkins過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
Java實(shí)現(xiàn)多個(gè)sheet頁(yè)數(shù)據(jù)導(dǎo)出功能
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)多個(gè)sheet頁(yè)數(shù)據(jù)導(dǎo)出功能的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03
30分鐘入門(mén)Java8之方法引用學(xué)習(xí)
在Java8中,我們可以直接通過(guò)方法引用來(lái)簡(jiǎn)寫(xiě)lambda表達(dá)式中已經(jīng)存在的方法,這篇文章主要介紹了30分鐘入門(mén)Java8之方法引用學(xué)習(xí),有興趣可以了解一下。2017-04-04
mybatis框架order by作為參數(shù)傳入時(shí)失效的解決
這篇文章主要介紹了mybatis框架order by作為參數(shù)傳入時(shí)失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06

