我正在开发一个多租户反应式应用程序,使用 Spring-Webflux + Spring-data-r2dbc 和 r2dbc 驱动程序连接到 Postgresql 数据库。多租户部分是基于模式的:每个租户一个模式。因此,根据上下文(例如用户登录),请求将命中数据库的特定架构。
我正在努力研究如何在 r2dbc 中实现这一点。理想情况下,这将是 Hibernate 使用MultiTenantConnectionProvider 的方式(参见示例 16.3)。
到目前为止我发现了什么以及我做了什么:
我查看了PostgresqlConnectionFactory。有趣的是,有prepareConnection一个电话setSchema(connection):
private Mono<Void> setSchema(PostgresqlConnection connection) {
if (this.configuration.getSchema() == null) {
return Mono.empty();
}
return connection.createStatement(String.format("SET SCHEMA '%s'", this.configuration.getSchema()))
.execute()
.then();
}
Run Code Online (Sandbox Code Playgroud)我可能需要找到一种方法来覆盖它,以便从上下文而不是配置中动态获取模式?
否则,我可以尝试将请求中的架构指定为表前缀:
String s = "tenant-1";
databaseClient.execute("SELECT * FROM \"" + s + "\".\"city\"")
.as(City.class)
.fetch()
.all()
Run Code Online (Sandbox Code Playgroud)但我不能再使用 SpringData,或者我需要重写每个请求以将租户作为参数传递。
任何提示/帮助表示赞赏:)
我有这些简化的表格
CREATE TABLE address(
id VARCHAR(36) NOT NULL PRIMARY KEY,
zip VARCHAR(5) NOT NULL,
city VARCHAR(32) NOT NULL
)
CREATE TABLE customer(
id VARCHAR(36) NOT NULL PRIMARY KEY,
name VARCHAR(32) NOT NULL,
address_fk VARCHAR(36) NOT NULL,
FOREIGN KEY (address_fk) REFERENCES address(id)
)
Run Code Online (Sandbox Code Playgroud)
以及这些简化的 Kotlin 类:
data class Address(val id: String, val zip: String, val city: String)
data class Kunde(val id: String?, val name: String, val address: Address)
Run Code Online (Sandbox Code Playgroud)
当我使用@Column(address_fk)该属性时address,我得到一个消息,没有找到从到 的ConverterNotFoundException转换器。而且看起来也不合适。任何提示表示赞赏。StringAddress@MappedCollection@Column
我试图使用最新版本的 r2dbc-postgresql (0.8.4) 中的 EnumCodec,但没有成功,我想知道你是否可以帮助我。
\n我还使用 spring-data-r2dbc 版本 1.1.1。
\n我从 GitHub 中获取了确切的示例,并在 Postgres 中创建了一个枚举类型 \xe2\x80\x9cmy_enum\xe2\x80\x9d,\n 和一个包含 \xe2\ 的表 \xe2\x80\x9csample_table\xe2\x80\x9d x80\x98name\xe2\x80\x99 (文本)和 \xe2\x80\x98value\xe2\x80\x99 (my_enum)。
\n然后我按照例子做了:
\nSQL:
\nCREATE TYPE my_enum AS ENUM ('FIRST', 'SECOND');\nRun Code Online (Sandbox Code Playgroud)\nJava模型:
\nenum MyEnumType {\n FIRST, SECOND;\n}\nRun Code Online (Sandbox Code Playgroud)\n编解码器注册:
\nPostgresqlConnectionConfiguration.builder()\n.codecRegistrar(EnumCodec.builder().withEnum("my_enum", MyEnumType.class).build());\nRun Code Online (Sandbox Code Playgroud)\n我使用 DatabaseClient 来与数据库通信。\n我尝试使用两种方法插入:
\ndatabaseClient.insert().into(SampleTable.class)\n.using(sampleTable).fetch().rowsUpdated();\nRun Code Online (Sandbox Code Playgroud)\n或者:
\ndatabaseClient.insert().into("sample_table")\n.value("name", sampleTable.getName())\n.value("value", sampleTable.getValue())\n.then();\nRun Code Online (Sandbox Code Playgroud)\n其中 SampleTable 是:
\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\n@Builder\n@Table("sample_table")\n@JsonIgnoreProperties(ignoreUnknown = true)\n@JsonInclude(JsonInclude.Include.NON_NULL)\npublic class SampleTable implements Serializable {\n private String name;\n @Column("value")\n @JsonProperty("value")\n …Run Code Online (Sandbox Code Playgroud) 我正在玩 Spring Boot 和称为 r2dbc 的反应式 jdbc 驱动程序。在我的主应用程序中,我使用 Postgres 作为数据库,现在我想使用 h2 进行测试。Flyway 迁移正在与设置一起工作,但是当 Spring 应用程序能够插入记录时。
这是我的设置和代码
@SpringBootTest
class CustomerRepositoryTest {
@Autowired
CustomerRepository repository;
@Test
void insertToDatabase() {
repository.saveAll(List.of(new Customer("Jack", "Bauer"),
new Customer("Chloe", "O'Brian"),
new Customer("Kim", "Bauer"),
new Customer("David", "Palmer"),
new Customer("Michelle", "Dessler")))
.blockLast(Duration.ofSeconds(10));
}
}
Run Code Online (Sandbox Code Playgroud)
这是我得到的错误
:: Spring Boot :: (v2.3.4.RELEASE)
2020-10-14 15:59:18.538 INFO 25279 --- [ main] i.g.i.repository.CustomerRepositoryTest : Starting CustomerRepositoryTest on imalik8088.fritz.box with PID 25279 (started by imalik in /Users/imalik/code/private/explore-java/spring-example)
2020-10-14 15:59:18.540 INFO 25279 --- [ main] …Run Code Online (Sandbox Code Playgroud) 我使用 spring-boot 2.4.2 和 webflux 连接到 postgres 数据库。@Transactional我在使用时观察到一种我不明白的行为。
为了展示该行为,我创建了一个示例应用程序,尝试将行添加到两个表中;表“a”和表“b”。对表“a”的插入预计会因重复键冲突而失败。鉴于使用了事务性,我预计不会将任何行添加到表“b”中。
但是,根据我使用的注释方法,@Transactional我会得到不同的结果。
如果我注释控制器方法,一切都会按预期工作,并且不会向表 B 添加任何行。
@PostMapping("/")
@Transactional
public Mono<Void> postEntities() {
return demoService.doSomething();
}
Run Code Online (Sandbox Code Playgroud)
演示服务如下所示:
public Mono<Void> doSomething() {
return internal();
}
public Mono<Void> internal() {
Mono<EntityA> clash = Mono.just(EntityA.builder().name("clash").build()).flatMap(repositoryA::save);
Mono<EntityB> ok = Mono.just(EntityB.builder().name("ok").build()).flatMap(repositoryB::save);
return ok.and(clash);
}
Run Code Online (Sandbox Code Playgroud)
如果我将@Transactional注释从控制器移至doSomething(),那么事务仍然按预期工作。但是,如果我将@Transactional注释移至internal(),则事务将无法按预期工作。一行被添加到表“b”中。
此示例的完整代码在这里:https ://github.com/alampada/pg-spring-r2dbc-transactional
我不明白为什么将注释移至internal()方法会导致事务处理出现问题。您能解释一下吗?
我正在尝试在我们的下一个 Spring Boot 服务中使用 Kotlincoroutines和spring-data-r2dbc( )。databaseClient我已经熟悉这两个概念,但当我们更深入地研究实现细节时,我开始问自己这个问题。
在我看到的大多数示例中,每个返回某种集合的端点在迁移到反应式方法时都会返回 Flow。
虽然没有其他(非阻塞)方法可以使用Mono/来完成此操作Flux,但由于我们想将订阅移交给 engine( Webflux),但情况与 Kotlin 非常不同Flows。Flow 的终端操作是挂起的,这使得它们本质上是非阻塞的。这意味着我可以在获取 Flow 的时间和地点以非阻塞方式终止 Flow,然后继续执行常规操作List。
当然,可能存在更复杂的场景,包括底层热门发布者、反应式传输/协议等,但就我而言,这是一个非常传统的服务。我们决定使用反应式方法的唯一原因是它受 IO 限制:对于每个 API 调用,我们需要通过 HTTP/REST 从多个其他服务获取数据,然后执行一些数据库查询,然后返回组合结果。List所以,问题是:如果我可以当场将 Flow 简化为常规层(例如在存储库中),那么将 Flow 分散到多个应用程序层(控制器、服务、存储库)中是否有意义:
suspend fun findByEvent(id: String): List<MyEntity> =
databaseClient.execute(MY_QUERY)....all().asFlow().toList()
Run Code Online (Sandbox Code Playgroud)
这样我的应用程序层的其余部分甚至不会知道任何事情Flow(无论如何,整个调用链将保持可挂起状态)?
kotlin spring-boot spring-webflux spring-data-r2dbc kotlin-coroutines
我正在使用 spring r2dbc 和 ReactiveCrudRepository,我有一个在生成更新查询时需要忽略的字段
@Data
@Table(PRODUCT_TABLE)
public class ProductEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO) // Id generated by database
private Integer id;
private Integer companyId;
@Column(insertable=false, updatable = false)
private String companyName;
@NotBlank
private String name;
private VerificationStatus verificationStatus;
}
Run Code Online (Sandbox Code Playgroud)
如何在更新查询中忽略companyName。我可以使用 @column 在插入查询中忽略它,但它不适用于更新
我一直在尝试让 Quartz 在 Spring Boot 中使用 R2DBC 时工作。到目前为止,我还没有弄清楚如何做到这一点。这是因为当创建 JDBC 数据源时,它会尝试初始化我的 R2DBC 存储库,但它无法执行此操作,因为 R2DBC 本质上是反应性的,而 JDBC 本质上是阻塞的。
我考虑过的替代方案
相关 Gradle 依赖项
dependencies {
implementation("io.projectreactor.netty:reactor-netty:0.9.7.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.springframework.boot:spring-boot-starter-quartz")
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
runtimeOnly("com.h2database:h2")
implementation("io.r2dbc:r2dbc-h2:0.8.3.RELEASE")
}
Run Code Online (Sandbox Code Playgroud)
我当前的石英配置
@Configuration
class QuartzConfig {
@Bean
@QuartzDataSource
fun dataSource(props: DataSourceProperties): DataSource {
return props.initializeDataSourceBuilder().type(HikariDataSource::class.java).build()
}
}
Run Code Online (Sandbox Code Playgroud)
相关R2DBC配置:
abstract class R2DbcConfiguration : AbstractR2dbcConfiguration() {
override fun getCustomConverters(): MutableList<Any> {
return mutableListOf(
// some custom converters
)
}
@Bean
fun connectionFactoryInitializer( …Run Code Online (Sandbox Code Playgroud) 我有一个简单的实体,由两个 UUID 组成:
\n@Table("library")\npublic class LibraryDao {\n @Id\n private UUID id;\n @NonNull\n private UUID ownerId;\n}\nRun Code Online (Sandbox Code Playgroud)\n我在 PostgreSQL 中有一个对应的表:
\nCREATE TABLE IF NOT EXISTS library (id UUID PRIMARY KEY, owner_id UUID NOT NULL);\nRun Code Online (Sandbox Code Playgroud)\n我正在使用正确的 R2DBC 驱动程序(io.r2dbc:r2dbc-postgresql和org.postgresql:postgresql)。
至此,一切正常。我的应用程序运行。但是\xe2\x80\xa6
\n因为 PostgreSQL 至少根据文档 \xe2\x80\x93 没有 UUID 自动生成功能,所以我在创建新实例时设置了 id LibraryDao。
但是,当我save在存储库中调用该方法时,出现异常:Failed to update table [library]. Row with Id [0ed4d7c0-871a-4473-8997-4c9c1ec67a00] does not exist.
似乎save被解释为,如果它不存在则update不会回退。insert
我正在探索在使用 Spring Data R2DBC 时设计一对一和一对多关系时可能的想法。
由于 Spring Data R2DBC 本身仍然不支持关系,因此仍然需要我们自己处理这些关系(与 Spring Data JDBC 不同)。
我想象的是,当涉及到一对一映射时,实现可能如下所示:
@Table("account")
public class Account {
@Id
private Long id;
@Transient // one-to-one
private Address address;
}
Run Code Online (Sandbox Code Playgroud)
@Table("address")
public class Address {
@Id
private Integer id;
}
Run Code Online (Sandbox Code Playgroud)
而数据库模式定义如下:
--address
CREATE TABLE address
(
id SERIAL PRIMARY KEY
)
--account
CREATE TABLE account
(
id SERIAL PRIMARY KEY,
address_id INTEGER REFERENCES address(id)
)
Run Code Online (Sandbox Code Playgroud)
由于该Account对象是我的聚合根,我想我应该Address按照 Jens Schaduer 的建议加载该对象:
聚合是形成一个单元的一组对象,它应该始终保持一致。此外,它应该始终被持久化(并加载)在一起。来源:Spring Data JDBC、参考资料和聚合
这让我想到,在像这样的一对一关系的情况下,我实际上应该Account …
r2dbc ×5
spring-boot ×5
java ×3
kotlin ×2
postgresql ×2
enums ×1
flyway ×1
multi-tenant ×1
quartz ×1
spring ×1
spring-data ×1
spring-jdbc ×1