Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例
作者:险远的奇伟诡怪 发布时间:2023-08-10 07:43:21
本文将介绍使用Spring Boot集成Mybatis并实现主从库分离的实现(同样适用于多数据源)。延续之前的Spring Boot 集成MyBatis。项目还将集成分页插件PageHelper、通用Mapper以及Druid。
新建一个Maven项目,最终项目结构如下:
多数据源注入到sqlSessionFactory
POM增加如下依赖:
<!--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)
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
创建数据源
@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来实现多数据源注入的
@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的实现
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();
}
}
注解实现
/**
* 该注解注释在service方法上,标注为链接slaves库
* Created by Jason on 2017/3/6.
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
Spring AOP对注解的拦截
@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中注入多个源)
public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType();
}
}
以上就完成了读写分离(多数据源)的配置方案。下面是一个具体的实例
使用方式
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);
}
}
通过mvn spring-boot:run启动后,即可通过http://localhost:9090/dictype/all 获取到数据
后台打印出
c.a.d.m.ReadOnlyConnectionInterceptor : set database connection to read only
说明使用了从库的链接获取数据
备注:如何保证多源事务呢?
1-在读写分离场景中不会考虑主从库事务,在纯读的上下文上使用@ReadOnlyConnection标签。其他则默认使用主库。
2-在多源场景中,Spring的@Transaction是可以保证多源的事务性的。
本文使用代码
来源:http://www.jianshu.com/p/8813ec02926a


猜你喜欢
- 不同的浏览器会把cookie文件保存在不同的地方以下是C# WebBrowser控件cookies的存放路径C:\Users\{你的帐号名}
- 做Android开发五年了,期间做做停停(去做后台开发,服务器管理),当回来做Android的时候,发现很生疏,好
- 很多开发者表示,不知道Android的Drawable和Bitmap之间如何相关转换。下面Android123给大家两种比较简单高效的方法。
- 使用第三方的vitamio插件实现简易的播放器。vitamio版本(5.2.3)官网地址:官网地址效果展示效果项目结构代码:MainActi
- Flutter自适应瀑布流前言:在电商app经常会看到首页商品推荐的瀑布流,或者类似短视频app首页也是瀑布流,这些都是需要自适应的,才能给
- 一、背景在通过Runnable接口创建线程时,启动线程需要借助Thread类,这里就涉及到了静态代理模式。二、实例以歌手演出为例,在演出的这
- 前言 CompoundButton在XML文件中主要使用下面两个属性。checked:指定按钮的勾选状态,true表示勾选,fal
- 有些手机在电话接通后会有振动提示,这有个好处就是可以等到接通后再放到耳边接听,减少辐射。本文就讲讲如何在Android手机中实现这种接通电话
- HttpServletRequest介绍HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HT
- Java的List在删除元素时,一般会用list.remove(o)/remove(i)方法。在使用时,容易触碰陷阱,得到意想不到的结果。总
- 接上一篇文章:Android实现图片区域裁剪功能上一篇文章提及了通过调用系统相册或拍照来实现图片的缩放\裁剪。不过这对于笔者项目的要求同样不
- 这个问题是我自己开发中遇到的问题 数据库使用的是mysql5.6 字段名称为checkingTime 类
- C# 关于Invoke首先说下,invoke和begininvoke的使用有两种情况:control中的invoke、begininvoke
- 为了解决以下两个问题:1、单JAR包应用查看日志需要的时候如果需要远程访问服务器登录查看日志,那样相对比较麻烦2、生产环境为了解决BUG需要
- 本文实例讲述了C#自定义处理xml数据类。分享给大家供大家参考。具体分析如下:这个C#类专门用户处理xml数据,可以大大简化xml的操作,类
- @Value从Nacos配置中心获取值并自动刷新在使用Nacos作为配置中心时,除了@NacosValue可以做到自动刷新外,nacos-s
- 一、常规形式1 项目结构2 配置文件及环境设置(1)配置文件# 应用服务 WEB 访问端口server.port=8080# spring
- 概念 基本映射是对一个实体进行映射,关联映射就是处理多个实体之间的关
- 什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现
- 本文实例为大家分享了Android刷新加载框架的具体代码,供大家参考,具体内容如下1.定义一个接口控制下拉和上拉public interfa