零基础尝试mybatis-plus读写分离
看了好几篇博友写的文章,关于spring-boot整合mybatis-plus实现读写分离,不过都是缺这少那的,跑不起来,所以自己实操了一次,做个记录
实现方式为使用Aop切面
1、增加数据库枚举类
/** * 数据库类型 */ public enum DBTypeEnum { /** * 主节点 */ MASTER, /** * 从 */ SLAVE }
2、配置数据源
/** * 多数据源配置 */ @AutoConfigureBefore(DruidDataSourceAutoConfigure.class) @Configuration @ConfigurationProperties(prefix = "spring.datasource.druid") @Data public class DataSourceConfig { private int initialSize; private int maxActive; private int minIdle; private long maxWait; private long minEvictableIdleTimeMillis; private long timeBetweenEvictionRunsMillis; private boolean testWhileIdle; /** * 配置主数据源 * * @return 数据源 */ @Bean(name = "masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.druid.master" ) public DataSource masterDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); parseDruidConfig(druidDataSource); return druidDataSource; } /** * 配置从数据源 * * @return 数据源 */ @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.druid.slave") public DataSource slaveDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); parseDruidConfig(druidDataSource); return druidDataSource; } private void parseDruidConfig(DruidDataSource dataSource) { dataSource.setInitialSize(initialSize); dataSource.setMaxActive(maxActive); dataSource.setMinIdle(minIdle); dataSource.setMaxWait(maxWait); dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); dataSource.setTestWhileIdle(testWhileIdle); } /** * 配置路由数据源 * * @param masterDataSource 主节点 * @param slaveDataSource 从节点 * @return 数据源 */ @Bean(name = "myRoutingDataSource") @DependsOn({"masterDataSource", "slaveDataSource"}) @Primary public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("slaveDataSource") DataSource slaveDataSource) { Map<Object, Object> targetDataSources = new HashMap<>(3); targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); targetDataSources.put(DBTypeEnum.SLAVE, slaveDataSource); MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource(); //设置默认数据源 myRoutingDataSource.setDefaultTargetDataSource(masterDataSource); myRoutingDataSource.setTargetDataSources(targetDataSources); return myRoutingDataSource; } }
3、线程轮循切换主从数据库(多主多从的情况下适用)
/** * 通过ThreadLocal将数据源设置到每个线程上下文中 */ public class DataSourceContextHolder { private static final ThreadLocal<DBTypeEnum> CONTEXT_HOLDER = new ThreadLocal<>(); private static final AtomicInteger COUNTER = new AtomicInteger(-1); public static void set(DBTypeEnum dbType) { CONTEXT_HOLDER.set(dbType); } public static DBTypeEnum get() { return CONTEXT_HOLDER.get(); } public static void clear(){ CONTEXT_HOLDER.remove(); } public static void master() { set(DBTypeEnum.MASTER); System.out.println("切换到master"); } public static void slave() { // 轮询 int index = COUNTER.getAndIncrement() % 2; if (COUNTER.get() > 9999) { COUNTER.set(-1); } // if (index == 0) { // set(DBTypeEnum.SLAVE1); // System.out.println("切换到slave1"); // } else { // set(DBTypeEnum.SLAVE2); // System.out.println("切换到slave2"); // } set(DBTypeEnum.SLAVE); System.out.println("切换到slave2"); } }
4、声明路由数据源key(多主多从的情况下适用)
/** * 声明路由数据源key */ public class MyRoutingDataSource extends AbstractRoutingDataSource { @Nullable @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.get(); } }
5、强制使用主/从数据库注解
/** * 强制使用主数据库注解 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface DataSourceMaster { }
/** * 强制使用从数据库注解 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface DataSourceSlave { }
6、切面类(从库注解会报错,不知道什么原因)
/** * 使用aop实现数据源切换 */ @Aspect @Component public class DataSourceAop { // /** // * 需要读的方法,切面 // */ // @Pointcut("!@annotation(com.readWriteSeparation.annotation.DataSourceSlave)" + // "|| execution(* com.readWriteSeparation.service..*.select*(..)) " + // "|| execution(* com.readWriteSeparation.service..*.get*(..))" + // "|| execution(* com.readWriteSeparation.service..*.query*(..))" + // "|| execution(* com.readWriteSeparation.service..*.find*(..)))") // public void readPointcut() { } /** * 需要读的方法,切面 */ @Pointcut("execution(* com.readWriteSeparation.service..*.select*(..)) " + "|| execution(* com.readWriteSeparation.service..*.get*(..))" + "|| execution(* com.readWriteSeparation.service..*.query*(..))" + "|| execution(* com.readWriteSeparation.service..*.find*(..)))") public void readPointcut() { } /** * 写切面 */ @Pointcut("@annotation(com.readWriteSeparation.annotation.DataSourceMaster) " + "|| execution(* com.readWriteSeparation.service..*.insert*(..))" + "|| execution(* com.readWriteSeparation.service..*.save*(..))" + "|| execution(* com.readWriteSeparation.service..*.add*(..))" + "|| execution(* com.readWriteSeparation.service..*.update*(..))" + "|| execution(* com.readWriteSeparation.service..*.edit*(..))" + "|| execution(* com.readWriteSeparation.service..*.delete*(..))" + "|| execution(* com.readWriteSeparation.service..*.remove*(..))") public void writePointcut() { } @Before("readPointcut()") public void read() { DataSourceContextHolder.slave(); } @Before("writePointcut()") public void write() { DataSourceContextHolder.master(); } @After("readPointcut()") public void readAfter() { DataSourceContextHolder.clear(); } @After("writePointcut()") public void writeAfter() { DataSourceContextHolder.clear(); } }
7、.yml文件配置
spring: datasource: druid: master: username: root password: 12345678 url: jdbc:mysql://192.168.10.15/zeroStart?characterEncoding=UTF-8&useSSL=true&requireSSL=false&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource slave: username: root password: 12345678 url: jdbc:mysql://localhost:3306/zeroStart?characterEncoding=UTF-8&useSSL=true&requireSSL=false&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&allowMultiQueries=true driver-class-name: com.mysql.cj.jdbc.Driver type: com.zaxxer.hikari.HikariDataSource
8、引入的依赖
<!--主从配置依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>2.4.2</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-extension</artifactId> <version>${mybatis-plus.version}</version> </dependency> <!--主从end--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.7</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.16</version> </dependency>
配置到这就好了,业务代码就不展示了