From 7dbcfc956aeef73a4818658de8ac4e2518969888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Miller=20=28=E9=94=BA=E4=BF=8A=29?= Date: Sun, 27 Jun 2021 10:52:35 +0800 Subject: [PATCH] perf($Starter): support switching dynamic data source on runtime based on SQL --- .../application-development-docker.yml | 15 ++- .../application-development-local.yml | 15 ++- .../main/resources/application-production.yml | 15 ++- .../src/main/resources/application-stage.yml | 15 ++- .../src/main/resources/application-test.yml | 15 ++- .../src/main/resources/application.yml | 1 - docker/.env | 3 +- docker/docker-compose.yml | 4 +- .../application-development-docker.yml | 15 ++- .../application-development-local.yml | 15 ++- .../main/resources/application-production.yml | 15 ++- .../src/main/resources/application-stage.yml | 15 ++- .../src/main/resources/application-test.yml | 15 ++- maf-mis/src/main/resources/application.yml | 1 - pom.xml | 1 + spring-cloud-starter/pom.xml | 5 + .../MafAutoConfiguration.java | 24 ++-- ...a => CommonExceptionControllerAdvice.java} | 36 +----- .../DatabaseExceptionControllerAdvice.java | 106 ++++++++++++++++++ .../configuration/AsyncConfiguration.java | 2 - .../ExcelImportConfiguration.java | 10 -- .../configuration/MafConfiguration.java | 2 - .../configuration/MafProjectProperty.java | 2 - .../configuration/MinioConfiguration.java | 3 - .../configuration/RabbitmqConfiguration.java | 6 +- .../RestTemplateConfiguration.java | 2 - .../configuration/Swagger2Configuration.java | 2 - .../configuration/WebMvcConfiguration.java | 2 - .../WebSecurityConfiguration.java | 2 - .../database/DataSourceConfiguration.java | 60 ++++++++++ .../database/DataSourceContextHolder.java | 58 ++++++++++ .../database/DataSourceTypeEnum.java | 17 +++ .../DynamicDataSourceInterceptor.java | 85 ++++++++++++++ .../database/DynamicRoutingDataSource.java | 15 +++ .../database/MyBatisPlusConfiguration.java | 26 ++--- .../redis/RedisConfiguration.java | 4 +- 36 files changed, 502 insertions(+), 127 deletions(-) rename spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/{ExceptionControllerAdvice.java => CommonExceptionControllerAdvice.java} (85%) create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/DatabaseExceptionControllerAdvice.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceContextHolder.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceTypeEnum.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicDataSourceInterceptor.java create mode 100644 spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicRoutingDataSource.java diff --git a/auth-center/src/main/resources/application-development-docker.yml b/auth-center/src/main/resources/application-development-docker.yml index ee3b3345..1614a6da 100644 --- a/auth-center/src/main/resources/application-development-docker.yml +++ b/auth-center/src/main/resources/application-development-docker.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: true datasource: - url: jdbc:mysql://maf-mysql-server-master-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-development-docker port: 6379 diff --git a/auth-center/src/main/resources/application-development-local.yml b/auth-center/src/main/resources/application-development-local.yml index b59a08bb..0287c76f 100644 --- a/auth-center/src/main/resources/application-development-local.yml +++ b/auth-center/src/main/resources/application-development-local.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: true datasource: - url: jdbc:mysql://localhost:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://localhost:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://localhost:3307/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 diff --git a/auth-center/src/main/resources/application-production.yml b/auth-center/src/main/resources/application-production.yml index 05b7ae8e..a53b1a35 100644 --- a/auth-center/src/main/resources/application-production.yml +++ b/auth-center/src/main/resources/application-production.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-production port: 6379 diff --git a/auth-center/src/main/resources/application-stage.yml b/auth-center/src/main/resources/application-stage.yml index 1c82ad8e..60a7a0b9 100644 --- a/auth-center/src/main/resources/application-stage.yml +++ b/auth-center/src/main/resources/application-stage.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-stage port: 6379 diff --git a/auth-center/src/main/resources/application-test.yml b/auth-center/src/main/resources/application-test.yml index a034608a..f38aee29 100644 --- a/auth-center/src/main/resources/application-test.yml +++ b/auth-center/src/main/resources/application-test.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-test port: 6379 diff --git a/auth-center/src/main/resources/application.yml b/auth-center/src/main/resources/application.yml index 51caf7ca..1bed29fc 100644 --- a/auth-center/src/main/resources/application.yml +++ b/auth-center/src/main/resources/application.yml @@ -40,7 +40,6 @@ spring: ip-address: ${spring.cloud.client.ip-address} health-check-critical-timeout: 15s datasource: - name: muscle_and_fitness type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/docker/.env b/docker/.env index 108178d0..b6ea0259 100644 --- a/docker/.env +++ b/docker/.env @@ -64,7 +64,8 @@ JAVA_TOOL_OPTIONS="-Xms128m -Xmx128m -Dspring.profiles.active=${SPRING_ENVIRONME ################################################## MAF_MYSQL_ROOT_PASSWORD=jm@mysql MAF_MYSQL_DATABASE=muscle_and_fitness -MAF_MYSQL_USER=maf_mysql_rw +MAF_MYSQL_USER_RW=maf_mysql_rw +MAF_MYSQL_USER_R=maf_mysql_r MAF_MYSQL_PASSWORD=maf@mysql MAF_ELASTICSEARCH_PASSWORD=maf@elasticsearch MAF_TIMEZONE=Asia/Hong_Kong diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 30eeb5d2..e6fc1a45 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -11,7 +11,7 @@ services: MYSQL_ROOT_HOST: "%" MYSQL_ROOT_PASSWORD: ${MAF_MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MAF_MYSQL_DATABASE} - MYSQL_USER: ${MAF_MYSQL_USER} + MYSQL_USER: ${MAF_MYSQL_USER_RW} MYSQL_PASSWORD: ${MAF_MYSQL_PASSWORD} TZ: ${MAF_TIMEZONE} ports: @@ -39,7 +39,7 @@ services: MYSQL_ROOT_HOST: "%" MYSQL_ROOT_PASSWORD: ${MAF_MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MAF_MYSQL_DATABASE} - MYSQL_USER: ${MAF_MYSQL_USER} + MYSQL_USER: ${MAF_MYSQL_USER_R} MYSQL_PASSWORD: ${MAF_MYSQL_PASSWORD} ports: - "3307:3306" diff --git a/maf-mis/src/main/resources/application-development-docker.yml b/maf-mis/src/main/resources/application-development-docker.yml index 091478aa..2462ff80 100644 --- a/maf-mis/src/main/resources/application-development-docker.yml +++ b/maf-mis/src/main/resources/application-development-docker.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: true datasource: - url: jdbc:mysql://maf-mysql-server-master-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-development-docker:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-development-docker port: 6379 diff --git a/maf-mis/src/main/resources/application-development-local.yml b/maf-mis/src/main/resources/application-development-local.yml index 7581c12e..09d29f2c 100644 --- a/maf-mis/src/main/resources/application-development-local.yml +++ b/maf-mis/src/main/resources/application-development-local.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: true datasource: - url: jdbc:mysql://localhost:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://localhost:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://localhost:3307/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 diff --git a/maf-mis/src/main/resources/application-production.yml b/maf-mis/src/main/resources/application-production.yml index eb630758..da4b9113 100644 --- a/maf-mis/src/main/resources/application-production.yml +++ b/maf-mis/src/main/resources/application-production.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-production:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-production port: 6379 diff --git a/maf-mis/src/main/resources/application-stage.yml b/maf-mis/src/main/resources/application-stage.yml index 453882ba..e0765ab7 100644 --- a/maf-mis/src/main/resources/application-stage.yml +++ b/maf-mis/src/main/resources/application-stage.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-stage:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-stage port: 6379 diff --git a/maf-mis/src/main/resources/application-test.yml b/maf-mis/src/main/resources/application-test.yml index a0b5b09e..e0691e21 100644 --- a/maf-mis/src/main/resources/application-test.yml +++ b/maf-mis/src/main/resources/application-test.yml @@ -4,9 +4,18 @@ spring: devtools: add-properties: false datasource: - url: jdbc:mysql://maf-mysql-server-master-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true - username: maf_mysql_rw - password: maf@mysql + dynamic: + datasource: + master: + url: jdbc:mysql://maf-mysql-server-master-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_rw + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver + slave1: + url: jdbc:mysql://maf-mysql-server-slave-test:3306/muscle_and_fitness?useSSL=true&useUnicode=true + username: maf_mysql_r + password: maf@mysql + driver-class-name: com.mysql.cj.jdbc.Driver redis: host: maf-redis-test port: 6379 diff --git a/maf-mis/src/main/resources/application.yml b/maf-mis/src/main/resources/application.yml index fb6edbcb..59ac3dbe 100644 --- a/maf-mis/src/main/resources/application.yml +++ b/maf-mis/src/main/resources/application.yml @@ -42,7 +42,6 @@ spring: ip-address: ${spring.cloud.client.ip-address} health-check-critical-timeout: 15s datasource: - name: muscle_and_fitness type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/pom.xml b/pom.xml index d9729ffe..8a497d19 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ 2.4.1 1.2.6 3.4.3.1 + 3.4.0 5.7.2 30.0-jre 2.0.8 diff --git a/spring-cloud-starter/pom.xml b/spring-cloud-starter/pom.xml index 8c89c8d0..c4baba1e 100644 --- a/spring-cloud-starter/pom.xml +++ b/spring-cloud-starter/pom.xml @@ -118,6 +118,11 @@ mybatis-plus-boot-starter ${mybatis-plus-boot-starter.version} + + com.baomidou + dynamic-datasource-spring-boot-starter + ${dynamic-datasource-spring-boot-starter.version} + mysql mysql-connector-java diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/MafAutoConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/MafAutoConfiguration.java index 30c47fbb..9d75123d 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/MafAutoConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/MafAutoConfiguration.java @@ -1,6 +1,8 @@ package com.jmsoftware.maf.springcloudstarter; -import com.jmsoftware.maf.springcloudstarter.aspect.ExceptionControllerAdvice; +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.jmsoftware.maf.springcloudstarter.aspect.CommonExceptionControllerAdvice; +import com.jmsoftware.maf.springcloudstarter.aspect.DatabaseExceptionControllerAdvice; import com.jmsoftware.maf.springcloudstarter.aspect.WebRequestLogAspect; import com.jmsoftware.maf.springcloudstarter.configuration.*; import com.jmsoftware.maf.springcloudstarter.controller.CommonController; @@ -17,10 +19,9 @@ import com.jmsoftware.maf.springcloudstarter.service.impl.CommonServiceImpl; import com.jmsoftware.maf.springcloudstarter.sftp.SftpConfiguration; import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.condition.SearchStrategy; +import org.apache.ibatis.exceptions.PersistenceException; +import org.mybatis.spring.MyBatisSystemException; +import org.springframework.boot.autoconfigure.condition.*; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -71,9 +72,16 @@ public void postConstruct() { @Bean @ConditionalOnMissingBean - public ExceptionControllerAdvice exceptionControllerAdvice() { - log.warn("Initial bean: '{}'", ExceptionControllerAdvice.class.getSimpleName()); - return new ExceptionControllerAdvice(); + public CommonExceptionControllerAdvice exceptionControllerAdvice() { + log.warn("Initial bean: '{}'", CommonExceptionControllerAdvice.class.getSimpleName()); + return new CommonExceptionControllerAdvice(); + } + + @Bean + @ConditionalOnClass({MyBatisSystemException.class, MybatisPlusException.class, PersistenceException.class}) + public DatabaseExceptionControllerAdvice databaseExceptionControllerAdvice() { + log.warn("Initial bean: '{}'", DatabaseExceptionControllerAdvice.class.getSimpleName()); + return new DatabaseExceptionControllerAdvice(); } @Bean diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/CommonExceptionControllerAdvice.java similarity index 85% rename from spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java rename to spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/CommonExceptionControllerAdvice.java index 218d04ee..84117dd9 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/ExceptionControllerAdvice.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/CommonExceptionControllerAdvice.java @@ -2,14 +2,11 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; -import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; import com.jmsoftware.maf.common.bean.ResponseBodyBean; import com.jmsoftware.maf.common.exception.BaseException; import com.jmsoftware.maf.springcloudstarter.util.RequestUtil; import lombok.extern.slf4j.Slf4j; import lombok.val; -import org.apache.ibatis.exceptions.PersistenceException; -import org.mybatis.spring.MyBatisSystemException; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -40,7 +37,7 @@ **/ @Slf4j @RestControllerAdvice -public class ExceptionControllerAdvice { +public class CommonExceptionControllerAdvice { /** *

Exception handler.

*

ATTENTION: In this method, cannot throw any exception.

@@ -171,37 +168,6 @@ public ResponseBodyBean handleException(HttpServletRequest request, HttpServl null); } - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(MyBatisSystemException.class) - public ResponseBodyBean handleMyBatisSystemException(HttpServletRequest request, - MyBatisSystemException exception) { - requestLog(request); - log.error("MyBatisSystemException message: {}", exception.getMessage()); - return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, - String.format("MyBatisSystemException message: %s", - removeLineSeparator(exception.getMessage()))); - } - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(MybatisPlusException.class) - public ResponseBodyBean handleMybatisPlusException(HttpServletRequest request, MybatisPlusException exception) { - requestLog(request); - log.error("MybatisPlusException message: {}", exception.getMessage()); - return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, - String.format("MybatisPlusException message: %s", - removeLineSeparator(exception.getMessage()))); - } - - @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) - @ExceptionHandler(PersistenceException.class) - public ResponseBodyBean handlePersistenceException(HttpServletRequest request, PersistenceException exception) { - requestLog(request); - log.error("PersistenceException message: {}", exception.getMessage()); - return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, - String.format("PersistenceException message: %s", - removeLineSeparator(exception.getMessage()))); - } - @ExceptionHandler(UndeclaredThrowableException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseBodyBean handleError(UndeclaredThrowableException exception) { diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/DatabaseExceptionControllerAdvice.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/DatabaseExceptionControllerAdvice.java new file mode 100644 index 00000000..87d43849 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/aspect/DatabaseExceptionControllerAdvice.java @@ -0,0 +1,106 @@ +package com.jmsoftware.maf.springcloudstarter.aspect; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException; +import com.jmsoftware.maf.common.bean.ResponseBodyBean; +import com.jmsoftware.maf.springcloudstarter.util.RequestUtil; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.ibatis.exceptions.PersistenceException; +import org.mybatis.spring.MyBatisSystemException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.context.support.DefaultMessageSourceResolvable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; +import java.util.Objects; + +/** + *

DatabaseExceptionControllerAdvice

+ *

+ * Exception advice for database exception. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 10:41 AM + **/ +@Slf4j +@RestControllerAdvice +@ConditionalOnClass({MyBatisSystemException.class, MybatisPlusException.class, PersistenceException.class}) +public class DatabaseExceptionControllerAdvice { + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(MyBatisSystemException.class) + public ResponseBodyBean handleMyBatisSystemException(HttpServletRequest request, + MyBatisSystemException exception) { + requestLog(request); + log.error("MyBatisSystemException message: {}", exception.getMessage()); + return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, + String.format("MyBatisSystemException message: %s", + removeLineSeparator(exception.getMessage()))); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(MybatisPlusException.class) + public ResponseBodyBean handleMybatisPlusException(HttpServletRequest request, MybatisPlusException exception) { + requestLog(request); + log.error("MybatisPlusException message: {}", exception.getMessage()); + return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, + String.format("MybatisPlusException message: %s", + removeLineSeparator(exception.getMessage()))); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(PersistenceException.class) + public ResponseBodyBean handlePersistenceException(HttpServletRequest request, PersistenceException exception) { + requestLog(request); + log.error("PersistenceException message: {}", exception.getMessage()); + return ResponseBodyBean.ofStatus(HttpStatus.INTERNAL_SERVER_ERROR, + String.format("PersistenceException message: %s", + removeLineSeparator(exception.getMessage()))); + } + + private void requestLog(HttpServletRequest request) { + log.error("Exception occurred when [{}] requested access. Request URL: [{}] {}", + RequestUtil.getRequestIpAndPort(request), request.getMethod(), request.getRequestURL()); + } + + /** + * Get field error message from exception. If two or more fields do not pass Spring Validation check, then will + * return the 1st error message of the error field. + * + * @param exception MethodArgumentNotValidException + * @return field error message + */ + private String getFieldErrorMessageFromException(MethodArgumentNotValidException exception) { + try { + val firstErrorField = + (DefaultMessageSourceResolvable) Objects.requireNonNull(exception.getBindingResult() + .getAllErrors() + .get(0) + .getArguments())[0]; + val firstErrorFieldName = firstErrorField.getDefaultMessage(); + val firstErrorFieldMessage = exception.getBindingResult().getAllErrors().get(0).getDefaultMessage(); + return String.format("%s %s", firstErrorFieldName, firstErrorFieldMessage); + } catch (Exception e) { + log.error("Exception occurred when get field error message from exception. Exception message: {}", + e.getMessage(), e); + return HttpStatus.BAD_REQUEST.getReasonPhrase(); + } + } + + /** + * Remove line separator string. + * + * @param source the source + * @return the string + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 12/24/2020 11:22 AM + */ + private String removeLineSeparator(String source) { + if (StrUtil.isBlank(source)) { + return "source is blank"; + } + return source.replaceAll(System.lineSeparator(), " "); + } +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/AsyncConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/AsyncConfiguration.java index 007e5669..f85378cc 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/AsyncConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/AsyncConfiguration.java @@ -3,7 +3,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @@ -15,7 +14,6 @@ **/ @Slf4j @EnableAsync -@Configuration @RequiredArgsConstructor public class AsyncConfiguration { @Bean diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/ExcelImportConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/ExcelImportConfiguration.java index 70ac28f5..a42307d3 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/ExcelImportConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/ExcelImportConfiguration.java @@ -1,23 +1,14 @@ package com.jmsoftware.maf.springcloudstarter.configuration; -import cn.hutool.core.util.ObjectUtil; -import com.google.common.collect.Lists; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import lombok.val; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; -import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.annotation.PostConstruct; -import javax.validation.Valid; import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import java.util.ArrayList; -import java.util.List; /** *

CustomConfiguration

@@ -30,7 +21,6 @@ @Data @Slf4j @Validated -@Component @RefreshScope @ConfigurationProperties(prefix = "maf.configuration.excel") public class ExcelImportConfiguration { diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafConfiguration.java index 6c04d484..4aa2f36f 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafConfiguration.java @@ -6,7 +6,6 @@ import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.annotation.PostConstruct; @@ -28,7 +27,6 @@ @Data @Slf4j @Validated -@Component @ConfigurationProperties(prefix = "maf.configuration") public class MafConfiguration { /** diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafProjectProperty.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafProjectProperty.java index d08a145e..949e318b 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafProjectProperty.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MafProjectProperty.java @@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.info.BuildProperties; -import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.annotation.PostConstruct; @@ -20,7 +19,6 @@ @Data @Slf4j @Validated -@Component @SuppressWarnings("jol") @ConfigurationProperties(prefix = "maf.project-property") public class MafProjectProperty { diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioConfiguration.java index 30de15a2..c5011ad5 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/MinioConfiguration.java @@ -4,9 +4,7 @@ import io.minio.MinioClient; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; /** @@ -17,7 +15,6 @@ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/7/21 9:58 PM **/ @Slf4j -@Configuration @Import({ MinioProperty.class }) diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RabbitmqConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RabbitmqConfiguration.java index 404503ff..05e03a77 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RabbitmqConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RabbitmqConfiguration.java @@ -3,12 +3,10 @@ import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.amqp.core.TopicExchange; -import org.springframework.amqp.rabbit.connection.ConnectionFactory; -import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; /** * Description: RabbitmqConfiguration, change description here. @@ -16,7 +14,7 @@ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 3/9/2021 11:31 AM **/ @Slf4j -@Configuration +@ConditionalOnClass({TopicExchange.class}) public class RabbitmqConfiguration { public final String topicExchangeName; diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RestTemplateConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RestTemplateConfiguration.java index 1c539655..cfb98fdf 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RestTemplateConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/RestTemplateConfiguration.java @@ -6,7 +6,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestTemplate; @@ -17,7 +16,6 @@ * @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 1/29/2021 4:29 PM **/ @Slf4j -@Configuration public class RestTemplateConfiguration { @Bean @LoadBalanced diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/Swagger2Configuration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/Swagger2Configuration.java index 9a9c6bf8..f1386b58 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/Swagger2Configuration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/Swagger2Configuration.java @@ -4,7 +4,6 @@ import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; @@ -25,7 +24,6 @@ * @date 2019-02-07 16:15 **/ @Slf4j -@Configuration @EnableSwagger2WebMvc @RequiredArgsConstructor public class Swagger2Configuration { diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebMvcConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebMvcConfiguration.java index 7c947fae..82ec5f20 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebMvcConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebMvcConfiguration.java @@ -1,7 +1,6 @@ package com.jmsoftware.maf.springcloudstarter.configuration; import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -13,7 +12,6 @@ * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com * @date 1/23/20 9:02 AM **/ -@Configuration @RequiredArgsConstructor public class WebMvcConfiguration implements WebMvcConfigurer { /** diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebSecurityConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebSecurityConfiguration.java index 4991f93c..f3c4475e 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebSecurityConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/configuration/WebSecurityConfiguration.java @@ -2,7 +2,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -20,7 +19,6 @@ * @date 5/2/20 11:41 PM **/ @Slf4j -@Configuration @EnableWebSecurity public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter { @PostConstruct diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java new file mode 100644 index 00000000..9e18ce10 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceConfiguration.java @@ -0,0 +1,60 @@ +package com.jmsoftware.maf.springcloudstarter.database; + +import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; + +/** + * Description: DataSourceConfiguration, change description here. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 8:15 AM + **/ +@Slf4j +@ConditionalOnClass({MybatisPlusAutoConfiguration.class}) +@AutoConfigureBefore({MybatisPlusAutoConfiguration.class}) +public class DataSourceConfiguration { + @Bean + @ConfigurationProperties("spring.datasource.dynamic.datasource.master") + public DataSource masterDataSource() { + log.warn("Initial bean: masterDataSource"); + return DruidDataSourceBuilder.create().build(); + } + + @Bean + @ConfigurationProperties("spring.datasource.dynamic.datasource.slave1") + public DataSource slave1DataSource() { + log.warn("Initial bean: slave1DataSource"); + return DruidDataSourceBuilder.create().build(); + } + + @Bean + @Primary + public DynamicRoutingDataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, + @Qualifier("slave1DataSource") DataSource slave1DataSource) { + log.warn("Loading masterDataSource and slave1DataSource as DynamicDataSource"); + val targetDataSources = new HashMap<>(4); + targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource); + targetDataSources.put(DataSourceTypeEnum.SLAVE1, slave1DataSource); + val dynamicDataSource = new DynamicRoutingDataSource(); + dynamicDataSource.setDefaultTargetDataSource(masterDataSource); + dynamicDataSource.setTargetDataSources(targetDataSources); + return dynamicDataSource; + } + + @Bean + public PlatformTransactionManager platformTransactionManager(DynamicRoutingDataSource dynamicRoutingDataSource) { + return new DataSourceTransactionManager(dynamicRoutingDataSource); + } +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceContextHolder.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceContextHolder.java new file mode 100644 index 00000000..055f0510 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceContextHolder.java @@ -0,0 +1,58 @@ +package com.jmsoftware.maf.springcloudstarter.database; + +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Description: DatabaseContextHolder, thread-safe + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 12:07 AM + **/ +@Slf4j +public class DataSourceContextHolder { + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + private static final AtomicInteger COUNTER = new AtomicInteger(-1); + private static final int MAX_COUNT = 9999; + + public static DataSourceTypeEnum get() { + return Optional.ofNullable(CONTEXT_HOLDER.get()).orElse(DataSourceTypeEnum.MASTER); + } + + public static void set(DataSourceTypeEnum dbType) { + CONTEXT_HOLDER.set(dbType); + } + + static void master() { + set(DataSourceTypeEnum.MASTER); + if (log.isDebugEnabled()) { + log.debug("Current data source -> {}", DataSourceTypeEnum.MASTER); + } + } + + static void slave() { + // Simple load-balance for more slave clusters + val index = COUNTER.getAndIncrement() % 2; + if (COUNTER.get() > MAX_COUNT) { + COUNTER.set(-1); + } + // if (index == 0) { + // set(DataSourceTypeEnum.SLAVE1); + // }else { + // set(DataSourceTypeEnum.SLAVE2); + // } + set(DataSourceTypeEnum.SLAVE1); + if (log.isDebugEnabled()) { + log.debug("Current data source -> {}, index: {}", DataSourceTypeEnum.MASTER, index); + } + } + + static void clear() { + CONTEXT_HOLDER.remove(); + if (log.isDebugEnabled()) { + log.debug("Cleared CONTEXT_HOLDER"); + } + } +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceTypeEnum.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceTypeEnum.java new file mode 100644 index 00000000..17088808 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DataSourceTypeEnum.java @@ -0,0 +1,17 @@ +package com.jmsoftware.maf.springcloudstarter.database; + +/** + * Description: DataSourceTypeEnum, change description here. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 12:03 AM + **/ +public enum DataSourceTypeEnum { + /** + * Master + */ + MASTER, + /** + * Slave 1 + */ + SLAVE1 +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicDataSourceInterceptor.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicDataSourceInterceptor.java new file mode 100644 index 00000000..4fbf44b2 --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicDataSourceInterceptor.java @@ -0,0 +1,85 @@ +package com.jmsoftware.maf.springcloudstarter.database; + +import lombok.extern.slf4j.Slf4j; +import lombok.val; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.executor.keygen.SelectKeyGenerator; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.mapping.SqlCommandType; +import org.apache.ibatis.plugin.*; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import java.util.Objects; + +/** + * Description: DynamicDataSourceInterceptor, change description here. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 12:26 AM + **/ +@Slf4j +@Intercepts({ + @Signature( + type = Executor.class, + method = "update", + args = {MappedStatement.class, Object.class} + ), + @Signature( + type = Executor.class, + method = "query", + args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class + }) +}) +public class DynamicDataSourceInterceptor implements Interceptor { + /** + * The constant WRITE_OPERATION_SQL_REGEX. Preserved this constant for future use. + */ + @SuppressWarnings("unused") + private static final String WRITE_OPERATION_SQL_REGEX = ".*INSERT.*|.*UPDATE.*|.*DELETE.*"; + + @Override + public Object intercept(Invocation invocation) throws Throwable { + // Check if the transaction synchronization is active. It it was, would use MASTER data source + val actualTransactionActive = TransactionSynchronizationManager.isActualTransactionActive(); + // MyBatis CRUD args + val args = invocation.getArgs(); + // MappedStatement SqlCommandType + val mappedStatement = (MappedStatement) args[0]; + // Default using master + DataSourceContextHolder.master(); + if (!actualTransactionActive) { + // SqlCommandType.SELECT, UNKNOWN|INSERT|UPDATE|DELETE|SELECT|FLUSH + if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) { + // If it's the SQL return primary key as result + if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) { + log.warn("Calling ID-return SQL, {}", SelectKeyGenerator.SELECT_KEY_SUFFIX); + DataSourceContextHolder.master(); + } else { + if (Objects.deepEquals(SqlCommandType.SELECT, mappedStatement.getSqlCommandType())) { + DataSourceContextHolder.slave(); + } else { + DataSourceContextHolder.master(); + } + } + } + } else { + DataSourceContextHolder.master(); + } + log.warn("SQL statement [{}], SqlCommandType: [{}], using 🐬 [{}] data source", mappedStatement.getId(), + mappedStatement.getSqlCommandType().name(), DataSourceContextHolder.get()); + val result = invocation.proceed(); + DataSourceContextHolder.clear(); + return result; + } + + @Override + public Object plugin(Object target) { + if (target instanceof Executor) { + return Plugin.wrap(target, this); + } else { + return target; + } + } +} + diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicRoutingDataSource.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicRoutingDataSource.java new file mode 100644 index 00000000..89b5b4fb --- /dev/null +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/DynamicRoutingDataSource.java @@ -0,0 +1,15 @@ +package com.jmsoftware.maf.springcloudstarter.database; + +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * Description: DynamicDataSource, change description here. + * + * @author Johnny Miller (锺俊), email: johnnysviva@outlook.com, date: 6/27/2021 12:06 AM + **/ +public class DynamicRoutingDataSource extends AbstractRoutingDataSource { + @Override + protected Object determineCurrentLookupKey() { + return DataSourceContextHolder.get(); + } +} diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java index 64fa1e42..e87593a0 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/database/MyBatisPlusConfiguration.java @@ -1,13 +1,15 @@ package com.jmsoftware.maf.springcloudstarter.database; import com.baomidou.mybatisplus.annotation.DbType; -import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration; import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import lombok.extern.slf4j.Slf4j; import lombok.val; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.transaction.annotation.EnableTransactionManagement; /** @@ -17,6 +19,10 @@ **/ @Slf4j @Configuration +@Import({ + DataSourceConfiguration.class +}) +@ConditionalOnClass({MybatisPlusAutoConfiguration.class}) @EnableTransactionManagement public class MyBatisPlusConfiguration { @Bean @@ -33,22 +39,10 @@ public BlockAttackInnerInterceptor blockAttackInnerInterceptor() { return new BlockAttackInnerInterceptor(); } - /** - * Mybatis plus interceptor mybatis plus interceptor. - * - * @param paginationInnerInterceptor the pagination inner interceptor - * @param blockAttackInnerInterceptor the block attack inner interceptor - * @return the mybatis plus interceptor - * @see MybatisPlusInterceptor - */ @Bean - public MybatisPlusInterceptor mybatisPlusInterceptor(PaginationInnerInterceptor paginationInnerInterceptor, - BlockAttackInnerInterceptor blockAttackInnerInterceptor) { - log.warn("Initial bean: '{}'", MybatisPlusInterceptor.class.getSimpleName()); - MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); - mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor); - mybatisPlusInterceptor.addInnerInterceptor(blockAttackInnerInterceptor); - return mybatisPlusInterceptor; + public DynamicDataSourceInterceptor dynamicDataSourceInterceptor() { + log.warn("Initial bean: '{}'", DynamicDataSourceInterceptor.class.getSimpleName()); + return new DynamicDataSourceInterceptor(); } @Bean diff --git a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/redis/RedisConfiguration.java b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/redis/RedisConfiguration.java index d8e7de10..c8a181be 100644 --- a/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/redis/RedisConfiguration.java +++ b/spring-cloud-starter/src/main/java/com/jmsoftware/maf/springcloudstarter/redis/RedisConfiguration.java @@ -3,8 +3,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory; @@ -23,11 +23,11 @@ * @author 钟俊(zhongjun), email: zhongjun@toguide.cn, date: 12/30/2020 1:08 PM **/ @Slf4j -@Configuration @RequiredArgsConstructor @Import({ RedisCachingConfiguration.class }) +@ConditionalOnClass({RedisConnectionFactory.class}) public class RedisConfiguration { private final ObjectMapper objectMapper;