Project name
After easymulti-datasource-spring-boot-starter, hotkit-r2dbc was developed. These two projects both support dynamic datasource switching. The former supports mybatis framework. The latter support responsive programming spring-data-R2DBC framework, since both are ORM framework, it is better to merge into a project maintenance.
The original EasyMulti-Datasource – Spring-boot-Starter project on GitHub has been renamed easyMulti-Datasource, The original EasyMulti-datasource – Spring-boot-Starter module has been renamed as EasyMulti-datasource – Mybatis, version number from 3.0.1. Easymulti-datasource – R2DBC (also known as hotKit – R2DBC) is added to the new version.
Project background
Multi-data source dynamic switching seems to have become the standard configuration of microservices. After doing so many projects, I found that each project needs to be equipped with a dynamic data source and write a section to achieve dynamic switching. Therefore, I packaged these tedious configurations as a starter and put them into use.
Easymulti-datasource two modules:
easymulti-datasource-mybatis
(the originaleasymulti-datasource-spring-boot-starter
)easymulti-datasource-r2dbc
(the originalhotkit-r2dbc
)
easymulti-datasource-mybatis
Mybatis version of the multi-data source framework, providing declarative and programmatic dynamic switching data source function.
Easymulti-datasource -mybatis automatically integrates mybatis-plus and provides two dynamic multi-datasource modes, namely master/slave mode and non-master/slave multi-datasource mode. Each datasource uses independent connection pool configuration, and connection pool can be configured separately for each datasource.
Support for dynamic switching of multiple data sources is not the biggest highlight of easyMulti-datasource – Mybatis framework. The main features of EasyMulti-Datasource – MyBatis framework are as follows:
- Support to monitor
SQL
, listen for changes to certain fields of a tablesql
, for realizing buried point events; - Supports transaction status monitoring and registers transaction listeners to complete some background operations when the transaction is rolled back/committed;
See Wiki for details.
Depend on the configuration
Maven uses:
<dependency>
<groupId>com.github.wujiuye</groupId>
<artifactId>easymulti-datasource-mybatis</artifactId>
<version>${version}</version>
</dependency>
Copy the code
The previous version is:
<dependency>
<groupId>com.github.wujiuye</groupId>
<artifactId>easymulti-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
Copy the code
Note The following figure shows the precautions for version selection.
Dynamically switching data sources
- Switch data sources with annotations:
@EasyMutiDataSource
; - use
API
Switching data source:DataSourceContextHolder#setDataSource
.
Register transaction listeners in AOP
Turn on tracing transaction method invocation links in the application configuration file as follows.
Monitor transaction method invocation links
easymuti:
transaction:
open-chain: true
Copy the code
Define the section, interceptMapper
Method to implement the logic of updating the cache in the surround method, as shown below.
TransactionInvokeContext.currentExistTransaction
: Checks whether a transaction exists on the current calling link.TransactionInvokeContext.addCurrentTransactionMethodPopListener
Bind a listener to the current transaction (PopTransactionListener
), the listener is called when the transaction commits or rolls back.
As shown in the above code, the first step is to determine whether there is a transaction on the current calling link. If there is, a listener is injected into the current transaction, and the listener completes the cache update logic. If there is no transaction, the update cache logic is executed after the target method is executed and no exception is thrown.
Listening to the SQL
Easymulti-datasource – Mybatis supports SQL buried listening and transaction state monitoring. If the current SQL execution exists in a transaction, the SQL listener will be called back after the transaction is committed.
Step 1: Enable SQL buried listening and transaction call link tracing.
easymuti:
transaction:
open-chain: true
sql-watcher:
enable: true
Copy the code
Step 2: Write an observer that can have more than n observers, and multiple observers can observe the same table, or even the same field.
@Component
@Slf4j
public class TestTableFieldObserver implements TableFieldObserver , InitializingBean {
@Override
public Set<WatchMetadata> observeMetadatas(a) {
// Register here which tables to listen on which fields
}
/** * is called synchronously when listening to SQL **@paramCommandType Specifies the event type *@paramMatchResult Matched ITEM *@returnReturn asynchronous consumer */
@Override
public AsyncConsumer observe(CommandType commandType, MatchItem matchResult) {
// Synchronous consumption
SQL > execute (); SQL > execute ()
// Asynchronous consumption, and then the SQL execution completes, or when the transaction method completes (if there is a transaction), the completion refers to the normal execution completes or the method abnormally exits
return throwable -> {
// SQL execution throws exceptions without processing
if(throwable ! =null) {
return;
}
// Consumption events
/ /...}; }}Copy the code
The observe method is called synchronously when the SQL is being listened on. The AsyncConsumer returned by this method is called back after the transaction is committed and not if the transaction is rolled back.
If multiple transactions occur on the calling link, all Asyncconsumers registered on the transaction will only be called back when the transaction of the current method commits, depending on the propagation mechanism of the transaction.
easymulti-datasource-r2dbc
Spring-data-r2dbc edition multi-data source component for responsive programming.
Easymulti-datasource – R2DBC implements dynamic routing interface for spring-data-R2DBC and supports declarative and programmatic multi-datasource dynamic switching for reactive programming. The Cluster mode supports a maximum of three data sources, while the master/slave mode supports one master/slave mode.
Add dependencies and configure data sources
After using the easyMulti-datasource – R2DBC, you do not need to add the spring-boot-starter-data-r2DBC dependency in the project, nor the spring-data-r2DBC dependency.
Easymulti-datasource – R2DBC version easyMulti-datasource – R2DBC version number
easymulti-datasource-r2dbc | spring-data-r2dbc |
---|---|
3.0.1 – RELEASE | 1.1.0. RELEASE |
Add easyMulti-datasource – R2DBC dependencies to the project as follows.
<dependency>
<groupId>com.github.wujiuye</groupId>
<artifactId>easymulti-datasource-r2dbc</artifactId>
<version>${version}</version>
</dependency>
Copy the code
In this case, you only need to add the driver dependency corresponding to the database type, for example, add the MYSQL R2DBC driver.
<dependency>
<groupId>dev.miku</groupId>
<artifactId>r2dbc-mysql</artifactId>
<version>0.8.2. RELEASE</version>
</dependency>
Copy the code
If the master/slave mode is used, the following configuration is used.
easymuti:
database:
r2dbc:
master-slave-mode:
master:
url: R2dbc: mysql: / / 127.0.0.1:3306 / r2dbc_stu
username: root
password:
pool:
max-size: 5
idel-timeout: 60
slave:
url: R2dbc: mysql: / / 127.0.0.1:3306 / r2dbc_stu
username: root
password:
pool:
max-size: 5
idel-timeout: 60
Copy the code
Master is set as the default data source, slave is set as the default data source, slave is set as the default data source, slave can be null if not. Although slave is allowed to be null, there is no need to use easyMulti-datasource – R2DBC if multiple data sources are not really needed.
If Cluster mode is used, the following configuration is used.
easymuti:
database:
r2dbc:
cluster-mode:
first:
url: R2dbc: mysql: / / 127.0.0.1:3306 / r2dbc_stu
username: root
password:
pool:
max-size: 5
idel-timeout: 60
second:
url: R2dbc: mysql: / / 127.0.0.1:3306 / r2dbc_stu
username: root
password:
pool:
max-size: 5
idel-timeout: 60
third:
url: R2dbc: mysql: / / 127.0.0.1:3306 / r2dbc_stu
username: root
password:
pool:
max-size: 5
idel-timeout: 60
Copy the code
First is set to the default data source, and second and third can be null.
Declaratively and dynamically switch data sources
Declarative dynamic switching of data sources is the dynamic switching of data sources using annotations. Simply add the @r2DBcDatabase annotation to the public method or class of the Spring bean and specify the value attribute of the annotation as the data source to use.
The sample code is as follows.
@Service
public class PersonService {
@Resource
private PersonRepository personRepository;
// The method returns a value of type Mono test
@R2dbcDataBase(MasterSlaveMode.Master)
@Transactional(rollbackFor = Throwable.class)
public Mono<Integer> addPerson(Person... persons) {
Mono<Integer> txOp = null;
for (Person person : persons) {
if (txOp == null) {
txOp = personRepository.insertPerson(person.getId(), person.getName(), person.getAge());
} else{ txOp = txOp.then(personRepository.insertPerson(person.getId(), person.getName(), person.getAge())); }}return txOp;
}
// The method returns a value of type Flux test
@R2dbcDataBase(MasterSlaveMode.Master)
@Transactional(rollbackFor = Throwable.class)
public Flux<Integer> addPersons(Flux<Person> persons) {
returnpersons.flatMap(person -> personRepository.insertPerson(person.getId(), person.getName(), person.getAge())); }}Copy the code
- In master-slave mode,
@R2dbcDataBase
annotationsvalue
Attribute Optional values seeMasterSlaveMode
Interface declared constants; - If it is
Cluster
Mode,@R2dbcDataBase
annotationsvalue
Attribute Optional values seeClusterMode
Interface declared constants;
Programmatically switch data sources dynamically
Declaratively switching data sources relies on programmatically switching data sources, so we can write code to switch data sources without exposing methods to public.
Only need to call the static method putDataSource EasyMutiR2dbcRoutingConnectionFactory provide the Context to use the data source, the code is as follows.
public class RoutingTest extends SupporSpringBootTest {
@Resource
private DatabaseClient client;
@Resource
private ReactiveTransactionManager reactiveTransactionManager;
@Test
public void test(a) throws InterruptedException {
TransactionalOperator operator = TransactionalOperator.create(reactiveTransactionManager);
Mono<Void> atomicOperation = client.execute("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
.bind("id"."joe")
.bind("name"."Joe")
.bind("age".34)
.fetch().rowsUpdated()
.then(client.execute("INSERT INTO person (id, name) VALUES(:id, :name)")
.bind("id"."joe")
.bind("name"."Joe")
.fetch().rowsUpdated())
.then();
// Wrap transaction
Mono<Void> txOperation = operator.transactional(atomicOperation);
// Wrap switch data sourceEasyMutiR2dbcRoutingConnectionFactory.putDataSource(txOperation, MasterSlaveMode.Slave).subscribe(); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); }}Copy the code
Note, if you need to use transactions, you must first call TransactionalOperator transactional method of an object, then call EasyMutiR2dbcRoutingConnectionFactory putDataSource method.