SpringBoot使用註解整合Mybatis配置多個數據源實現讀寫分離
- 數據庫【mysql】
- 數據源【Druid】阿里開源
- maven引入依賴jar
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.9</version> </dependency>
- 多個數據源配置
@Configuration public class StudyConfiguration { @Bean(name = "readDataSouce") @ConfigurationProperties(prefix = "spring.primary.druid") public DataSource readDataSouce() { return new DruidDataSource(); } @Bean(name = "writeDataSouce") @ConfigurationProperties(prefix = "spring.secondary.druid") public DataSource writeDataSouce() { return new DruidDataSource(); } }
- 屬性文件配置applicatin.properties
spring.primary.druid.url=jdbc:mysql://localhost:3306/database?useUnicode=true&characterEncoding=utf8 spring.primary.druid.driverClassName=com.mysql.jdbc.Driver spring.primary.druid.username=root spring.primary.druid.password=root spring.secondary.druid.url=jdbc:mysql://localhost:3306/database1?useUnicode=true&characterEncoding=utf8 spring.secondary.druid.driverClassName=com.mysql.jdbc.Driver spring.secondary.druid.username=root spring.secondary.druid.password=root
- DynamicDataSource繼承spring的AbstractRoutingDataSouce類用來切換數據源,在整合mybatis的框架最終通過determineCurrentLookupKey方法決定當前的執行計劃選擇哪一個
package com.tao.study.dynamic; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.springframework.stereotype.Component; @Component public class DynamicDataSouce extends AbstractRoutingDataSource { @Autowired @Qualifier("readDataSouce") private DataSource readDataSource; @Autowired @Qualifier("writeDataSouce") private DataSource writeDataSource; @Override public void afterPropertiesSet() { if(writeDataSource==null) { throw new IllegalArgumentException("write data souce can not be empty"); } setDefaultTargetDataSource(writeDataSource); Map<Object,Object> targetDataSources = new ConcurrentHashMap<Object, Object>(); targetDataSources.put(DynamicDataSouceType.WRITE, writeDataSource); if(readDataSource!=null) { targetDataSources.put(DynamicDataSouceType.READ, readDataSource); } setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { DynamicDataSouceType type = DynamicDataSouceHolder.getDataSouceType(); if(type==null) { return DynamicDataSouceType.WRITE; } return type; } }
- DynamicDataSouceHolder類用來存儲需要切換的數據源類型,使用ThreadLocal線程本地存儲確保安全性
package com.tao.study.dynamic; public class DynamicDataSouceHolder { public final static ThreadLocal<DynamicDataSouceType> currentDynamicDataSouceType = new ThreadLocal<DynamicDataSouceType>(); public static void putDataSouceType(DynamicDataSouceType type) { currentDynamicDataSouceType.set(type); } public static DynamicDataSouceType getDataSouceType() { return currentDynamicDataSouceType.get(); } }
- DynamicDataSouceType枚舉定義多個數據源類型
package com.tao.study.dynamic; public enum DynamicDataSouceType { READ,WRITE }
- DynamicPlugin類實現mybatis的Interceptor的接口,在執行操作數據庫的業務流程中對Query,Update等類型進行處理,可以根據業務需求選擇相應的數據源,實現多個數據源,讀寫分離
package com.tao.study.dynamic; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.SqlCommandType; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.plugin.Intercepts; import org.apache.ibatis.plugin.Invocation; import org.apache.ibatis.plugin.Signature; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.springframework.stereotype.Component; @Component @Intercepts({ @Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class}), @Signature( type= Executor.class, method = "query", args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class}) }) public class DynamicPlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object[] objs = invocation.getArgs(); MappedStatement ms = (MappedStatement) objs[0]; DynamicDataSouceType type = null; // 如果執行select語句使用READ數據源, 其它使用WRITE數據源 if(ms.getSqlCommandType().equals(SqlCommandType.SELECT)){ type = DynamicDataSouceType.READ; }else { type = DynamicDataSouceType.WRITE; } DynamicDataSouceHolder.putDataSouceType(type); return invocation.proceed(); } }
- MybatisConfig配置SqlSessionFactory注入Plugins、DataSource
package com.tao.study.dynamic; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisConfig { @Autowired private DynamicDataSouce dynamicDataSouce; @Autowired private DynamicPlugin dynamicPlugin; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { System.out.println("SqlSessinFactory"); SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dynamicDataSouce); factoryBean.setPlugins(dynamicPlugin); factoryBean.setVfs(SpringBootVFS.class); // Sets the SpringBootVFS class into SqlSessionFactoryBean // ... return factoryBean.getObject(); } }
結束語:以上就是使用SpringBoot實現配置多個數據源進行讀寫分離,如果有其它好的方法也請大家留言一起探討