Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

网友投稿 284 2023-06-04


Spring Boot 集成Mybatis实现主从(多数据源)分离方案示例

本文将介绍使用Spring Boot集成Mybatis并实现主从库分离的实现(同样适用于多数据源)。延续之前的Spring Boot 集成MyBatis。项目还将集成分页插件PageHelper、通用Mapper以及Druid。

新建一个Maven项目,最终项目结构如下:

多数据源注入到sqlSessionFactory

POM增加如下依赖:

com.fasterxml.jackson.core

jackson-core

com.fasterxml.jackson.core

jackson-databind

com.fasterxml.jackson.datatype

jackson-datatype-joda

com.fasterxml.jackson.module

jackson-module-parameter-names

org.springframework.boot

spring-boot-starter-jdbc

mysql

mysql-connector-java

com.alibaba

druid

1.0.11

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.1.1

tk.mybatis

mapper-spring-boot-starter

1.1.0

com.github.pagehelper

pagehelper-spring-boot-starter

1.1.0

mybatis-spring-boot-starter

<groupId>org.mybatis.spring.boot

这里需要注意的是:项目是通过扩展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 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 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 {

}

Service

@Service

public class DicTypeService {

@Autowired

private DicTypeMapper dicTypeMapper;

@ReadOnlyConnection

public List 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 getALL(DicType dicType){

List 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是可以保证多源的事务性的。

本文使用代码


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序开发之入门实例教程篇
下一篇:java 实例化类详解及简单实例
相关文章

 发表评论

暂时没有评论,来抢沙发吧~