Spring Boot 集成Mybatis實現主從(多數據源)分離方案示例


文將介紹使用Spring Boot集成Mybatis並實現主從庫分離的實現(同樣適用於多數據源)。延續之前的Spring Boot 集成MyBatis。項目還將集成分頁插件PageHelper、通用Mapper以及Druid。

新建一個Maven項目,最終項目結構如下:

多數據源注入到sqlSessionFactory

POM增加如下依賴:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<!--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 >

這里需要注意的是:項目是通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的。在mybatis-spring-boot-starter:1.2.0中,該類取消了默認構造函數,因此本項目依舊使用1.1.0版本。需要關注后續版本是否會重新把擴展開放處理。

之所以依舊使用舊方案,是我個人認為開放擴展是合理的,相信在未來的版本中會回歸。

如果你需要其他方案可參考傳送門

增加主從庫配置(application.yml)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
druid:
   type: com.alibaba.druid.pool.DruidDataSource
   master:
     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:
     driver-class-name: com.mysql.jdbc.Driver
     username: root
     password: root
     initial-size: 5
     min-idle: 1
     max-active: 100
     test-on-borrow: true

創建數據源

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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();
   }
}

將多數據源注入到sqlSessionFactory中

前面提到了這里通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@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); //默認源
     proxy.setTargetDataSources(targetDataResources);
     return proxy;
   }
}

實現讀寫分離(多數據源分離)

這里主要思路如下:

1-將不同的數據源標識記錄在ThreadLocal中

2-通過注解標識出當前的service方法使用哪個庫

3-通過Spring AOP實現攔截注解並注入不同的標識到threadlocal中

4-獲取源的時候通過threadlocal中不同的標識給出不同的sqlSession

標識存放ThreadLocal的實現

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
   }
 
}

注解實現

?
1
2
3
4
5
6
7
8
/**
  * 該注解注釋在service方法上,標注為鏈接slaves庫
  * Created by Jason on 2017/3/6.
  */
@Target ({ElementType.METHOD,ElementType.TYPE})
@Retention (RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}

Spring AOP對注解的攔截

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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 ;
   }
}

根據標識獲取不同源

這里我們通過擴展AbstractRoutingDataSource來獲取不同的源。它是Spring提供的一個可以根據用戶發起的不同請求去轉換不同的數據源,比如根據用戶的不同地區語言選擇不同的數據庫。通過查看源碼可以發現,它是通過determineCurrentLookupKey()返回的不同key到sqlSessionFactory中獲取不同源(前面已經展示了如何在sqlSessionFactory中注入多個源)

?
1
2
3
4
5
6
7
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
 
   @Override
   protected Object determineCurrentLookupKey() {
     return DbContextHolder.getDbType();
   }
}

以上就完成了讀寫分離(多數據源)的配置方案。下面是一個具體的實例

使用方式

Entity

?
1
2
3
4
5
6
7
8
9
10
11
@Table (name = "t_sys_dic_type" )
public class DicType extends BaseEntity{
 
   String code;
 
   String name;
 
   Integer status;
 
   ...
}

Mapper

?
1
2
public interface DicTypeMapper extends BaseMapper<DicType> {
}

Service

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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

?
1
2
3
4
5
6
7
8
9
10
11
12
@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);
   }
}

通過mvn spring-boot:run啟動后,即可通過http://localhost:9090/dictype/all 獲取到數據

后台打印出

?
1
c.a.d.m.ReadOnlyConnectionInterceptor  : set database connection to read only

說明使用了從庫的鏈接獲取數據

備注:如何保證多源事務呢?

1-在讀寫分離場景中不會考慮主從庫事務,在純讀的上下文上使用@ReadOnlyConnection標簽。其他則默認使用主庫。

2-在多源場景中,Spring的@Transaction是可以保證多源的事務性的。

Spring Boot+Mybatis+Druid+PageHelper實現多數據源並分頁的方法

 

原文鏈接:http://www.jianshu.com/p/8813ec02926a

 


免責聲明!

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



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