DynamicDataSource shows how to implement multi-source switching. In fact, MP already has a packaged multi-source switching framework, which has more functionality than manual implementation.

Implementation and Principle

The introduction of the jar package

<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> The < version > 3.3.2 rainfall distribution on 10-12 < / version > < / dependency >Copy the code

Create master and Demo data sources manually and configure DynamicRoutingDataSource

@Bean(name = "dynamicDataSource") @DependsOn({"master", "demo"}) @Primary public DynamicRoutingDataSource dataSource(DynamicDataSourceProvider dynamicDataSourceProvider) { DynamicRoutingDataSource dynamicDataSource = new DynamicRoutingDataSource(); / / configuration data source dynamicDataSource. AddDataSource (" master ", mainSource ()); dynamicDataSource.addDataSource("demo", demoSource()); dynamicDataSource.setPrimary("master"); / / read yml configuration of data sources, this not used dynamicDataSource. SetProvider (dynamicDataSourceProvider); return dynamicDataSource; }Copy the code

DynamicRoutingDataSource Reads the YML configuration to automatically add data sources, see the official documentation.

When getting a connection, the entry method is still getConnection().

DynamicRoutingDataSource->getConnection(): String xid = TransactionContext.getXID(); If (stringutils.isempty (xID)) {// Get the data source and call its getConnection() method return determineDataSource().getConnection(); } else { ...... } DynamicRoutingDataSource->determineDataSource(): String dsKey = DynamicDataSourceContextHolder.peek(); return getDataSource(dsKey); DynamicRoutingDataSource->getDataSource(): if (StringUtils.isEmpty(ds)) { return determinePrimaryDataSource(); } else if { ...... } } else if (dataSourceMap.containsKey(ds)) { return dataSourceMap.get(ds); }Copy the code

DynamicDataSourceContextHolder internal storage in the form of the stack, lifo, with push (String ds) set the current data source (stack), using peek () to obtain the current thread data sources (value) for stack, there is no set back to the main data sources, Use poll() to clear the current data source (out of the stack), use clear() to clear the stack value, and call push to ensure that it is eventually cleared (out of the stack).

@DS

The @ds annotation can be used on a class or method to switch data sources automatically.

The Advisor is DynamicDataSourceAnnotationAdvisor, when classes or methods exist @ DS annotation meet plane expression, the interceptor is DynamicDataSourceAnnotationInterceptor.

DynamicDataSourceAnnotationInterceptor - > the invoke () : / / String annotation value obtained dsKey = determineDatasourceKey (invocation); / / set up the data source DynamicDataSourceContextHolder. Push (dsKey); Try {// Return invocation. Proceed (); } the finally {/ / empty data source DynamicDataSourceContextHolder. Poll (); }Copy the code

@DSTransactional

@dstransactional are multi-source transactions, where multiple data sources are submitted together or rolled back together, and are used on classes or methods.

Its definition in DynamicDataSourceAutoConfiguration class, when classes or methods exist @ DSTransactional annotation meet plane expression, the interceptor is DynamicLocalTransactionAdvisor.

DynamicLocalTransactionAdvisor - > the invoke () : / / xid is a random string, so long as has the value is the transaction if (! StringUtils.isEmpty(TransactionContext.getXID())) { return methodInvocation.proceed(); } boolean state = true; // Set a random String xid String xid = uuid.randomuuid ().toString(); TransactionContext.bind(xid); try { o = methodInvocation.proceed(); } catch (Exception e) { state = false; throw e; } finally { ConnectionFactory.notify(state); // --1 TransactionContext.remove(); } return o;Copy the code

When a multi-source transaction acquires a connection, the entry method is still getConnection().

DynamicRoutingDataSource->getConnection(): String xid = TransactionContext.getXID(); // We must enter the else branch...... } else {/ / the current data source String ds = DynamicDataSourceContextHolder. Peek (); / / cache Map collections, the key is the data source name, the value is a data source ConnectionProxy connection = ConnectionFactory. GetConnection (ds); Return connection == null? getConnectionProxy(ds, determineDataSource().getConnection()) : connection; } AbstractRoutingDataSource->getConnectionProxy(): ConnectionProxy connectionProxy = new ConnectionProxy(connection, ds); ConnectionFactory.putConnection(ds, connectionProxy); // --2 return connectionProxy; ConnectionFactory->putConnection(): // --2 Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get(); if (! ConcurrentHashMap. Either containsKey (ds)) {try {/ / set the autocommit to false connection. SetAutoCommit (false); } catch (SQLException e) { e.printStackTrace(); } concurrentHashMap.put(ds, connection); }Copy the code
ConnectionFactory->notify(): // --1 Map<String, ConnectionProxy> concurrentHashMap = CONNECTION_HOLDER.get(); For (ConnectionProxy ConnectionProxy: conCurrenthashMap.values ()) {connectionProxy.notify(state); } ConnectionProxy->notify(): if (commit) {connection.mit (); } else { connection.rollback(); Connection.close ();Copy the code

While @dstransactional does not define rollback and non-rollback exceptions as @Transactional does, and does not have a mechanism for propagating transactions, it does provide support for multi-source transactions.