Dynamic data source
By using the annotation mode of the method, you can switch the database source used by the method. The method is enhanced through the spring section.
Therefore, our construction steps are annotation - Section - data source principle - Implementation (in no order, only the logic of this article)
Learn how to use annotations
Core concept
@Retention
- Retention in English means "keep" and "keep". It means that the annotation exists in the source code (compilation time), bytecode (class loading) or runtime (running in the JVM). Use the enumeration RetentionPolicy in the @ retention annotation to indicate the annotation retention period
- @Retention(RetentionPolicy.SOURCE). The annotation only exists in the source code and is not included in the class bytecode file
- @Retention(RetentionPolicy.CLASS), the default retention policy. Annotations exist in the class bytecode file, but cannot be obtained at runtime
- @Retention(RetentionPolicy.RUNTIME), the annotation will exist in the class bytecode file and can be obtained through reflection at run time
- If we are user-defined annotations, according to the previous analysis, our user-defined annotations will not work if they are only stored in the source code or bytecode file, and our purpose can only be achieved if they can be obtained during operation. Therefore, @ Retention(RetentionPolicy.RUNTIME) must be used in the user-defined annotations
@Target
- Target means target in English, which is easy to understand. Using @ target meta annotation to represent the scope of our annotation is more specific. It can be class, method, method parameter variable, etc. it also expresses the action type through enumerating class ElementType
- @Target(ElementType.TYPE) functions as interface, class, enumeration and annotation
- @Target(ElementType.FIELD) acts as a constant for attribute fields and enumerations
- @Target(ElementType.METHOD) action method
- @Target(ElementType.PARAMETER) action method parameter
- @Target(ElementType.CONSTRUCTOR) function constructor
- @Target(ElementType.LOCAL_VARIABLE) acts as a local variable
- @Target(ElementType.ANNOTATION_TYPE) acts on annotation (@ Retention annotation uses this attribute)
- @Target(ElementType.PACKAGE) acts on the package
- @Target(ElementType.TYPE_PARAMETER) acts on type generics, that is, generic methods, generic classes and generic interfaces (added by jdk1.8)
- @Target(ElementType.TYPE_USE) can be used to label any type except class (added by jdk1.8)
- ElementType.TYPE is commonly used
@Documented
- Document means document in English. Its function is to include the elements in the annotation into the Javadoc.
@Inherited
- Inherited means inheritance in English, but this inheritance is similar to what we usually understand. An annotation annotated by @ inherited modifies a parent class. If its subclass is not modified by other annotations, its subclass also inherits the annotation of the parent class.
@Repeatable
- Repeatable means repeatable in English. As the name suggests, the annotation modified by this meta annotation can act on an object multiple times at the same time, but each action annotation can represent different meanings.
The essence of annotation
- Annotation is essentially an annotation interface
/**Annotation Interface source code*/ public interface Annotation { boolean equals(Object obj); int hashCode(); Class<? extends Annotation> annotationType(); }
Get annotation properties
There are three main methods to obtain through reflection:
/**Does the corresponding Annotation object exist*/ public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) { return GenericDeclaration.super.isAnnotationPresent(annotationClass); } /**Get Annotation object*/ public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { Objects.requireNonNull(annotationClass); return (A) annotationData().annotations.get(annotationClass); } /**Gets an array of all Annotation objects*/ public Annotation[] getAnnotations() { return AnnotationParser.toArray(annotationData().annotations); }
Custom comments are as follows:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Serize { String name() default "Serize"; String part() default ""; int version() default 1; }
Use cases are as follows:
public class SerizeDemo { @Serize(version = 15000) public static void TransferVersion(double version) throws NoSuchMethodException { System.out.println(processSerizeVersion(money)); } private static boolean processSerizeVersion(double version) throws NoSuchMethodException { Method transferVersion = SerizeDemo.class.getDeclaredMethod("TransferVersion", double.class); boolean present = transferVersion.isAnnotationPresent(Serize.class); if (present) { Serize serize = transferVersion.getAnnotation(Serize.class); int version = serize.version(); System.out.println("annotation version"+version); return true; } else { return false; } } public static void main(String[] args) throws NoSuchMethodException { TransferVersion(222); } }
Slice implementation
Here, we use the section directly to enhance the annotation with our custom tags. To achieve method level enhancement, other specific implementation methods can be implemented by querying data.
@Aspect @Slf4j @Component public class SerizeAspect { /** * Define pointcuts. Pointcuts are all functions under com.serize.annoDemo */ @Pointcut("execution(public * com.serize.annoDemo..*.*(..))") public void serizePoint() { } /** * Custom annotation pointcuts */ @Pointcut("@annotation(com.serize.annoDemo.Serize)") public void noAnnotation() { } /** * Pre notification: notification executed before the connection point * Same as the method annotated serialize * @param joinPoint * @throws Throwable */ @Before("serizePoint()&&noAnnotation()") public void doBefore(JoinPoint joinPoint) throws Throwable { log.info(joinPoint.getSignature().getName()); log.info(joinPoint.getSignature().toString()); log.info(joinPoint.getKind()); log.info(joinPoint.getThis().toString()); log.info(joinPoint.getTarget().toString()); log.info(joinPoint.getSourceLocation().toString()); } @AfterReturning(returning = "ret", pointcut = "serizePoint()") public void doAfterReturning(Object ret) throws Throwable { // After processing the request, return the content log.info("RESPONSE : " + ret); } }
Data source principle*
In Java, all connection pools implement the DataSource interface according to the specification. When obtaining the connection, you can obtain the connection through getConnection(), regardless of the underlying database connection pool.
This specification is given by the java package. The code is as follows:
package javax.sql; public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }
In most systems, we only need one data source, and now WEB systems are usually based on spring. Whether you are xml configuration, javaBean configuration or yml,properties configuration file configuration, the core is to inject a data source to spring for management.
In Spring, AbstractRoutingDataSource is provided by default from version 2.0.1. We inherit its implementation methods and set all required data sources to dynamically switch data sources.
package org.springframework.jdbc.datasource.lookup; /** An abstract data source implementation that routes getConnection() calls to one of the various target data sources according to the lookup key. The latter is usually (but not necessarily) determined by the transaction context bound by a thread. */ public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { //Set all data sources private Map<Object, Object> targetDataSources; //Set the default data source. If no relevant data source is found, the default data source will be returned private Object defaultTargetDataSource; //Quick failure, negligible private boolean lenientFallback = true; //Jndi related, negligible private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); //All data sources after parsing, core private Map<Object, DataSource> resolvedDataSources; //Parsed default data source, core private DataSource resolvedDefaultDataSource; /** Specifies the mapping of the target DataSources, using the lookup key as the key. The mapping value can be the corresponding DataSource instance or the data source name string (resolved through DataSourceLookup). Keys can be of any type; This class implements only the generic lookup procedure. The specific key representation will be handled by resolvespecifiedlookup key (object) and determineCurrentLookupKey(). */ public void setTargetDataSources(Map<Object, Object> targetDataSources) { this.targetDataSources = targetDataSources; } /** If so, specify the default target data source. The mapping value can be the corresponding DataSource instance or the data source name string (resolved through DataSourceLookup). If no keyed targetDataSources match the current lookup key of determineCurrentLookupKey(), this DataSource will be used as the target. */ public void setDefaultTargetDataSource(Object defaultTargetDataSource) { this.defaultTargetDataSource = defaultTargetDataSource; } /** If a specific data source for the current lookup key cannot be found, specify whether to apply a relaxed fallback to the default data source. The default is "true" and accepts a lookup key that does not have a corresponding entry in the target DataSource map -- in this case, simply go back to the default DataSource. If you want to apply fallback only when the lookup key is empty, switch this flag to "false". A lookup key without a data source entry will result in an IllegalStateException. */ public void setLenientFallback(boolean lenientFallback) { this.lenientFallback = lenientFallback; } /** Sets the DataSourceLookup implementation used to resolve the data source name string in the targetDataSources map. The default is JNDI datasourcelookup, which allows you to directly specify the JNDI name of the application server DataSources. */ public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) { this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup()); } @Override public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<>(this.targetDataSources.size()); this.targetDataSources.forEach((key, value) -> { Object lookupKey = resolveSpecifiedLookupKey(key); DataSource dataSource = resolveSpecifiedDataSource(value); this.resolvedDataSources.put(lookupKey, dataSource); }); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } } /** Resolves the given lookup key object to the actual lookup key in the manner specified in the targetDataSources map to match the current lookup key. The default implementation simply returns the given key as is. Parameters: lookupKey—The lookup key object specified by the user return: Match the required lookup key */ protected Object resolveSpecifiedLookupKey(Object lookupKey) { return lookupKey; } /** Resolves the specified data source object to a DataSource instance. The default implementation handles the DataSource instance and data source name (resolved through DataSourceLookup). Parameters: dataSource—The data source value object specified in the targetDataSources map return: Resolved datasource (never empty) Throw: IllegalArgumentException-In case of unsupported value type */ protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException { if (dataSource instanceof DataSource) { return (DataSource) dataSource; } else if (dataSource instanceof String) { return this.dataSourceLookup.getDataSource((String) dataSource); } else { throw new IllegalArgumentException( "Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource); } } @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return determineTargetDataSource().getConnection(username, password); } @Override @SuppressWarnings("unchecked") public <T> T unwrap(Class<T> iface) throws SQLException { if (iface.isInstance(this)) { return (T) this; } return determineTargetDataSource().unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface)); } /** Retrieves the current target data source. Determine the current lookup key, perform the lookup in the targetDataSources map, and return the specified default target DataSource if necessary. */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); if (dataSource == null && (this.lenientFallback || lookupKey == null)) { dataSource = this.resolvedDefaultDataSource; } if (dataSource == null) { throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]"); } return dataSource; } /** Determines the current lookup key. This is usually used to check the transaction context of the thread binding. Allow any key. The returned key must match the stored lookup key type and be resolved by the resolvespecificedlookup key method. */ @Nullable protected abstract Object determineCurrentLookupKey(); }
Note that the AOP order must precede the transaction order. If you don't understand the concept of order, you can baidu.
Dynamic data source code analysis process
Refer to the following blog post: Dynamic data source - springmanagedtransaction & & abstractroutingdatasource source code parsing process
No annotation for dynamic data source
The idea is to directly use aop to intercept the specified method, and use the characteristics of aop to set the part in advance.
@RestController public class DysourceController { @Autowired DysourceService dysourceService; @GetMapping("primary") public Object primary(){ return dysourceService.getAll(); } @GetMapping("secondary") public Object secondary(){ return dysourceService.getAll(); } }
Enhanced under controller:
@Aspect @Component public class DataSourceAop { //Execute before the primary method @Before("execution(* com.serize.controller.DysourceController.primary(..))") public void setDataSource2test01() { System.err.println("Primary business"); DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary); } //Execute before using method @Before("execution(* com.serize.controller.DysourceController.secondary(..))") public void setDataSource2test02() { System.err.println("Secondary business"); DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary); } }
Profile:
server.port=8086 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/dysource?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true spring.datasource.username=root spring.datasource.password=x5 mybatis-plus.configuration.map-underscore-to-camel-case=true mybatis-plus.com.serize.mapper-locations=classpath*:mapper/**/*Mapper.xml #Configure master database spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/dysource?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false spring.datasource.primary.username=root spring.datasource.primary.password=x5 spring.datasource.primary.driver-class-name=com.mysql.jdbc.Driver ##Configure secondary database spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/dysource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false spring.datasource.secondary.username=root spring.datasource.secondary.password=x5 spring.datasource.secondary.driver-class-name=com.mysql.jdbc.Driver
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType(); return dataBaseType; } }
public class DataSourceType { //The internal enumeration class is used to select a specific data type public enum DataBaseType { Primary, Secondary } // Using ThreadLocal to ensure thread safety private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>(); // Set the data source type to the current process public static void setDataBaseType(DataBaseType dataBaseType) { if (dataBaseType == null) { throw new NullPointerException(); } TYPE.set(dataBaseType); } // Get data source type public static DataBaseType getDataBaseType() { DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get(); return dataBaseType; } // Clear data type public static void clearDataBaseType() { TYPE.remove(); } }
@Configuration @MapperScan(basePackages = "com.serize.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages the address of our interface file public class DynamicDataSourceConfig { // Put this object into the Spring container @Bean(name = "PrimaryDataSource") // Indicates that this data source is the default data source @Primary // Read the configuration parameters in application.properties and map them into an object // Prefix indicates the prefix of the parameter @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource getDateSource1() { return DataSourceBuilder.create().build(); } @Bean(name = "SecondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource getDateSource2() { return DataSourceBuilder.create().build(); } @Bean(name = "dynamicDataSource") public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource, @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) { //This place is the core of the comparison. The targetDataSource collection is the mapping between our database and the name Map<Object, Object> targetDataSource = new HashMap<>(); targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource); targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource); dataSource.setDefaultTargetDataSource(primaryDataSource);//Set default object return dataSource; } @Bean(name = "SqlSessionFactory") public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dynamicDataSource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*Mapper.xml"));//Set our xml file path return bean.getObject(); } }
Dynamic data source annotation (method level)
Add dynamic data source annotation
/** * Toggle data annotation can be used at class or method level. Method level priority > class level */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String value() default "primary"; //This value is the key value. The default database is used by default }
Add annotation method section
@Aspect @Component @Slf4j public class DynamicDataSourceAspect { @Before("@annotation(dataSource)")//Block our comments public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable { String value = dataSource.value(); if (value.equals("primary")){ DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary); }else if (value.equals("secondary")){ DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary); }else { DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//The primary database is used by default } } @After("@annotation(dataSource)") //Clear configuration of data source public void restoreDataSource(JoinPoint point, DataSource dataSource) { DataSourceType.clearDataBaseType(); } }
The summary is to give the method annotated and indicating which database to use to aop and set it before obtaining the connection.