带你了解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 ThreadLocal contextHolder = 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 targetDataSources = new HashMap<>(); targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource(); myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); myRoutingDataSource.setTargetDataSources(targetDataSources); return myRoutingDataSource; }}

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); }


总结 本篇文章就到这里了,希望能给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

    推荐阅读