はじめに
r2dbc-mysql の使い方は、 で紹介しました。DatabaseClientを使ったMySQLの操作はあまりにも初歩的で低レベルなので、開発には向いていません。今日はSpring Data R2DBCを使って、Spring Data Store抽象化スタイルのR2DBCデータベース操作のデモを行います。
注意: 現在Spring Data R2DBCはまだ発展途上で、いくつかの公式リリースで反復されていますが、本番で使えるほどではありません。しかし、将来は有望であり、勉強して学ぶ価値があります。
Spring Data R2DBC
Spring Data R2DBCは、R2DBCリアクティブリレーショナルデータベースドライバに基づいて、一般的なリポジトリの抽象化を提供します。しかし、これはORMフレームワークではなく、データベースアクセスやR2DBCクライアントプログラムの抽象化レイヤと考えることができます。これは、ORMフレームワークは、キャッシュ、遅延ローディングや他の多くの機能を提供していませんが、それは、軽量で使いやすいと、データベースとオブジェクト間の抽象的なマッピング関係を抽象化します。
バージョン対応
Fattyは今日現在の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 |
非互換を避けるために、必ずバージョン対応に注意してください。
Foundationの依存関係
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>
<!-- リアクティブ・ウェブ・フレームワーク>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
ここでは SpringBoot 2.3.2.RELEASEを使っています。
コンフィギュレーション
spring:
r2dbc:
url: r2dbcs:mysql://.1:3306/r2dbc
username: root
password: 123456
上記は、R2DBCの主な構成です。特別な注意がspring.r2dbc.urlの形式に支払われ、データベースに応じて異なる書かれている、ドライバの定義に応じて、これは非常に重要です。接続プーリングは、ここでデフォルトの構成を使用することができます、明示的に定義する必要はありません。
ビジネスコードを書く
次のステップはビジネスコードを書くことです。ここでもDatabaseClientを使ってclient_userテーブルを作成するDDL文を実行してみましたが、なかなかいい感じでした。
@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());
}
データベースエンティティの宣言
Spring Data JPAに慣れ親しんでいる学生であれば、非常によく理解しているはずです。
/**
* the client user type
*
*
*/
@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;
}
CRUDインターフェースの宣言
ReactiveCrudRepository<T, ID>
ReactiveSortingRepository<T, ID>
上記のエンティティ・クラスの@Tableアノテーションは、継承する操作インタフェースが.
public interface ReactiveClientUserSortingRepository extends ReactiveSortingRepository<ClientUser,String> {
}
R2dbcRepository<T, ID>
もちろん、@Table アノテーションが付けられていない場合は、エンティティ・クラスもインタフェースを継承できます。ReactiveClientUserSortingRepository
そして、データベースを操作するためのメソッドを提供します。
次にSpring Data JPAの書き方ですが、こちらもほぼ書き方は同じですが、上記のページングや主キーポリシーなど、まだサポートされていない機能もあります。
PagingAndSortingRepository<T,ID>
同様のリアクティブページング機能のインターフェイスは、現在稼働しておらず、将来のリリースで統合される予定です。
実用的
次のステップは、R2DBCを通して実際にMySQLデータベースを操作することです。以下の追加ロジックは、従来のロジックに従って書きました:
ClientUser clientUser = new ClientUser();
clientUser.setGender(2);
clientUser.setNickName("r2dbc");
clientUser.setPhoneNumber("9527");
clientUser.setUserId("snowflake");
Mono<ClientUser> save = reactiveClientUserSortingRepository.save(clientUser);
その結果、データはデータベースに書き込まれません。これは、r2jdbc-mysqlを直接使用することができず、実装してクライアントに委譲することしかできないためです。
これは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
しかし、まだ正常に実行することはできません、プロンプトは、データベースが雪片レコードの主キーを見つけることができないため、新しいが、実際には更新を実行すると予想される、と言うことですエラーが報告されました。なぜそれは更新ですか?
これは、新しいのエンティティクラスは、プライマリキーが満たされているかどうかを決定するため、満たされていない場合は、新しいデータとみなされ、実際の新しい操作を取るには、プライマリキーが自動的にデータベースによって満たされる必要があります。太っちょは、Spring DataのR2DBCプロジェクトチームと通信し、友好的な解決策を得ませんでしたが、私は最初にここに穴を残す方法を見つけました。
では、どうやって新しいデータを追加するのでしょうか?SQLを記述するには、@Queryアノテーションを使用します:
@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);
Mono<ClientUser>
Modifyingを付けると、返り値は、モノ、
reactiveClientUserSortingRepository
.addClientUser("snowflake",
"r2dbc",
"132****155",
0)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
これは、正常にデータが書き込まれたことを証明するものです。
Webfluxとの併用
しかし、実際にはどのように適用するのでしょうか?現在考えられるのは、Spring Data JPAとSpring MVCのように、リアクティブフレームワークSpring Webfluxの組み合わせです。
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);
}
}
並行性が低い場合は、Spring MVC + JDBCが最も良いパフォーマンスを示しますが、並行性が高い場合は、WebFlux + R2DBCがリクエスト処理あたりのリソース使用量が最も少なくなります。
高い同時実行数では、Spring MVC + JDBCの応答時間は低下し始めます。また、Spring WebFluxはSpring MVCを使った同様の実装よりも優れています。