带你了解mybatis如何实现读写分离
目录
- 1、spring aop实现
- 2、mybatis-plus的实现方式
- 总结
1、spring aop实现 首先application-test.yml增加如下数据源的配置
spring:datasource:master:jdbc-url: jdbc:mysql://master域名:3306/testusername: rootpassword: 123456driver-class-name: com.mysql.jdbc.Driverslave1:jdbc-url: jdbc:mysql://slave域名:3306/testusername: root# 只读账户password: 123456driver-class-name: com.mysql.jdbc.Driverslave2:jdbc-url: jdbc:mysql://slave域名:3306/testusername: root# 只读账户password: 123456driver-class-name: com.mysql.jdbc.Driver
package com.cjs.example.enums; public enum DBTypeEnum {MASTER, SLAVE1, SLAVE2; }
【带你了解mybatis如何实现读写分离】定义ThreadLocal上下文,将当前线程的数据源进行动态修改
public class DBContextHolder {private staticvolatile ThreadLocalcontextHolder = new ThreadLocal<>(); public static synchronized void set(DBTypeEnum dbType) {contextHolder.set(dbType); }public static synchronized DBTypeEnum get() {return contextHolder.get(); }public static void master() {set(DBTypeEnum.MASTER); }public static void slave() {set(DBTypeEnum.SLAVE1); }public static void slave2(){ set(DBTypeEnum.SLAVE2); }// 清除数据源名public static void clearDB() {contextHolder.remove(); }}
重写mybatis数据源路由接口,在此修改数据源为我们上一块代码设置的上下文的数据源
public class MyRoutingDataSource extends AbstractRoutingDataSource {@Nullable@Overrideprotected Object determineCurrentLookupKey() {DBTypeEnum dbTypeEnum=DBContextHolder.get(); return dbTypeEnum; }}
将yml配置的多数据源手动指定注入
@Configurationpublic class DataSourceConfig {@Bean@ConfigurationProperties("spring.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build(); }@Bean@ConfigurationProperties("spring.datasource.slave1")public DataSource slave1DataSource() {return DataSourceBuilder.create().build(); }@Beanpublic DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,@Qualifier("slave1DataSource") DataSource slave1DataSource) {Map
sqlsession注入以上我们配置的datasource路由
@EnableTransactionManagement@Configuration@Import({TableSegInterceptor.class})public class MyBatisConfig {@Resource(name = "myRoutingDataSource")private DataSource myRoutingDataSource; @Autowiredprivate MybatisConfigProperty mybatisConfigProperty; @Autowiredprivate TableSegInterceptor tableSegInterceptor; @Beanpublic SqlSessionFactory sqlSessionFactory() throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(myRoutingDataSource); // SpringBoot项目集成mybatis打包为jar运行时setTypeAliasesPackage无效解决VFS.addImplClass(SpringBootVFS.class); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mybatisConfigProperty.getMapperLocations())); sqlSessionFactoryBean.setTypeAliasesPackage(mybatisConfigProperty.getTypeAliasesPackage()); sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(mybatisConfigProperty.getConfigLocation())); sqlSessionFactoryBean.setPlugins(new Interceptor[]{tableSegInterceptor}); return sqlSessionFactoryBean.getObject(); }@Beanpublic PlatformTransactionManager platformTransactionManager() {return new DataSourceTransactionManager(myRoutingDataSource); }}
spring aop拦截指定前缀的service方法,并设置对应所属的上下文
@Aspect@Componentpublic class DataSourceAop {@Pointcut("!@annotation(com.ask.student.interceptor.annotation.Master) " +"&& (execution(* com.ask.student.service..*.select*(..)) " +"|| execution(* com.ask.student.service..*.get*(..))" +"|| execution(* com.ask.student.service..*.find*(..))" +")")public void readPointcut() {}@Pointcut("@annotation(com.ask.student.interceptor.annotation.Master) " +"|| execution(* com.ask.student.service..*.insert*(..)) " +"|| execution(* com.ask.student.service..*.clean*(..)) " +"|| execution(* com.ask.student.service..*.reset*(..)) " +"|| execution(* com.ask.student.service..*.add*(..)) " +"|| execution(* com.ask.student.service..*.update*(..)) " +"|| execution(* com.ask.student.service..*.edit*(..)) " +"|| execution(* com.ask.student.service..*.delete*(..)) " +"|| execution(* com.ask.student.service..*.remove*(..))")public void writePointcut() {}@Before("readPointcut()")public void read() {DBContextHolder.slave(); }@Before("writePointcut()")public void write() {DBContextHolder.master(); }@After("readPointcut()||writePointcut()")public void afterSwitchDS(){DBContextHolder.clearDB(); }}
以上最后一个方法的作用,在拦截器中获取后及时清除避免导致来回切换当前线程变量延迟问题导致某些操作的数据源错误
DBContextHolder.clearDB();
@After("readPointcut()||writePointcut()")
public void afterSwitchDS(){
DBContextHolder.clearDB();
}
2、mybatis-plus的实现方式 这个方式配置简单,代码少,很多事情mybatis-plus都已经做好了,推荐使用
yml配置如下
datasource:dynamic:primary: master#设置默认的数据源或者数据源组,默认值即为masterstrict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源.datasource:master:url: jdbc:mysql://xxx:3306/db0?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaiusername: adminpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 5maximum-pool-size: 15auto-commit: trueidle-timeout: 30000pool-name: springHikariCPmax-lifetime: 1800000connection-timeout: 30000connection-test-query: SELECT 1slave1:url: jdbc:mysql://xxx:3306/db2?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaiusername: adminpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 5maximum-pool-size: 15auto-commit: trueidle-timeout: 30000pool-name: springHikariCPmax-lifetime: 1800000connection-timeout: 30000connection-test-query: SELECT 1slave2:url: jdbc:mysql://xxx:3306/db3?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghaiusername: adminpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 5maximum-pool-size: 15auto-commit: trueidle-timeout: 30000pool-name: springHikariCPmax-lifetime: 1800000connection-timeout: 30000connection-test-query: SELECT 1
使用起来非常简单,只需要加上这个master的注解即可
@Override@DS("master")public DestMedia getOneByCodeFromEpg(String code) {QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("code", code); return super.getOne(queryWrapper); }
总结 本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!
推荐阅读
- 我们重新了解付费。
- 拍照一年啦,如果你想了解我,那就请先看看这篇文章
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- mybatisplus如何在xml的连表查询中使用queryWrapper
- mybatisplus|mybatisplus where QueryWrapper加括号嵌套查询方式
- MybatisPlus|MybatisPlus LambdaQueryWrapper使用int默认值的坑及解决
- MybatisPlus使用queryWrapper如何实现复杂查询
- C语言中的时间函数clock()和time()你都了解吗
- 不废话,代码实践带你掌握|不废话,代码实践带你掌握 强缓存、协商缓存!
- 操作系统|[译]从内部了解现代浏览器(1)