Spring Webflux + JPA: JPA不支持响应式存储库

35

我启动我的应用程序时遇到错误 JPA:JPA不支持响应式存储库。 我的Pom有以下依赖项,我正在使用Spring Boot 2.0.5

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

这是我的仓库接口。

public interface CustomerRepository extends ReactiveCrudRepository {
}

当我启动我的应用程序时,它会抛出错误:

org.springframework.dao.InvalidDataAccessApiUsageException: Reactive Repositories are not supported by JPA. Offending repository is com.example.demo.CustomerRepository!
    at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.useRepositoryConfiguration(RepositoryConfigurationExtensionSupport.java:310) ~[spring-data-commons-2.0.10.RELEASE.jar:2.0.10.RELEASE]
    at org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport.getRepositoryConfigurations(RepositoryConfigurationExtensionSupport.java:103) ~[spring-data-commons-2.0.10.RELEASE.jar:2.0.10.RELEASE]
    at org.springframework.data.repository.config.RepositoryConfigurationDelegate.registerRepositoriesIn(RepositoryConfigurationDelegate.java:126) ~[spring-data-commons-2.0.10.RELEASE.jar:2.0.10.RELEASE]
    at org.springframework.boot.autoconfigure.data.AbstractRepositoryConfigurationSourceSupport.registerBeanDefinitions(AbstractRepositoryConfigurationSourceSupport.java:60) ~[spring-boot-autoconfigure-2.0.5.RELEASE.jar:2.0.5.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.lambda$loadBeanDefinitionsFromRegistrars$1(ConfigurationClassBeanDefinitionReader.java:358) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at java.util.LinkedHashMap.forEach(LinkedHashMap.java:684) ~[na:1.8.0_144]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsFromRegistrars(ConfigurationClassBeanDefinitionReader.java:357) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:145) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:328) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:233) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:271) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:91) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:694) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:532) ~[spring-context-5.0.9.RELEASE.jar:5.0.9.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:61) ~[spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:780) [spring-boot-2.0.5.RELEASE.jar:2.0.5.RELEASE]

请问如果不支持JPA,应该使用什么,任何帮助将不胜感激。

8个回答

35
如果您想要利用响应式、异步/非阻塞的全部好处,您需要使整个堆栈都是异步/非阻塞的。JDBC在本质上是一个阻塞的API,因此如果需要通过JDBC访问数据库,则无法构建完全响应式/非阻塞的应用程序。
但是如果您仍然需要关系数据库,则建议使用rxjava2-jdbc。下面是使用RxJava和RxJava jdbc的完整示例:spring-webflux-async-jdbc-sample 目前Spring webflux支持Mongodb、Redis等NoSQL响应式,所以建议使用spring-boot-starter-data-mongodb-reactive而不是JPA。

3
不错的回答。然而,如果您所指的是 https://github.com/davidmoten/rxjava2-jdbc,那个例子并没有使用它。它只是将 CrudRepositoryFluxMono 包装起来。 - heez
Spring Data Redis不支持响应式存储库:https://jira.spring.io/browse/DATAREDIS-796 - Alexander Berezovsky
@kj007 你能解释一下什么是“阻塞API”吗?JDBC查询返回一个ResultSet,据我所知,该调用可以很快返回--即使许多数据库的默认行为是实际获取整个结果集。我相信MySQL和PostgreSQL都有以每次几条记录流式传输结果集的方法,而不是一次性全部获取。 - Frans
4
@Frans,尽管快速,但仍会在等待响应时阻塞线程。Reactive基于消息传递/回调,因此您可以发出数据库请求,关闭线程,而回调将在另一个线程中处理。 - cody mikol
我同意@heez的观点,即spring-webflux-async-jdbc-sample并没有产生正确的效果。如果查询在将所有结果集检索完毕之前等待(关系型数据库的正常行为),然后再将行提供给Flux,则您将无法像从反应式NoSQL DB中获得的那样获得相同的好处。以分块(分页)方式获取数据可能会使RDB查询更具响应性。 - TastyWheat

8
我不知道之前的支持情况,但截至2019年6月9日,您绝对可以使用JPA Repositories与WebFlux! 您的堆栈并不一定完全是响应式的。我喜欢WebFlux,但需要一个关系型数据库。
我有:
spring-boot-starter-data-redis-reactive spring-boot-starter-webflux
spring-boot-starter-data-jpa
编辑:(FYI)代码是用Kotlin编写的,但仍然可以在Java中运行。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = ["com.example.core.repositories"])
@EnableJpaAuditing
class PersistenceConfig

src/core/models/User

@Entity
@Table(name = "user")
class User(
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "user_id")
    var id: Long,

    @Column(name = "username")
    var username: String,

    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)
    val createdDate: Date,

    @Column(name = "modified_date")
    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)
    val modifiedDate: Date
) : Serializable {

    /**
     * This constructor is not to be used. This is for hibernate,
     * which requires an empty constructor.
     */
    constructor() : this(1L, "", "", Date(), Date())

    companion object {
        private const val serialVersionUID = 2398467923L
    }

}

当我仍然从Spring Data查询中返回Mono<User>对象时,我遇到了相同的错误:JPA:不支持JPA的响应式存储库。 然而,如果你移除Mono包装器,它应该正常工作。

src/core/repositories/UserRepository

@Repository
interface UserRepository: CrudRepository<User, Long> {

    fun findUserByUsername(username: String): User?

}

8
使用这种方法,您将失去事务支持以及更多功能。只有在调用findUserByUsername时,您的会话才处于活动状态,而不再存在其他时间。因此,不能支持关系的延迟加载等功能。 - Boni2k
如果返回一个User而不是Mono<User>,那么作为Java用户,我看不出这怎么可能是非阻塞的。实现需要等待从数据库加载一个User才能创建一个,对吧? - julaine
@Boni2k:为什么会失去事务支持?只要@Transactional中的整个代码没有调用任何反应式接口,代码就应该在同一个线程中运行。当然,它将会是阻塞的,但这正如答案作者所说的那样:您可以混合使用反应式和非反应式。 - chris
@chris 你正在回答一个来自2019年的评论 - 到现在为止,Kotlin + 协程的支持确实有很大进展。 - Boni2k

5

8
不支持ManyToOne、OneToMany等关系。 - Selast Lambou
1
它也不支持L2缓存。 - Archimedes Trajano

4
即使您选择的数据库(H2)不支持非阻塞反应式查询,您仍然可以以阻塞的方式获取数据,然后尽快将其转换为反应式类型,以便上游组件受益。
您无法改变在JPA存储库上调用方法的阻塞性质。 但是,您可以在收到非反应式类型时尽快将其转换为反应式类型(Flux / Mono),以便从那时起以反应方式处理结果。
或者,您可以使用支持反应模型的另一个数据库,例如Cassandra,MongoDB,Couchbase或Redis。

2
我这样解决的:

我这样解决的

import org.springframework.data.repository.reactive.ReactiveCrudRepository;


@Repository
public interface ProductRepository extends ReactiveCrudRepository<Product, Long> {

    @Query("SELECT * FROM product WHERE id = :id")
    Flux<Product> findById(@Param("id") Long id);

}

2

如果您计划使用阻塞API,则可以简单地使用SpringMVC,因为它最适合处理这些技术,如此文档所述。

从文档中可以看到:

如果您有阻塞持久性API(JPA、JDBC)或网络API要使用,则Spring MVC是至少常见架构的最佳选择。使用Reactor和RxJava在单独的线程上执行阻塞调用在技术上是可行的,但您将无法充分利用非阻塞Web堆栈。

否则,您可以使用rxjava2甚至Reactor进行轻微修改以处理阻塞API。例如此git hub项目


附言:我知道我来晚了,但这是为了其他有兴趣的人


1

比没有好,希望有一些标准的东西来实现JPA响应式...我认为还需要等待几年...需要等待Eclipse。 - robert trudel
看起来是一个非常有前途的实现。 - PeMa
1
似乎Hibernate团队正在开发一些东西 https://github.com/hibernate/hibernate-rx - Javier Sainz

0
请尝试一下。
@Configuration
@EnableR2dbcAuditing
@EnableR2dbcRepositories(basePackages = {"com.abc.test.repositories"})
class R2dbcConfig extends AbstractR2dbcConfiguration {
    @Override
    @Bean
    public ConnectionFactory connectionFactory() {
       //add your DB connection config
    }
}

希望能够帮到你!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接