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 monitorSQL, 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;
  • useAPISwitching 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, interceptMapperMethod 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.addCurrentTransactionMethodPopListenerBind 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,@R2dbcDataBaseannotationsvalueAttribute Optional values seeMasterSlaveModeInterface declared constants;
  • If it isClusterMode,@R2dbcDataBaseannotationsvalueAttribute Optional values seeClusterModeInterface 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.