【发布时间】:2021-10-10 09:42:33
【问题描述】:
我是第一次使用 SpringData Reactive Repositories。
我一直在研究official documentation,并创建了一个基本的 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 Repositories):
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 is also supported的文档。
我又翻了官方Oracle driver documentation。
这个驱动程序确实正在开发中,所以它还没有准备好生产。
但是,我克隆了存储库并尝试在本地 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 开发人员在示例文件夹中提供的代码。
执行此代码后,一切正常,并在我的 WALLET 表中创建了一个新行。
最后我尝试在 SpringData 中做同样的事情。
我使用完全相同的 DESCRIPTOR、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 Reactive Repositories(连接卡住,没有任何反应):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: 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 驱动程序的工作示例吗?
您可以查看repository中的代码。
【问题讨论】:
-
请发布您的控制器代码等。您的“其他代码”不是那么相关。相关的是执行的代码。例如,您为什么打电话给
repo.save(Mono.empty())在此期间会发生什么?这对我来说有点不清楚。 -
完成!无论如何,您都有 Github 存储库的 URL。关于保存操作,请注意:不是
Mono.empty()而是Wallet.empty(),我的DTO中生成空钱包的方法 -
如果不将其包装在 ResponseEntity 中会怎样?可以使用
@ResponseStatus注解来设置方法的HTTP状态码。 -
我可以测试,但我认为控制器没有任何问题。它适用于任何其他数据库
-
感谢@jfcorugedo 报告此事。我应该有时间在第二天左右调试它。看起来 Oracle R2DBC 正在做其他驱动程序没有做的事情。我会想纠正那个。
标签: oracle spring-boot spring-webflux spring-data-r2dbc r2dbc