Data source switching method

Springboot provides AbstractRoutingDataSource abstract class, the class name mean data source routing, Let the user can choose according to need to switch the current source this class provides an abstract method determineCurrentLookupKey (), switch springboot will call this method when the data source, so only need to implement the method, in this method returns the need to switch the data source name

The source code interpretation

1. You can see from the class diagram AbstractRoutingDataSource class implements the DataSource interface (not bottom), the requirements to achieve a method getConnection (), obtaining the DB connection

2. AbstractRoutingDataSource implements these two methods

Including determineTargetDataSource () call determineCurrentLookupKey () method, apply to the current set of search key, Use the find key in the context this.resolvedDataSources property to try to get the DataSource object, which is the currently connected DataSource

3. Where is this. ResolvedDataSources maintained? AbstractRoutingDataSource class implements the InitializingBean class afterPropertiesSet () method, which all property is set in the bean will change after the completion of this method is called, You can see the information this. ResolvedDataSources fetched from this. TargetDataSources;

So just change this.targetdatasources and trigger afterPropertiesSet() to change this.resolveddatasources; Subsequent change determineCurrentLookupKey () return values (key), in the calling getConnection () can get to the specified data source

Multi-tenant service background

In multi-tenant service scenarios, each tenant usually has an independent database (whether an independent data source instance is handled according to actual needs). Data of each tenant is isolated at the database level, so that data of different tenants can be mixed when detailed services are written. However, the demand for flexible switching of data sources comes with the need to encapsulate a set of methods, which can easily switch to the corresponding data sources according to the provided tenant code when writing business

Provides the switching mode

1. Annotation mode switching Provides an annotation, which can be switched according to the tenant code or the data source name specified in the configuration file

2. Direct call method switching provides a tenant RDS switching class that calls method switching when writing business code, which allows tenant code to be passed in variables without prior knowledge

Implementation Step Outline

Add POM dependencies and configure data source information. 2. Write data source configuration classes to inject data source configuration information into container 3. Write DynamicDataSource AbstractRoutingDataSource abstract class, the class hierarchy to maintain the current data source information, provide 4 switching method. Write tenant RDS switching class, which is called uniformly when business switches data sources 5. Write custom annotations 6. Write the aspect class, set the join point directly on the custom annotation written, and call the RDS switcher class to switch the data source 7 according to parameters and so on. Exception class, exception enumeration class, specification exception throwing

The detailed steps

1. Pom relies on adding and configuring data source information

pom.xml

<dependencies>
	<! Mysql > select * from 'mysql'; mysql > select * from 'mysql';
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.38</version>
		<scope>runtime</scope>
	</dependency>
	<! -- aop -->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-aop</artifactId>
	</dependency>
	<! -- druid data source -->
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>druid-spring-boot-starter</artifactId>
		<version>1.1.10</version>
	</dependency>
</dependencies>
Copy the code

application.yml

# master configuration
spring:
  # data source configuration
  datasource:
    Change data source to druid
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver This is based on the mysql-connector-java version
    # druid configuration
    druid:
      # primary data source
      master:
        driver-class-name: com.mysql.jdbc.Driver
        Default database connection (config library)
        url: jdbc:mysql://xxx:xxx/config? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        username: xxx
        password: xxx
      Increment db configuration
      db1:
        driver-class-name: com.mysql.jdbc.Driver This is based on the mysql-connector-java version
        url: jdbc:mysql://xxx:xxx/mydb? useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
        username: xxx
        password: xxx
      initial-size: 5 Number of physical connections established during initialization
      max-active: 30 # Maximum number of connection pools
      min-idle: 5 Minimum number of connection pools
      max-wait: 60000 Maximum wait time to get a connection in milliseconds
      time-between-eviction-runs-millis: 60000 Configure how often to detect idle connections that need to be closed, in milliseconds
      min-evictable-idle-time-millis: 300000 The minimum time for a connection to remain idle without being expelled
      validation-query: SELECT 1 FROM DUAL SQL to check whether a connection is valid
      test-while-idle: true # It is recommended to set this parameter to true, which does not affect performance and ensures security. Apply for connection, if free time is more than timeBetweenEvictionRunsMillis, performing validationQuery test connection is valid.
      test-on-borrow: false Execute validationQuery when applying for a connection to check whether the connection is valid. This configuration can degrade performance.
      test-on-return: false Execute validationQuery when returning a connection to check if the connection is valid. This configuration can degrade performance.
      pool-prepared-statements: true PreparedStatement (PSCache) PSCache provides a huge performance boost for databases that support cursors, such as Oracle. You are advised to disable this function in mysql.
      max-pool-prepared-statement-per-connection-size: 50 To enable PSCache, the configuration must be greater than 0. When greater than 0, poolPreparedStatements automatically triggers a change to true.
      filters: stat,wall,log4j2 # Configure the filters for monitoring statistics interception. After removing the filters, the SQL on the monitoring interface cannot be counted. Configure the filters for monitoring statistics interception, stat: monitoring statistics, log4j: logging, and Wall: defending against SQL injection
      connection-properties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=500 Enable mergeSql via connectProperties; Slow SQL record
      use-global-data-source-stat: true # DruidDataSource datasource
      stat-view-servlet:
        allow: ' ' Allow: 127.0.0.1,192.168.163.1
        deny: ' ' # IP blacklist (deny takes precedence over allow if common)
        login-password: xxxxxx # login password
        login-username: admin # login name
        reset-enable: false # Disable the Reset All function on HTML pages
        url-pattern: /druid/* # configuration DruidStatViewServlet
      web-stat-filter: # configuration DruidStatFilter
        enabled: true
        exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
        url-pattern: / *
Copy the code

Druid configuration contains some druid configurations, which can be customized based on service requirements. 2. . Among them, the spring datasource. The druid. Master give priority to the data source, also is the repository data source, the tenant library data source connection information will be obtained in the repository, spring. The datasource. The druid. Increasing db1 as data source, the db1 can be named specific business name of a repository I’m just going to call it DB1


2. Write the data source configuration class to inject the data source configuration information into the container

The data source configuration class DataSourceConfig

@Configuration
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) / / exclude DataSourceAutoConfiguration automatic configuration, avoid circular calls
public class DataSourceConfig {
    /** * Default data source **@return* /
    @Bean(DataSourceConstant.DATA_SOURCE_MASTER)
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource dataSourceMaster(a) {
        return DruidDataSourceBuilder.create().build();
    }

    /** * increments the data source **@return* /
    @Bean(DataSourceConstant.DATA_SOURCE_DB_1)
    @ConfigurationProperties("spring.datasource.druid.db1")
    public DataSource dataSourceDb1(a) {
        return DruidDataSourceBuilder.create().build();
    }


    /** * Set dynamic data source as primary data source **@return* /
    @Bean
    @Primary
    public DynamicDataSource dataSource(a) {
        // Set the data source to map
        DynamicDataSource.setDataSourceMap(DataSourceConstant.DATA_SOURCE_MASTER, dataSourceMaster());
        DynamicDataSource.setDataSourceMap(DataSourceConstant.DATA_SOURCE_DB_1, dataSourceDb1());
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // Use Map to save multiple data sources and set them to a dynamic data source object. This value will eventually be set to resolvedDataSources in afterPropertiesSet
        dynamicDataSource.setTargetDataSources(DynamicDataSource.dataSourceMap);
        returndynamicDataSource; }}Copy the code

Data source constant class

public class DataSourceConstant {
    private DataSourceConstant(a) {}/** * The name of the dataSource is the same as the name of the configuration file
    public static final String DATA_SOURCE_MASTER = "dataSourceMaster";

    /** * dbdatasource = dataSource; /** * dbdatasource = dataSource; /** * dbdatasource = dataSource DBN can also be based on */
    public static final String DATA_SOURCE_DB_1 = "dataSourceDb1";
}
Copy the code

Here to first DynamicDataSource. DataSourceMap will write two configured data source connection information, and set to dynamic data source object, the value will be set in the afterPropertiesSet on resolvedDataSources


3. Write DynamicDataSource AbstractRoutingDataSource abstract class, the class hierarchy to maintain the current data source information, provide switching methods

DynamicDataSource class

public class DynamicDataSource extends AbstractRoutingDataSource {
    /** * Stores the data source key */ of the current thread
    private static final ThreadLocal<String> DATA_SOURCE_KEY = ThreadLocal.withInitial(() -> DataSourceConstant.DATA_SOURCE_MASTER);

    /** * Data source map */
    public static Map<Object, Object> dataSourceMap = new ConcurrentHashMap<>(1000);

    /** * get the data source key **@return* /
    public static String getDataSourceKey(a) {
        return DynamicDataSource.DATA_SOURCE_KEY.get();
    }

    /** * Sets the data source key **@param key
     */
    public static void setDataSourceKey(String key) {
        DynamicDataSource.DATA_SOURCE_KEY.set(key);
    }

    /** * Remove the default data source key */
    public static void remove(a) {
        DynamicDataSource.DATA_SOURCE_KEY.remove();
    }

    /** * switches to the default data source */
    public static void setDataSourceDefault(a) {
        setDataSource(DataSourceConstant.DATA_SOURCE_MASTER);
    }

    /** * Switch to the specified data source if the key exists in the dataSourceMap. ** If the map exists in the dataSourceMap, check whether the map exists@param dataSource
     */
    public static void setDataSource(String dataSource) {
        setDataSourceKey(dataSource);
        . / / InitializingBean afterPropertiesSet () is instantiated, all attributes of the bean after initialization calls; But if the bean is taken directly from the container, there is no need to instantiate the action
        / / here directly to the dataSource, manual trigger, let AbstractRoutingDataSource. ResolvedDataSources assignment again, to this kind of maintaining the value of the map
        DynamicDataSource dynamicDataSource = (DynamicDataSource) SpringContextUtil.getBean("dataSource");
        dynamicDataSource.afterPropertiesSet();
    }

    /** * Get tenant data source configuration **@param tenantCode
     * @return* /
    public static Object getDataSourceMap(String tenantCode) {
        return DynamicDataSource.dataSourceMap.get(tenantCode);
    }

    /** * Set map **@param dataSourceName
     * @return void
     * @author Linzs
     * @date2021/8/28 11:53 * * /
    public static void setDataSourceMap(String dataSourceName, Object dataSource) {
        dataSourceMap.put(dataSourceName, dataSource);
    }

    /** * Set map **@param dataSourceName
     * @return void
     * @author Linzs
     * @date2021/8/28 11:53 * * /
    public static void setDataSourceMap(String dataSourceName) {
        dataSourceMap.put(dataSourceName, SpringContextUtil.getBean(dataSourceName));
    }

    /** * Set tenant data source configuration **@param rdsConfig
     * @return* /
    public static void setDataSourceMap(RdsConfig rdsConfig) {
        DynamicDataSource.dataSourceMap.put(rdsConfig.getTenantCode(), getDruidDataSource(rdsConfig));
    }

    /** * Get DruidDataSource **@param rdsConfig
     * @return* /
    private static DruidDataSource getDruidDataSource(RdsConfig rdsConfig) {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl("jdbc:mysql://" + rdsConfig.getDbUrl() + ":" + rdsConfig.getDbPort() + "/" + rdsConfig.getDbName() + "? useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=true&autoReconnect=true&serverTimezone =Asia/Shanghai");
        druidDataSource.setUsername(rdsConfig.getDbAccount());
        druidDataSource.setPassword(rdsConfig.getDbPassword());
        return druidDataSource;
    }

    / * * * * * rewrite determineCurrentLookupKey method@return java.lang.Object
     * @date2021/8/28 toward * * /
    @Override
    protected Object determineCurrentLookupKey(a) {
        returngetDataSourceKey(); }}Copy the code

  1. A key is maintained to indicate which data source is being used
  2. Maintains a map for SpringBoot to get data source information
  3. Rewrite determineCurrentLookupKey method, it can be reference to the above source code reading comprehension

4. Write the tenant RDS switching class, which is called uniformly when the business switches to the data source

RdsConfig class, a Javabean that describes connection information for RDS

@Data
public class RdsConfig implements Serializable {

    private static final long serialVersionUID = 1L;

    /** * Tenant code */
    private String tenantCode;

    /** * database URL */
    private String dbUrl;

    /** * database port */
    private String dbPort;

    /** * Database name */
    private String dbName;

    /** * database account */
    private String dbAccount;

    /** * Database password */
    private String dbPassword;
}
Copy the code

RDS switch service class: TenantRdsServiceImpl class, implementation of TenantRdsService interface, not posted here

@Service
@Slf4j
public class TenantRdsServiceImpl implements TenantRdsService {
    @Autowired
    private TenantMapper tenantMapper;

    @Autowired
    private RdsMapper rdsMapper;

    /** * get the RDS configuration **@param tenantCode
     * @date2021/8/28 13:53 * * /
    @Override
    public RdsConfig getRdsConfig(String tenantCode) {
        // Get the table according to the tenant code
        Tenant tenant = tenantMapper.selectByTenantCode(tenantCode);
        if (null == tenant) {
            return null;
        }
        / / RDS table
        Rds rds = rdsMapper.selectByPrimaryKey(tenant.getRdsId());
        if (null == rds) {
            return null;
        }
        // Convert to RDS configuration
        RdsConfig rdsConfig = new RdsConfig();
        rdsConfig.setDbUrl(rds.getHost());
        rdsConfig.setTenantCode(tenantCode);
        rdsConfig.setDbName(tenant.getDbName());
        rdsConfig.setDbAccount(rds.getAccount());
        rdsConfig.setDbPassword(rds.getPwd());
        rdsConfig.setDbPort(String.valueOf(rds.getPort()));
        return rdsConfig;
    }

    /** * Switch RDS connection according to tenant code, RDS configuration in the same thread will be checked only once **@param tenantCode
     * @date2021/8/28: * * /
    @Override
    public void switchRds(String tenantCode) {
        if (StringUtils.isBlank(tenantCode)) {
            throw new TenantCodeIsBlankException();
        }
        // RDS is returned if the tenant is already RDS
        if (tenantCode.equals(DynamicDataSource.getDataSourceKey())) {
            return;
        }
        // If the RDS has been changed locally, you need to restart the service
        if (null == DynamicDataSource.getDataSourceMap(tenantCode)) {
            // If it is not currently a repository, it is cut back to the repository
            if(! DataSourceConstant.DATA_SOURCE_MASTER.equals(DynamicDataSource.getDataSourceKey())) { DynamicDataSource.setDataSourceDefault(); }// Get the RDS configuration
            RdsConfig rdsConfig = getRdsConfig(tenantCode);
            if (null == rdsConfig) {
                throw new RdsNotFoundException();
            }
            DynamicDataSource.setDataSourceMap(rdsConfig);
        }
        // Switch to the business library
        DynamicDataSource.setDataSource(tenantCode);
    }

    /** * Switch the RDS connection based on the data source name, the RDS configuration in the same thread will be checked only once **@param dataSourceName
     * @date2021/8/28: * * /
    @Override
    public void switchRdsByDataSourceName(String dataSourceName) {
        if (StringUtils.isBlank(dataSourceName)) {
            throw new DataSourceNameIsEmptyException();
        }
        // If the data source is already present, return it directly
        if (dataSourceName.equals(DynamicDataSource.getDataSourceKey())) {
            return;
        }
        // If the RDS has been changed locally, you need to restart the service
        if (null == DynamicDataSource.getDataSourceMap(dataSourceName)) {
            throw new DataSourceNotExistException();
        }
        / / switchDynamicDataSource.setDataSource(dataSourceName); }}Copy the code

The tenant table is used to store the tenant code and the corresponding relationship with RDS, and the DB connection information (RDS) table is used to store the data source connection information, mapper and Javabean code here will not be published, according to the requirements of the table specific implementation can be 2. Three methods are provided, namely, obtaining RDS connection information according to tenant code, switching RDS according to tenant code, and switching RDS according to data source name. In the switching method, the current connection information is judged without repeated switching, and the RDS information is obtained by repeatedly checking the configuration library


5. Write custom annotations

Custom annotations @switchMasterrds

/** * Switch to primary data source - Custom annotations * This is just for convenience, using SwitchRds annotations to specify the default data source is also possible */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchMasterRds {
}
Copy the code

Custom annotations @Switchrds

/** * Switch data sources - custom annotations */
// Annotation target; ElementType.METHOD indicates that the annotation will be used on the METHOD; Elementtype. TYPE indicates that the annotation will be used in classes, interfaces, and enumerations.
@Target({ElementType.METHOD, ElementType.TYPE})
// Annotate policy attributes; Retentionpolicy. RUNTIME indicates that the annotations are not only saved to the class file, but still exist after the JVM loads the class file
@Retention(RetentionPolicy.RUNTIME)
public @interface SwitchRds {
    /** * Switch data sources based on the data source bean ** The data sources that can be toggled in the DataSourceConfig configuration class * take precedence if tenantCode is also specified */
    String dataSource(a) default "";

    /** * Dynamic switching - Switching data sources based on tenant code */
    String tenantCode(a) default "";
}
Copy the code

SwitchRds annotations can be used to SwitchRds with tenant code and data source name. 2.SwitchMasterRds annotations are added to facilitate switching to the master data source


6. Write aspect classes

The SwitchMasterRds annotation section class SwitchMasterRdsAspect

@Aspect
@Component
@Slf4j
public class SwitchMasterRdsAspect {
    /** * Tenant RDS service class */
    @Autowired
    private TenantRdsService tenantRdsServiceImpl;

    /** ** pointcut * join point: specify directly as annotation * Note: com.xxx.SwitchMasterRds@date2021/8/27 14:26 * * /
    @Pointcut("@annotation(com.xxx.SwitchMasterRds)")
    public void myPointcut(a) {}/** * surround notification **@return java.lang.Object
     * @date2021/8/27 14:26 * * /
    @Around(value = "myPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object proceed;
        try {
            tenantRdsServiceImpl.switchRdsByDataSourceName(DataSourceConstant.DATA_SOURCE_MASTER);
            / / execution
            proceed = pjp.proceed();
        } finally {
            // The todo data source that needs to be removed is also ok, but if not removed, the next time the todo will be switched to the configuration library first
        }
        returnproceed; }}Copy the code

SwitchRds Annotation aspect class SwitchRdsAspect

@Aspect
@Component
@Slf4j
public class SwitchRdsAspect {
    /** * Tenant RDS service class */
    @Autowired
    private TenantRdsService tenantRdsServiceImpl;

    /** * SwitchRds: com.xxx.SwitchRds: com.xxx.SwitchRds: com.xxx.SwitchRds: com.xxx.SwitchRds@date2021/8/27 14:26 * * /
    @Pointcut("@annotation(com.xxx.SwitchRds)")
    public void myPointcut(a) {}/** * surround notification **@return java.lang.Object
     * @date2021/8/27 14:26 * * /
    @Around(value = "myPointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        SwitchRds annotation = getAnnotation(pjp);
        // Get the tenant code on the annotation
        String tenantCode = annotation.tenantCode();
        String dataSource = annotation.dataSource();
        Object proceed;
        try {
            if (StringUtils.isNotBlank(dataSource)) {
                tenantRdsServiceImpl.switchRdsByDataSourceName(dataSource);
            } else if (StringUtils.isNotBlank(tenantCode)) {
                tenantRdsServiceImpl.switchRds(tenantCode);
            } else {
                throw new DataSourceSwitchFailException();
            }
            / / execution
            proceed = pjp.proceed();
        } finally {
            // The todo data source that needs to be removed is also ok, but if not removed, the next time the todo will be switched to the configuration library first
        }
        return proceed;
    }

    /** * get the annotation **@param pjp
     * @date2021/8/27 bethlehemite * * /
    private SwitchRds getAnnotation(ProceedingJoinPoint pjp) {
        // Try to get an annotation on the class
        SwitchRds annotation = pjp.getTarget().getClass().getAnnotation(SwitchRds.class);
        // If there is no annotation on the class, get the annotation above the method
        if (null == annotation) {
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            annotation = methodSignature.getMethod().getAnnotation(SwitchRds.class);
        }
        returnannotation; }}Copy the code

Here the join point is set directly on the custom annotation written, and the DATA source is toggled by calling the RDS toggle class based on parameters and so on


7. Exception classes, exception enumeration classes

ErrorInfo interface that normalizes exception enumeration classes

public interface ErrorInfo {
    /** * exception code *@return int
     */
    int code(a);

    /** * Exception description *@return String
     */
    String message(a);
}
Copy the code

Handle exception enumeration classes that enumerate all error types and error codes

/** * Handle exception enumeration class */
public enum HandleExceptionEnum implements ErrorInfo {
    /** ** pending */
    WAIT(0."Pending"),

    /** * succeeded */
    SUCCESS(10."SUCCESS"),

    /** * error */
    ERROR(100."Program error"),


    /** * public - RDS configuration not fetch */
    C_GENERATE_RDS_NOT_FOUND(1001."RDS configuration not retrieved"),

    /** * Public - Tenant code is empty */
    C_GENERATE_TENANT_CODE_IS_BLANK(1002."Tenant code is empty"),

    /** * public - Data source configuration does not exist */
    C_GENERATE_DATA_SOURCE_NOT_EXIST(1003."Data source configuration does not exist"),

    /** * public - Data source name is empty */
    C_GENERATE_DATA_SOURCE_NAME_IS_EMPTY(1004."Data source name is empty"),

    /** * public - Data source name is empty */
    C_GENERATE_DATA_SOURCE_SWITCH_FAIL(1005."Data source switchover failed"),


    // ------------------------------------------------------------------

    ;

    /** ** encoding */
    private final int code;

    /** * info */
    private final String message;

    HandleExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public int code(a) {
        return code;
    }

    @Override
    public String message(a) {
        return message;
    }

    /** * code converts to enum **@paramCode Indicates the error code *@return HandleExceptionEnum
     */
    public static HandleExceptionEnum codeOf(int code) {
        for (HandleExceptionEnum item : HandleExceptionEnum.values()) {
            if (item.code() == code) {
                returnitem; }}return null;
    }

    /** * specifies whether code is within the enumeration **@paramCode Indicates the error code *@return boolean
     */
    public static boolean contain(int code) {
        for (HandleExceptionEnum item : HandleExceptionEnum.values()) {
            if (item.code() == code) {
                return true; }}return false; }}Copy the code

Exception processing base class. All exceptions are inherited from this class. ErrorInfo information is saved and error codes can be easily obtained

/** * HandlerException */
public class HandlerException extends RuntimeException {
    /** * Exception message */
    private final ErrorInfo errorInfo;

    /** * The no-argument constructor defaults to a program error */
    public HandlerException(a) {
        super(HandleExceptionEnum.ERROR.message());
        this.errorInfo = HandleExceptionEnum.ERROR;
    }

    public HandlerException(HandleExceptionEnum handleExceptionEnum) {
        super(handleExceptionEnum.message());
        this.errorInfo = handleExceptionEnum;
    }

    public HandlerException(HandleExceptionEnum handleExceptionEnum, String message) {
        super(message);
        this.errorInfo = handleExceptionEnum;
    }

    /** * Get code ** based on the exception type@param e
     * @return int
     */
    public static int getCode(Exception e){
        return e instanceof HandlerException ? ((HandlerException) e).getErrorInfo().code() : HandleExceptionEnum.ERROR.code();
    }

    /** * Get exception information **@return ErrorInfo
     */
    public ErrorInfo getErrorInfo(a) {
        returnerrorInfo; }}Copy the code

Specific exception class, directly inherit processing exception base class, the article actively throws exceptions are written in this way, here is not a list

/** * RDS configuration is not */
public class RdsNotFoundException extends HandlerException {
    public RdsNotFoundException(a) {
        super(HandleExceptionEnum.C_GENERATE_RDS_NOT_FOUND); }}Copy the code

use

1. Annotation method

@RestController
public class HelloController {
    /** * Switch to primary data source mode 1 */
    @GetMapping("/masterFirst")
    @SwitchRds(dataSource = DataSourceConstant.DATA_SOURCE_MASTER)
    public Object masterFirst(a) {
		// todo
    }

    /** * Switch to primary data source mode 2 */
    @GetMapping("/masterSecond")
    @SwitchMasterRds
    public Object masterSecond(a) {
		// todo
    }

    /** * Switch to another configured data source */
    @GetMapping("/other")
    @SwitchRds(dataSource = DataSourceConstant.DATA_SOURCE_DB_1)
    public Object other(a) {
		// todo
    }
	
    /** * Switch */ according to the tenant code
    @GetMapping("/tenant")
    @SwitchRds(tenantCode = "tenantxxx")
    public Object tenant(a) {
		// todo}}Copy the code

2. Use of business code

try {
    tenantRdsServiceImpl.switchRds("any tenant code");
} catch (Exception e) {
    log.error("Error switching tenant RDS :{},context:{}", e.getMessage(), context, e);
}
Copy the code