r2dbc介紹 及 動態資料來源實現方式
一、基本概念:
傳統情況Java 使用 JDBC 來操作關係型資料庫,而 JDBC 是阻塞的、同步的,即使使用執行緒池進行改善也是有限的。基於此,Spring官方(Pivotal)提出了R2DBC(Reactive Relational Database Connectivity)。R2DBC是一項API規範計劃,它聲明瞭一個反應式API,該方法將由資料庫廠商實現以訪問其關係資料庫。
目前實現了R2DBC的資料庫驅動有:H2、MariaDB、Microsoft SQL Server、MySQL 、jasync-sql MySQL、Postgres 。
使用maven新增兩個依賴:r2dbc 介面、資料庫驅動實現。
例如:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-r2dbc</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>com.github.jasync-sql</groupId> <artifactId>jasync-r2dbc-mysql</artifactId> <version>1.1.4</version> </dependency>
注:Maven Central到目前為止還沒有R2DBC工件,因此我們還需要在專案中新增幾個Spring的儲存庫。
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories>
二、直接使用r2bdc
使用r2dbc訪問關係型資料庫的核心是建立一個io.r2dbc.spi.ConnectionFactory介面的例項(通常使用單例)。如:
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.test.StepVerifier;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
public class R2dbcApp {
private static final Log log = LogFactory.getLog(R2dbcApp.class);
public static void main(String[] args) {
ConnectionFactory connectionFactory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
R2dbcEntityTemplate template = new R2dbcEntityTemplate(connectionFactory);
template.getDatabaseClient().sql("CREATE TABLE person" +
"(id VARCHAR(255) PRIMARY KEY," +
"name VARCHAR(255)," +
"age INT)")
.fetch()
.rowsUpdated()
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
template.insert(Person.class)
.using(new Person("joe", "Joe", 34))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
template.select(Person.class)
.first()
.doOnNext(it -> log.info(it))
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}
}
也可通過spring的方式建立連線工廠。
@Configuration
public class ApplicationConfiguration extends AbstractR2dbcConfiguration {
@Override
@Bean
public ConnectionFactory connectionFactory() {
return …
}
}
ConnectionFactory建立完成後,可通過它得到訪問資料庫的操作類:R2dbcEntityTemplate(提供了面向實體的資料庫訪問操作)。
三、使用spring repository 支援
需要使用註解@EnableR2dbcRepositories開啟功能。如下
@Configuration
@EnableR2dbcRepositories
class ApplicationConfig extends AbstractR2dbcConfiguration {
@Override
public ConnectionFactory connectionFactory() {
return …
}
}
再建立相應的repository介面
public interface PersonRepository extends ReactiveCrudRepository<Person, Long> {
// additional custom query methods go here
}
然後就可以使用repository特性來進行資料庫操作了,例如:
@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {
@Autowired
PersonRepository repository;
@Test
void readsAllEntitiesCorrectly() {
repository.findAll()
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}
@Test
void readsEntitiesByNameCorrectly() {
repository.findByFirstname("Hello World")
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
}
}
四、r2dbc動態資料來源的實現方案
r2dbc提供了org.springframework.r2dbc.connection.lookup.AbstractRoutingConnectionFactory抽象類,並需要我們自己實現protected abstract Mono<Object> determineCurrentLookupKey();
方法以達到動態切換資料來源的目的。
專案中實現動態資料來源的流程如下:
1、spring ioc啟動時,通過jdbc方式載入中心庫資料來源,由中心庫查詢對話庫配置資訊及相應公司id與serverKey對應關係(serverKey與資料來源一對一,公司id與serverKey多對一)
2、通過對話庫配置資訊建立多個ConnectionFactory
,得到 Map<String, ConnectionFactory> connectionFactoryMap
。此hash的key為serverKey,value為一個可用的ConnectionFactory
3、呼叫AbstractRoutingConnectionFactory
的public void setTargetConnectionFactories(Map<?, ?> targetConnectionFactories)
方法設定值連線工廠集合
4、呼叫AbstractRoutingConnectionFactory
的public void setDefaultTargetConnectionFactory(Object defaultTargetConnectionFactory)
設定預設連線工廠
5、重寫protected abstract Mono<Object> determineCurrentLookupKey()
方法。從**Context**
中獲取公司id對應的**serverKey**
注:此處需要使用到reactor的高階特性:Context
(具有與ThreadLocal
類似的功能),它是一個鍵值對的資料結構。它作用於一個 Flux
或一個 Mono
上,而不是應用於一個執行緒(Thread
)。
並且它使用 Subscription
的傳播機制來讓自己對每一個操作符都可見(從最後一個 subscribe
沿鏈向上)。因此需要在呼叫鏈最後或儘可能後的位置 (呼叫 subscribe()
前 )建立Context
並將其繫結到Flux
或Mono
上。
6、將我們自己實現的AbstractRoutingConnectionFactory
的子類通過@Bean注入ioc容器
7、在進行資料庫操作時將公司id繫結到Flux
或Mono
中的Context
上。
參考文件:
https://docs.spring.io/spring-data/r2dbc/docs/current/reference/html/#get-started:first-steps:what
https://projectreactor.io/docs/core/release/reference/index.html#context