[spring-projects/spring-boot]R2DBC 和 JPA/JDBC 配置之间的互操作性

2024-05-14 892 views
3

编辑上下文和 TL;DR:这个问题最初是关于 spring R2DBC 与使用 JPA/JDBC 的项目互操作/兼容的问题。发现这是多个repos模块更普遍的问题,有很多解决方案 我决定在@Enable..注释中指定各自的模块路径。仍然存在一个问题,R2DBC 将停用 JDBC/JPA autoconf,如文档所述,并且需要使用 @Import 注释导入它。

原始问题:像许多项目一样,我不想使用 Flux/Flow 为所有控制器路由着色,并且只为 R2DBC 提供一些特定的合法用例。然而,目前还不清楚(而且应该)是否可以将 R2DBC 集成到现有应用程序中,并在大多数路由中继续使用 JPA/JDBC,并且仅在适当的情况下使用 R2DBC。 (我们的合法用例是使用 postgres NOTIFY/LISTEN 的 SSE 流)如果存在互操作问题,我想澄清一下,我对 R2DBC 的使用是只读的,而所有写入都使用 JPA

回答

4

@mp911de 友好的 ping

4

我不确定你在要求什么。 JDBC 使用 JDBC 驱动程序技术,R2DBC 使用非阻塞驱动程序。 R2DBC 驱动程序不能在 JDBC 模式下使用,反之亦然,因此如果您想使用这两个堆栈,则需要包含这两种技术。

7

我知道我需要包含这两种技术!但是,如果我有一个用于 RequestModel 的阻塞/JPA 存储库,以及另一个用于 RequestModel 的反应式 R2DBC 存储库。我的问题是这是否受支持,或者两个连接之间是否会存在副作用/同步差异。由于我现在使用的 R2DBC 是只读的,我认为不会存在同步问题,但如果我进行了写入,那么写入可能不遵守代码的顺序?更重要的是,在我的 JPA 项目中引入 R2DBC 会产生一个例外(如链接的 stackoverflow 帖子中所示)

Parameter 0 of constructor in com.brainflow.brainflowserver.services.UserService required a bean of type 'com.brainflow.brainflowserver.repositories.UserRepository' that could not be found.

我的阻塞存储库不再被 Spring 识别,我目前正在寻找差异,因为显然这个存储库组合了两个堆栈,没有这个问题 https://github.com/hantsy/spring-puzzles/tree/master/jpa-r2dbc

5

简单地包含 R2DBC 依赖项(spring、postgres 驱动程序)会引发异常

Parameter 0 of constructor in com.brainflow.brainflowserver.services.UserService required a bean of type 'com.brainflow.brainflowserver.repositories.UserRepository' that could not be found.

即使我的应用程序和 application.properties 中的 R2DBC 代码为零,也对其进行了评论

1

感谢您提供背景知识,但从最初的描述来看并不能立即清楚。在单个项目中使用多个 Spring Data 模块可以启用严格的存储库检测模式,这意味着实体或存储库必须明确指示它们属于哪个模块。这可以通过使用@Entity/注释实体@Table或使用特定于模块的存储库接口(JpaRepository, R2dbcRepository)作为存储库超类来实现。

您应该看到一些日志输出,指示 Spring Data 为特定模块找到了多少个存储库(Could not safely identify store assignment for repository candidate …Multiple Spring Data modules found, entering strict repository configuration mode!Finished Spring Data repository scanning …)。

查看有关此主题的文档:https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.multiple-modules

9

谢谢,我忘记了这个多模块问题!

但是在指定(非重叠)模块之后:

@EnableJpaRepositories("com.brainflow.brainflowserver.repositories")
@EnableR2dbcRepositories("com.brainflow.brainflowserver.reactiveRepos")
class BrainflowServerApplication {

(注意:com.brainflow.brainflowserver.reactiveRepos 目前是一个空文件夹)

它仍然会触发错误

Parameter 0 of constructor in com.brainflow.brainflowserver.services.UserService required a bean named 'entityManagerFactory' that could not be found.

The injection point has the following annotations:
    - @org.springframework.beans.factory.annotation.Autowired(required=true)

Action:

Consider defining a bean named 'entityManagerFactory' in your configuration.

所以我认为 @EnableJpaRepositories("com.brainflow.brainflowserver.repositories") 禁用了 spring boot JPA autoconf,破坏了实体管理器,但是当我指定 jpa 模块并删除 r2dbc 依赖项时没有问题。

编辑:你对日志的看法是正确的

Multiple Spring Data modules found, entering strict repository configuration mode!
Bootstrapping Spring Data JPA repositories in DEFAULT mode.
Finished Spring Data repository scanning in 46 ms. Found 9 JPA repository interfaces.
Multiple Spring Data modules found, entering strict repository configuration mode!
Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
Finished Spring Data repository scanning in 0 ms. Found 0 R2DBC repository interfaces.
Multiple Spring Data modules found, entering strict repository configuration mode!
Bootstrapping Spring Data R2DBC repositories in DEFAULT mode.
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Spring Data R2DBC - Could not safely identify store assignment for repository candidat
Finished Spring Data repository scanning in 16 ms. Found 0 R2DBC repository interfaces
Multiple Spring Data modules found, entering strict repository configuration mode!
Bootstrapping Spring Data Redis repositories in DEFAULT mode.
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Spring Data Redis - Could not safely identify store assignment for repository candidat
Finished Spring Data repository scanning in 7 ms. Found 0 Redis repository interfaces.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.BrainflowRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.LinkRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.NodeRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.ProjectMemberRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcReposit
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.ProjectRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.RequestRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.TagRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.UserNodeRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.RepositoryConfigurationExtensionSupport : Spring Data R2DBC - Could not safely identify store assignment for repository candidate interface com.brainflow.brainflowserver.repositories.UserRepository. If you want this repository to be a R2DBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table (preferred), or consider extending one of the following types with your repository: org.springframework.data.r2dbc.repository.R2dbcRepository.
.s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 16 ms. Found 0 R2DBC repository interfaces.

我不明白为什么 R2DBC spring 查看存储库模块,我明确告诉 spring 仅查看反应模块 @EnableR2dbcRepositories("com.brainflow.brainflowserver.reactiveRepos")

编辑添加 @EnableRedisRepositories("com.brainflow.brainflowserver.repositories") 不能解决问题

8

我认为问题在于,当位于类路径上并且提供数据源时,DataSourceAutoConfiguration请退出:ConnectionFactory

   DataSourceAutoConfiguration:
      Did not match:
         - @ConditionalOnMissingBean (types: io.r2dbc.spi.ConnectionFactory; SearchStrategy: all) found beans of type 'io.r2dbc.spi.ConnectionFactory' connectionFactory (OnBeanCondition)

由于这已成为 Spring Boot 问题,我建议将此票移至启动项目中。

您可以通过提供自己的DataSourcebean 来解决此问题。

2

根据 @mp911de 的请求转移到 Spring Boot。

7

从 Spring Boot 的角度来看,这是按照设计和文档工作的:

ConnectionFactorybean 可用时,常规 JDBCDataSource自动配置就会停止。如果您想保留 JDBCDataSource自动配置,并且愿意接受在反应式应用程序中使用阻塞 JDBC API 的风险,请在应用程序中添加@Import(DataSourceAutoConfiguration.class)一个@Configuration类以重新启用它。

6

谢谢,这确实是我面临的问题(我应该阅读文档><'),并且一如既往地感谢您出色的专业精神!

3

这些是我为实现这一目标所做的改变。 这是我的 R2dbc 配置类 ->

@Configuration
public class R2dbcConfig {

    @Value("${spring.r2dbc.url}")
    private String url;

    @Value("${spring.r2dbc.name}")
    private String name;

    @Value("${spring.r2dbc.username}")
    private String username;

    @Value("${spring.r2dbc.password}")
    private String password;
    @Bean
    public ConnectionFactory connectionFactory() {
        return new PostgresqlConnectionFactory(
                PostgresqlConnectionConfiguration.builder()
                        .host(url)
                        .database(name)
                        .username(username)
                        .password(password)
                        .build()
        );
    }

    @Bean
    DatabaseClient databaseClient(ConnectionFactory connectionFactory) {
        return DatabaseClient.builder()
                .connectionFactory(connectionFactory)
                .namedParameters(true)
                .build();
    }
}

然后我还定义了我的 jpa 配置文件

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.artemis.repositories")
@EntityScan("com.artemis.entities")
@Slf4j
public class JpaConfig implements EnvironmentAware {

    private static final String ENV_HIBERNATE_DIALECT = "hibernate.dialect";
    private static final String ENV_HIBERNATE_HBM2DDL_AUTO = "hibernate.hbm2ddl.auto";
    private static final String ENV_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
    private static final String ENV_HIBERNATE_FORMAT_SQL = "hibernate.format_sql";
    private Environment env;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(
                env.getProperty("datasource.url"),
                env.getProperty("datasource.username"),
                env.getProperty("datasource.password")
        );
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
        emf.setDataSource(dataSource);
        emf.setPackagesToScan(ArtemisApplication.class.getPackage().getName());
        emf.setPersistenceProvider(new HibernatePersistenceProvider());
        emf.setJpaProperties(jpaProperties());
        return emf;
    }

    private Properties jpaProperties() {
        Properties extraProperties = new Properties();
        extraProperties.put(ENV_HIBERNATE_FORMAT_SQL, env.getProperty(ENV_HIBERNATE_FORMAT_SQL));
        extraProperties.put(ENV_HIBERNATE_SHOW_SQL, env.getProperty(ENV_HIBERNATE_SHOW_SQL));
        extraProperties.put(ENV_HIBERNATE_HBM2DDL_AUTO, env.getProperty(ENV_HIBERNATE_HBM2DDL_AUTO));
        if (log.isDebugEnabled()) {
            log.debug(" hibernate.dialect @" + env.getProperty(ENV_HIBERNATE_DIALECT));
        }
        if (env.getProperty(ENV_HIBERNATE_DIALECT) != null) {
            extraProperties.put(ENV_HIBERNATE_DIALECT, env.getProperty(ENV_HIBERNATE_DIALECT));
        }
        return extraProperties;
    }

    @Bean
    public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory.getObject());
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
    }
}

这是我的服务类,带有用于监听的 postgress 触发器

class myService{

    final PostgresqlConnection connection;

    public myService(ConnectionFactory connectionFactory ) {
        this.connection =  Mono.from(connectionFactory.create())
                .cast(PostgresqlConnection.class).block();
    }

    @PostConstruct
    private void postConstruct() {
        connection.createStatement("LISTEN my_channel").execute()
                .flatMap(PostgresqlResult::getRowsUpdated).subscribe();
        connection.getNotifications().subscribe(myService::catchTrigger);
    }

    private static void catchTrigger(Notification notification) {
        System.out.println(notification.getName());
        System.out.println(notification.getParameter());
    }
}