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)文章
Spring中DAO被循環(huán)調(diào)用的時(shí)候數(shù)據(jù)不實(shí)時(shí)更新的解決方法
這篇文章主要介紹了Spring中DAO被循環(huán)調(diào)用的時(shí)候數(shù)據(jù)不實(shí)時(shí)更新的解決方法,需要的朋友可以參考下2014-08-08SpringBoot不讀取bootstrap.yml/properties文件問(wèn)題
這篇文章主要介紹了SpringBoot不讀取bootstrap.yml/properties文件問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Idea如何集成Git&添加項(xiàng)目到git倉(cāng)庫(kù)
這篇文章主要介紹了Idea如何集成Git&添加項(xiàng)目到git倉(cāng)庫(kù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java final 修飾符知識(shí)點(diǎn)總結(jié)(必看篇)
下面小編就為大家?guī)?lái)一篇Java final 修飾符知識(shí)點(diǎn)總結(jié)(必看篇)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-09-09使用Mybatis-plus實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)表的內(nèi)部字段進(jìn)行比較
這篇文章主要介紹了使用Mybatis-plus實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)表的內(nèi)部字段進(jìn)行比較方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Springboot實(shí)現(xiàn)接口傳輸加解密的步驟詳解
這篇文章主要給大家詳細(xì)介紹了Springboot實(shí)現(xiàn)接口傳輸加解密的操作步驟,文中有詳細(xì)的圖文解釋和代碼示例供大家參考,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-09-09Java中的System.arraycopy()淺復(fù)制方法詳解
這篇文章主要介紹了Java中的System.arraycopy()淺復(fù)制方法詳解,Java數(shù)組的復(fù)制操作可以分為深度復(fù)制和淺度復(fù)制,簡(jiǎn)單來(lái)說(shuō)深度復(fù)制,可以將對(duì)象的值和對(duì)象的內(nèi)容復(fù)制;淺復(fù)制是指對(duì)對(duì)象引用的復(fù)制,需要的朋友可以參考下2023-11-11