在知乎看到一個這樣的問題:“為什麼別選電腦專業?” 來源:https://www.zhihu.com/question/465369002/answer/2213759239 這個話題有756人關註,以及1,721,580人次瀏覽。以下是一位匿名用戶的高贊回答,內容可能比較主觀化,僅代表作者個人觀 ...
看了好幾篇博友寫的文章,關於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>
配置到這就好了,業務代碼就不展示了