Spring Data R2DBC響應式操作MySQL
1. 前言
在 使用R2DBC操作MySQL數據庫 一文中初步介紹了 r2dbc-mysql 的使用。由於藉助 DatabaseClient
操作 MySQL ,過於初級和底層,不利於開發。今天就利用 Spring Data R2DBC 來演示 Spring 數據存儲抽象(Spring Data Repository) 風格的 R2DBC 數據庫操作。
請注意 :目前 Spring Data R2DBC 雖然已經迭代了多個正式版,但是仍然處於初級階段,還不足以運用到生產中。不過未來可期,值得研究學習。
2. Spring Data R2DBC
Spring Data R2DBC提供了基於 R2DBC 反應式關係數據庫驅動程序的流行的 Repository 抽象。但是這並不是一個ORM框架,你可以把它看做一個數據庫訪問的抽象層或者 R2DBC 的客戶端程序。它不提供 ORM 框架具有的緩存、懶加載等諸多特性,但它抽象了數據庫和對象的抽象映射關係,具有輕量級、易用性的特點。
2.1 版本對應關係
胖哥總結了截至目前 Spring Data R2DBC 和 Spring Framework 的版本對應關係:
Spring Data R2DBC | Spring Framework |
---|---|
1.0.0.RELEASE | 5.2.2.RELEASE |
1.1.0.RELEASE | 5.2.6.RELEASE |
1.1.1.RELEASE | 5.2.7.RELEASE |
1.1.2.RELEASE | 5.2.8.RELEASE |
一定要注意版本對應關係,避免不兼容的情況。
3. 基礎依賴
上次我沒有引用 R2DBC 連接池,這次我將嘗試使用它。主要依賴如下 ,這裏我還集成了 Spring Webflux :
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-r2dbc</artifactId> </dependency> <!-- r2dbc 連接池 --> <dependency> <groupId>io.r2dbc</groupId> <artifactId>r2dbc-pool</artifactId> </dependency> <!--r2dbc mysql 庫--> <dependency> <groupId>dev.miku</groupId> <artifactId>r2dbc-mysql</artifactId> </dependency> <!--自動配置需要引入的一個嵌入式數據庫類型對象--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <!-- 反應式web框架 webflux--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
這裏我採用的是 Spring Boot 2.3.2.RELEASE 。
4. 配置
上次我們採用的是 JavaConfig 風格的配置,只需要向 Spring IoC 注入一個 ConnectionFactory
。這一次我將嘗試在 application.yaml
中配置 R2DBC 的必要參數。
spring: r2dbc: url: r2dbcs:mysql://127.0.0.1:3306/r2dbc username: root password: 123456
以上就是 R2DBC 的主要配置。特別注意的是 spring.r2dbc.url
的格式,根據數據庫的不同寫法是不同的,要看驅動的定義,這一點非常重要。連接池這裏使用默認配置即可,不用顯式定義。
5. 編寫業務代碼
接下來就是編寫業務代碼了。這裏我還嘗試使用 DatabaseClient
來執行了 DDL 語句創建了 client_user
表,感覺還不錯。
@Autowired DatabaseClient databaseClient; @Test void doDDL() { List<String> ddl = Collections.unmodifiableList(Arrays.asList("drop table if exists client_user;", "create table client_user(user_id varchar(64) not null primary key,nick_name varchar(32),phone_number varchar(16),gender tinyint default 0) charset = utf8mb4;")); ddl.forEach(sql -> databaseClient.execute(sql) .fetch() .rowsUpdated() .as(StepVerifier::create) .expectNextCount(1) .verifyComplete()); }
5.1 聲明數據庫實體
熟悉 Spring Data JPA 的同學應該很輕車熟路了。
/** * the client user type * * @author felord.cn */ @Data @Table public class ClientUser implements Serializable { private static final long serialVersionUID = -558043294043707772L; @Id private String userId; private String nickName; private String phoneNumber; private Integer gender; }
5.2 聲明CRUD接口
上面實體類中的 @Table
註解是有說法的,當我們的操作接口繼承的是 ReactiveCrudRepository<T, ID>
或者 ReactiveSortingRepository<T, ID>
時,需要在實體類上使用 @Table
註解,這也是推薦的用法。
public interface ReactiveClientUserSortingRepository extends ReactiveSortingRepository<ClientUser,String> { }
當然實體類不使用 @Table
註解標記時,我們還可以繼承 R2dbcRepository<T, ID>
接口。然後 ReactiveClientUserSortingRepository
將提供一些操作數據庫的方法。
然後 Spring Data JPA 怎麼寫,這裏也差不多怎麼寫,但是有些功能現在還沒有得到支持,比如上面提到的分頁,還有主鍵策略等。
類似 PagingAndSortingRepository<T,ID>
的反應式分頁功能接口目前還沒有實裝,會在未來的版本集成進來。
5.3 實際操作
接下來我們就要通過 R2DBC 實際操作 MySQL 數據庫了。按照我們傳統的邏輯寫了如下的新增邏輯:
ClientUser clientUser = new ClientUser(); clientUser.setGender(2); clientUser.setNickName("r2dbc"); clientUser.setPhoneNumber("9527"); clientUser.setUserId("snowflake"); Mono<ClientUser> save = reactiveClientUserSortingRepository.save(clientUser);
結果數據庫並沒有寫入數據。這時因爲 r2dbc-mysql 不能被直接使用,只能由客戶端去實現並委託給客戶端去操作。
這也是 R2DBC 的設計原則,R2DBC的目標是最小化SPI平面,目的是消除數據庫之間的差異部分,並使得整個數據庫完全具有反應式和背壓。它主要用作客戶端庫使用的驅動程序SPI,而不打算直接在應用程序代碼中使用。
所以這裏我們可以藉助於 reactor-test
測試庫去執行一下,改寫爲:
reactiveClientUserSortingRepository.save(clientUser) .log() .as(StepVerifier::create) .expectNextCount(1) .verifyComplete();
但是依然不能執行成功,提示 update table [client_user]. Row with Id [snowflake] does not exist
,也就是說期望執行的是新增但是實際執行的是更新,由於數據庫找不到主鍵爲 snowflake
的記錄就報了錯。這裏爲什麼是更新呢?
這時因爲實體類在進行新增時會判斷主鍵是否填充,如果沒有填充就認爲是新數據,採取真正的新增操作,主鍵需要數據庫來自動填充;如果主鍵存在值則認爲是舊數據則調用更新操作。胖哥同 Spring Data R2DBC 的項目組溝通後並沒有得到友好的解決方案,不過我已經找到了方法,這裏先留個坑。
那麼該如何新增一條數據呢?我們只能藉助於 @Query
註解來編寫一條 SQL
寫入了:
@Modifying @Query("insert into client_user (user_id,nick_name,phone_number,gender) values (:userId,:nickName,:phoneNumber,:gender)") Mono<Integer> addClientUser(String userId, String nickName, String phoneNumber, Integer gender);
當添加了 @Modifying
後,返回值可以從 Mono<ClientUser>
、 Mono<Boolean>
或者 Mono<Integer>
任意一種選擇。
reactiveClientUserSortingRepository .addClientUser("snowflake", "r2dbc", "132****155", 0) .as(StepVerifier::create) .expectNextCount(1) .verifyComplete();
這樣就證明寫成功了一條數據。
5.4 搭配Webflux使用
但是實際中該如何應用呢?目前能夠想到的就是結合反應式框架 Spring Webflux 了,就像 Spring Data JPA 配合 Spring MVC 一樣。
我們編寫一個 Webflux 接口:
@RestController @RequestMapping("/user") public class ReactiveClientUserController { @Autowired private ReactiveClientUserSortingRepository reactiveClientUserSortingRepository; /** * 這裏爲了檢驗默認api 就不分層了 * * @param userId the user id * @return the mono */ @GetMapping("/{userId}") public Mono<ClientUser> findUserById(@PathVariable String userId) { return reactiveClientUserSortingRepository.findById(userId); } }
5.5 一些測試數據參考
在低併發時, Spring MVC + JDBC 表現最佳,但在高併發下, WebFlux + R2DBC 使用每個已處理請求的內存最少。
在高併發下, Spring MVC + JDBC 的響應時間開始下降。顯然, R2DBC 在更高的併發性下提供了更好的響應時間。 Spring WebFlux 也比使用 Spring MVC 的類似實現更好。
6. 總結
今天對 Spring Data R2DBC 進一步演示,相信你能夠從中學到一些東西。由於 R2DBC 還是比較新,還存在一些需要改進和補充的東西。目前社區非常活躍,發展十分迅速。好了今天的文章就到這裏,原創不易多多關注: 碼農小胖哥 如果你覺得本文很有用,請點贊、轉發、再看。
關注公衆號:Felordcn 獲取更多資訊