无法使用Oracle R2DBC驱动程序与Spring Data响应式存储库执行任何查询



我是第一次使用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

最新更新