Spring Dynamic data source
What is a dynamic data source? What does it solve??
In real-world development, it is common to use multiple data sources in the same project. For example, a project with read/write separation has master and read data sources.
Dynamic data sources automatically control which data source a certain piece of data manipulation logic goes to through Spring configuration. For an example of read/write separation, the project references two data sources, master and slave. Spring configures or extends the ability to use slave data sources automatically if a query method is invoked in an interface.
This effect can generally be achieved by:
-
Use the @mapperscan annotation to specify that all methods in a package go to a fixed data source (this is more rigid, resulting in redundant code, but can also be used as a temporary solution);
-
Using annotations + AOP + AbstractRoutingDataSource to specify in the form of a method of database operations is that the data source.
-
Through the Sharding-JDBC component (external dependencies need to be introduced, which is recommended if the project references the component itself)
<hr> Copy the code
Key core class[Obtain information]
Here introduces through annotations + AOP + AbstractRoutingDataSource linkage to realize the dynamic data source.
Is the starting point of all AbstractRoutingDataSource this class that implements the DataSource interface
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
// .... 省略 ...
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Map<Object, DataSource> resolvedDataSources;
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
}
@Override
public void afterPropertiesSet(a) {
// Initialize targetDataSources, resolvedDataSources
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);
});// Join Java development chat
if (this.defaultTargetDataSource ! =null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); }}@Override
public Connection getConnection(a) throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource(a) {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// @1 start
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
// @1 end
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;
}
/** * returns a key that is used to retrieve the data source object from the resolvedDataSources data source. See */
@Nullable
protected abstract Object determineCurrentLookupKey(a);
}
Copy the code
Can see a extensible abstraction method in AbstractRoutingDataSource determineCurrentLookupKey (), using this method can realize the dynamic data source.
Write a simple dynamic data source component from zero
From the last part we know can achieve AbstractRoutingDataSource determineCurrentLookupKey () method of the dynamic setting a key, We then set our DataSource Map in the configuration class using the setTargetDataSources() method.
Annotation, constant definition
/** * @author axin * @summary Dynamic data source annotation definition */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyDS {
String value(a) default "default";
}
/** * @author axin * @summary Dynamic data source constant */
publicInterface DSConst {String Default ="default"; String = main library"master"; String = from the library"slave"; A String of statistical ="stat";
}
Copy the code
/** * @author axin * @summary dynamic data source ThreadLocal tool */
public class DynamicDataSourceHolder {
// Join Java development chat
// Save the DataSource specified by the current thread
private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<>();
public static String getDataSource(a) {
return THREAD_DATA_SOURCE.get();
}
public static void setDataSource(String dataSource) {
THREAD_DATA_SOURCE.set(dataSource);
}
public static void removeDataSource(a) { THREAD_DATA_SOURCE.remove(); }}Copy the code
A custom AbstractRoutingDataSource class
/** * @author axin * @summary dynamic data source */
public class DynamicDataSource extends AbstractRoutingDataSource {
/** * Get the target data source key * @return */ from the data source
@Override
protected Object determineCurrentLookupKey(a) {
// Get the key from ThreadLocal
String dataSourceKey = DynamicDataSourceHolder.getDataSource();
if (StringUtils.isEmpty(dataSourceKey)) {
returnDSConst. The default; }returndataSourceKey; }}Copy the code
AOP implementations
/** * @author axin * @summary Data source switch AOP */
@Slf4j
@Aspect
@Service
public class DynamicDataSourceAOP {
public DynamicDataSourceAOP(a) {
log.info("/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /");
log.info("/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /");
log.info("/*---------- dynamic data source initialization... -- -- -- -- -- -- -- -- -- - * /");
log.info("/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /");
log.info("/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /");
}
/** ** cut point */
@Pointcut(value = "@annotation(xxx.xxx.MyDS)")
private void method(a){}
/** * switch to the specified data source * @param point */ before executing the method
@Before("method()")
public void before(JoinPoint point) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
// Get the proxied method object
Method targetMethod = methodSignature.getMethod();
// Get the annotation information for the proxied method
CultureDS cultureDS = AnnotationUtils.findAnnotation(targetMethod, CultureDS.class);
// Dynamic data source annotations at the outermost layer of the method chain have the highest priority
// Join Java development chat
String key = DynamicDataSourceHolder.getDataSource();
if(! StringUtils.isEmpty(key)) {log.warn("Warning: overwrite scenario appears on dynamic data source annotation call chain, please make sure there is no problem");
return;
}
if(cultureDS ! = null ) {// Set the database flagDynamicDataSourceHolder.setDataSource(MyDS.value()); }}/** * release data source */
@AfterReturning("method()")
public void doAfter(a) { DynamicDataSourceHolder.removeDataSource(); }}Copy the code
DataSourceConfig configuration
Configure dynamic data sources into SqlSession with the following code
/** * sqlsession; There is no use mybatis annotation * @ Configuration @ EnableTransactionManagement @ EnableAspectJAutoProxy class DataSourceConfig { /** read-write SQL Session */
public static final String BEANNAME_SQLSESSION_COMMON = "sqlsessionCommon";
/** Specifies the name of the transaction manager. If there are multiple transaction managers, specify beanName */
public static final String BEANNAME_TRANSACTION_MANAGER = "transactionManager";
/** primary DataSource, must be configured. Spring starts to initialize data (whether it is needed or not)
@Bean
@Primary
@ConfigurationProperties(prefix = "datasource.common")
public DataSource datasourceCommon(a) {
// Data source configuration can be changed to another implementation
return DataSourceBuilder.create().build();
}
/** * dynamic data source * @returnr */
@Bean
public DynamicDataSource dynamicDataSource(a) {
DynamicDataSource dynamicDataSource = newDynamicDataSource(); LinkedHashMap<Object, Object> hashMap = Maps.newLinkedHashMap(); Hashmap.put (dsconconst. Default, datasourceCommon()); Hashmap.put (dsconst. main library, datasourceCommon()); Hashmap.put (dsconconst. Slave library, datasourceReadOnly()); Hashmap.put (dsconconst. Statistics, datasourceStat());// Initialize the data source Map
dynamicDataSource.setTargetDataSources(hashMap);
dynamicDataSource.setDefaultTargetDataSource(datasourceCommon());
return dynamicDataSource;
}
/** * Configure the transaction manager */
@Primary
@Bean(name = BEANNAME_TRANSACTION_MANAGER)
public DataSourceTransactionManager createDataSourceTransactionManager2(a) {
DataSource dataSource = this.dynamicDataSource();
DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);
return manager;
}
/** * Configure read and write SQLSession */
@Primary
@Bean(name = BEANNAME_SQLSESSION_COMMON)
public SqlSession readWriteSqlSession(a) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
// Join Java development chat
// Set the dynamic data source
factory.setDataSource(this.dynamicDataSource());
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factory.setConfigLocation(resolver.getResource("mybatis/mybatis-config.xml"));
factory.setMapperLocations(resolver.getResources("mybatis/mappers/**/*.xml"));
return newSqlSessionTemplate(factory.getObject()); }}Copy the code
conclusion
To sum up, you implement a simple Spring dynamic data source feature using AOP+ annotations that simply add the @myds annotation to the target method. Many open source components add an extension to the existing functionality, such as routing policies, and so on. [Obtain information]
By the way, sharding-JDBC implementation, update write class SQL automatically go to the master library, query class automatically go to the library, if the new project has no historical debt, is able to use this scheme. If you are working on a legacy project, then if you are using sharding-JDBC read-write separation, you will have to tease out SQL calls in your existing code logic to avoid the business impact of data inconsistencies caused by master-slave latency.
Inconsistent read data due to master/slave delay means that the master/slave synchronization has a certain delay time. The delay value is always in milliseconds regardless of the network situation. At this time, if sharding-JDBC is used for read and write separation processing, real-time data insertion and query judgment, judgment exceptions will occur. 【 References 】
In the end, I wish you all success as soon as possible, get satisfactory offer, fast promotion and salary increase, and walk on the peak of life.
If you can please give me a three support me yo [Obtain information]