我是第一次使用SpringData响应式存储库。
我一直在浏览官方文档,并创建了一个基本的CRUD API来使用它们。
我从H2开始,只是为了简单,一切都如预期的那样工作。
当我尝试创建一个新实体时,一切正常:
% curl -v -# -X POST http://localhost:8080/wallet/
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /wallet/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 201 Created
< Content-Type: application/json
< Content-Length: 57
<
* Connection #0 to host localhost left intact
{"id":"6cccd902-01a4-4a81-8166-933b2a109ecc","balance":0}
代码非常简单(与SpringData存储库一样):
import com.jfcorugedo.reactivedemo.wallet.model.Wallet;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
public interface WalletRepository extends ReactiveCrudRepository<Wallet, String> {
}
和控制器:
import com.jfcorugedo.reactivedemo.wallet.dao.WalletRepository;
import com.jfcorugedo.reactivedemo.wallet.model.Wallet;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
import static org.springframework.data.relational.core.query.Query.query;
@RestController
@RequestMapping("wallet")
@Slf4j
public class WalletController {
private WalletRepository walletRepository;
@Autowired
private R2dbcEntityTemplate template;
public WalletController(WalletRepository walletRepository) {
this.walletRepository = walletRepository;
}
@GetMapping("{id}")
public Mono<ResponseEntity<Wallet>> get(@PathVariable("id") String id) {
return walletRepository
.findById(id)
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@GetMapping("count")
public Mono<ResponseEntity<Long>> count() {
return walletRepository
.count()
.map(ResponseEntity::ok)
.defaultIfEmpty(ResponseEntity.notFound().build());
}
@PostMapping
public Mono<ResponseEntity<Wallet>> create() {
return walletRepository
.save(Wallet.empty())
.map(w -> ResponseEntity.status(201).body(w));
}
@PostMapping("/entityTemplate")
public Mono<ResponseEntity<Wallet>> insert() {
log.info("Inserting using R2dbcEntityTemplate");
return template.insert(new Wallet(null, BigDecimal.ZERO))
.map(ResponseEntity::ok);
}
}
DTO也很简单:
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import java.math.BigDecimal;
@AllArgsConstructor
@Getter
public class Wallet {
@Id
private String id;
private BigDecimal balance;
public static Wallet empty() {
return new Wallet(null, BigDecimal.ZERO);
}
public Wallet withId(String id) {
return new Wallet(id, this.balance);
}
}
然后我在文档中检查Oracle也被支持。
我看了Oracle的官方驱动文档。
这个驱动程序确实还在开发中,所以还不能用于生产。
然而,我克隆了存储库,并尝试在本地Oracle实例上执行一些测试,一切都运行良好。
下面是我直接使用Oracle驱动程序执行的代码:
String r2dbcUrl = "r2dbc:oracle://?oracleNetDescriptor="+DESCRIPTOR;
Mono.from(ConnectionFactories.get(ConnectionFactoryOptions.parse(r2dbcUrl)
.mutate()
.option(ConnectionFactoryOptions.USER, USER)
.option(ConnectionFactoryOptions.PASSWORD, PASSWORD)
.build())
.create())
.flatMapMany(connection ->
Mono.from(connection.createStatement(
"INSERT INTO WALLET (ID, BALANCE) VALUES ('" + UUID.randomUUID().toString() + "', 0)")
.execute())
.flatMapMany(result ->
result.map((row, metadata) -> row.get(0, String.class)))
.concatWith(Mono.from(connection.close()).cast(String.class)))
.toStream()
.forEach(System.out::println);
// A descriptor may also be specified as an Option
Mono.from(ConnectionFactories.get(ConnectionFactoryOptions.builder()
.option(ConnectionFactoryOptions.DRIVER, "oracle")
.option(Option.valueOf("oracleNetDescriptor"), DESCRIPTOR)
.option(ConnectionFactoryOptions.USER, USER)
.option(ConnectionFactoryOptions.PASSWORD, PASSWORD)
.build())
.create())
.flatMapMany(connection ->
Mono.from(connection.createStatement(
"SELECT * from wallet")
.execute())
.flatMapMany(result ->
result.map((row, metadata) -> row.get(0, String.class)))
.concatWith(Mono.from(connection.close()).cast(String.class)))
.toStream()
.forEach(System.out::println);
我使用的代码Oracle开发人员提供的样本文件夹。
执行此代码后,一切正常,并在我的钱包表中创建了新行。
最后,我尝试在SpringData中做同样的事情。
我使用了完全相同的描述符,USER和PASSWORD来连接Oracle。
这是我用来获取ConnectionFactory的配置类:
package com.jfcorugedo.reactivedemo.config;
import com.jfcorugedo.reactivedemo.wallet.model.Wallet;
import io.r2dbc.spi.ConnectionFactories;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
import reactor.core.publisher.Mono;
import java.util.UUID;
@Configuration
@ConditionalOnProperty(name = "dababase.vendor", havingValue = "oracle")
@Slf4j
public class OracleR2dbcConfig extends AbstractR2dbcConfiguration {
@Value("${database.host:localhost}")
private String host;
@Value("${database.port:1521}")
private int port;
@Value("${database.serviceName}")
private String serviceName;
@Override
@Bean("r2dbcConnectionFactory")
public ConnectionFactory connectionFactory() {
String descriptor = "(DESCRIPTION=" +
"(ADDRESS=(HOST=" + host + ")(PORT=" + port + ")(PROTOCOL=tcp))" +
"(CONNECT_DATA=(SERVICE_NAME=" + serviceName + ")))";
log.info("Creating connection factory with descriptor " + descriptor);
String r2dbcUrl = "r2dbc:oracle://?oracleNetDescriptor="+descriptor;
return ConnectionFactories.get(ConnectionFactoryOptions.parse(r2dbcUrl)
.mutate()
.option(ConnectionFactoryOptions.USER, "jfcorugedo")
.option(ConnectionFactoryOptions.PASSWORD, System.getenv("DB_PASSWORD"))
.build());
}
@Bean
BeforeConvertCallback<Wallet> idGenerator() {
return (entity, table) -> entity.getId() == null ? Mono.just(entity.withId(UUID.randomUUID().toString())) : Mono.just(entity);
}
}
它与我在另一个项目中使用的非常相似:
private static final String DESCRIPTOR = "(DESCRIPTION=" +
"(ADDRESS=(HOST="+HOST+")(PORT="+PORT+")(PROTOCOL=tcp))" +
"(CONNECT_DATA=(SERVICE_NAME="+SERVICE_NAME+")))";
...
String r2dbcUrl = "r2dbc:oracle://?oracleNetDescriptor="+DESCRIPTOR;
Mono.from(ConnectionFactories.get(ConnectionFactoryOptions.parse(r2dbcUrl)
.mutate()
.option(ConnectionFactoryOptions.USER, USER)
.option(ConnectionFactoryOptions.PASSWORD, PASSWORD)
.build())
.create())
...
切换到Oracle后,SpringBoot应用程序启动没有任何错误:
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` |
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.3)
2021-08-05 12:48:22.891 INFO 99453 --- [ main] c.j.r.ReactiveDemoApplication : Starting ReactiveDemoApplication using Java 11.0.10 on APM3LC02CH2VNMD6R with PID 99453 (/Users/lp68ba/Developer/personal/reactive-demo/target/classes started by lp68ba in /Users/lp68ba/Developer/personal/reactive-demo)
2021-08-05 12:48:22.892 INFO 99453 --- [ main] c.j.r.ReactiveDemoApplication : No active profile set, falling back to default profiles: default
2021-08-05 12:48:23.165 INFO 99453 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2021-08-05 12:48:23.214 INFO 99453 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 45 ms. Found 1 R2DBC repository interfaces.
2021-08-05 12:48:23.554 INFO 99453 --- [ main] c.j.r.config.OracleR2dbcConfig : Creating connection factory with descriptor (DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=ORCLCDB)))
2021-08-05 12:48:24.129 INFO 99453 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080
2021-08-05 12:48:24.142 INFO 99453 --- [ main] c.j.r.ReactiveDemoApplication : Started ReactiveDemoApplication in 1.466 seconds (JVM running for 2.021)
但是现在当我尝试执行任何操作时,连接保持打开状态,没有任何事情发生:
% curl -v -# -X POST http://localhost:8080/wallet/
* Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /wallet/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
在应用程序的日志中我可以看到这样的跟踪:
2021-08-05 13:08:20.735 DEBUG 144 --- [nPool-worker-19] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO WALLET (ID, BALANCE) VALUES (:P0_id, :P1_balance)]
然而,执行永远不会结束,并且在数据库中没有创建任何内容。
我尝试了两个:Spring Data Reactive Repositories和R2DBCEntityTemplate,结果相同。
我已经生成了一个自定义版本的Oracle R2DBC驱动程序与一些跟踪,这是我得到的:
直接使用Oracle R2DBC驱动程序:
Creating OracleConnectionFactoryImpl with options: ConnectionFactoryOptions{options={driver=oracle, oracleNetDescriptor=(DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=ORCLCDB))), password=REDACTED, user=jfcorugedo}}
Oracel reactive adapter obtained: oracle.r2dbc.impl.OracleReactiveJdbcAdapter@c33b74f
Datasource obtained: oracle.jdbc.pool.OracleDataSource@696da30b
Creating a new connection
using adatper y datasource to create a new connection
Creating a OracleConnectionImpl with JDBC connection oracle.jdbc.driver.T4CConnection@10f7f7de
createStatement(sql): INSERT INTO WALLET (ID, BALANCE) VALUES ('9a3ab3db-ec38-4544-ac87-4e1a4ad40343', 0)
close()
使用SpringData响应式存储库(连接卡住,什么都没有发生):
. ____ _ __ _ _
/\ / ___'_ __ _ _(_)_ __ __ _
( ( )___ | '_ | '_| | '_ / _` |
\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |___, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.3)
2021-08-05 13:12:49.557 INFO 304 --- [ main] c.j.r.ReactiveDemoApplication : Starting ReactiveDemoApplication using Java 11.0.10 on APM3LC02CH2VNMD6R with PID 304 (/Users/lp68ba/Developer/personal/reactive-demo/target/classes started by lp68ba in /Users/lp68ba/Developer/personal/reactive-demo)
2021-08-05 13:12:49.559 INFO 304 --- [ main] c.j.r.ReactiveDemoApplication : No active profile set, falling back to default profiles: default
2021-08-05 13:12:49.849 INFO 304 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
2021-08-05 13:12:49.891 INFO 304 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 38 ms. Found 1 R2DBC repository interfaces.
2021-08-05 13:12:50.208 INFO 304 --- [ main] c.j.r.config.OracleR2dbcConfig : Creating connection factory with descriptor (DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=ORCLCDB)))
Creating OracleConnectionFactoryImpl with options: ConnectionFactoryOptions{options={driver=oracle, oracleNetDescriptor=(DESCRIPTION=(ADDRESS=(HOST=localhost)(PORT=1521)(PROTOCOL=tcp))(CONNECT_DATA=(SERVICE_NAME=ORCLCDB))), password=REDACTED, user=jfcorugedo}}
Oracel reactive adapter obtained: oracle.r2dbc.impl.OracleReactiveJdbcAdapter@5f172d4a
Datasource obtained: oracle.jdbc.pool.OracleDataSource@934b52f
2021-08-05 13:12:50.736 INFO 304 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080
2021-08-05 13:12:50.745 INFO 304 --- [ main] c.j.r.ReactiveDemoApplication : Started ReactiveDemoApplication in 1.417 seconds (JVM running for 4.428)
Creating a new connection
using adatper y datasource to create a new connection
Creating a OracleConnectionImpl with JDBC connection oracle.jdbc.driver.T4CConnection@42dce884
2021-08-05 13:12:54.481 DEBUG 304 --- [nPool-worker-19] o.s.r2dbc.core.DefaultDatabaseClient : Executing SQL statement [INSERT INTO WALLET (ID, BALANCE) VALUES (:P0_id, :P1_balance)]
OracleConnectionImpl#createStatement(sql): INSERT INTO WALLET (ID, BALANCE) VALUES (:P0_id, :P1_balance)
Creating OracleStatementImpl with SQL: INSERT INTO WALLET (ID, BALANCE) VALUES (:P0_id, :P1_balance)
OracleConnectionImpl#close()
我不知道为什么执行卡住了SpringData。连接似乎是好的,我在这里使用的参数与我直接使用Oracle驱动程序完全相同。
是否有人有使用SpringData R2DBC存储库和Oracle R2DBC驱动程序的工作示例?
您可以在此存储库中检查代码。
目前,使用Spring Data编程时,请坚持使用Oracle R2DBC 0.1.0版本。
更新版本的Oracle R2DBC实现0.9.0版本。R2DBC SPI的M1,目前Spring Data不支持。这在GitHub的讨论中得到了证实:https://github.com/oracle/oracle-r2dbc/issues/30 issuecomment - 862989986
当我回滚到Oracle R2DBC的0.1.0版本时,我能够让演示应用程序工作。我不得不重构OracleR2dbcConfig.java,因为直到0.1.0之后才添加了对Oracle Net描述符的支持。一个普通的URL可以很好地配置主机、端口和服务名:
public ConnectionFactory connectionFactory() {
String url =
String.format("r2dbc:oracle://%s:%d/%s", host, port, serviceName);
log.info("Creating connection factory with URL:" + url);
return ConnectionFactories.get(ConnectionFactoryOptions.parse(url)
.mutate()
.option(ConnectionFactoryOptions.USER, user)
.option(ConnectionFactoryOptions.PASSWORD, System.getenv("DB_PASSWORD"))
.build());
}
而且,我必须在执行curl测试之前手动创建表:
create table wallet (id VARCHAR2(256), balance NUMBER);
我认为Spring Data通常自动创建表,所以我不确定为什么我必须手动这样做。如果没有创建表,则INSERT操作将失败,并显示钱包表不存在的错误:
ORA-04043: object WALLET does not exist
经过这些修改后,curl命令似乎运行正常:
curl -v -# -X POST http://localhost:8080/wallet/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /wallet/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 201 Created
< Content-Type: application/json
< Content-Length: 57
<
* Connection #0 to host localhost left intact
{"id":"2bcecf46-05eb-46b4-90ec-cfacff2bbaa8","balance":0}* Closing connection 0