能否在单个Spring Boot应用程序中同时使用Spring Data R2DBC和Spring Data JPA?

18

我有一个使用Spring Data JPA和Hibernate Envers用于数据库审计的应用程序。 由于R2DBC尚不支持审计,是否可以在单个应用程序中同时使用两者的组合?

如果是,计划是使用Spring Data JPA进行插入、更新和删除操作,以便所有的数据库审计都由Hibernate Envers处理。并使用R2DBC来读取数据的响应式非阻塞API。

如果不行,是否有任何建议来实现响应式API和审计?


2
是的,这应该可以工作。 - Jens Schauder
你可以潜在地使用R2DBC进行所有操作,并使用库(如果可用)连接到数据库日志(例如mysql-binlog-connector-java)的单独进程,该进程将捕获数据库更改,然后将其持久化到审核表中。 - NikolaB
1
尝试使用 Hibernate Reactive(Hibernate 下的一个新项目,目前有很多限制),不确定它现在是否支持审计功能。 - Hantsy
3个回答

9
  1. Spring提供了简单的审计功能,通过 @EnableR2dbcAuditing 实现,在我的示例中查看

  2. 在响应式应用程序中混合使用JPA也是可能的,我有一个示例,演示了如何在响应式应用程序中运行JPA,但其中没有添加r2dbc。

  3. 对于您的计划,更好的解决方案是在数据库拓扑结构上应用cqrs模式,为您的应用程序使用数据库集群。

    • JPA用于应用更改,使用主/主数据库接受修改,并将更改同步到次要/从属数据库。
    • r2dbc用于查询,使用次要/从属数据库进行查询。
    • 在前端使用网关来提供查询和命令服务。

更新: 我创建了一个示例,演示了如何在单个webflux应用程序中同时使用JPA和R2dbc,可以在这里查看。但我不建议在实际应用中使用它,请考虑上述第3种解决方案。


你能更详细地解释一下为什么最好分别使用JPA和R2DBC来处理Command和Query,而不是混用吗?我只是很好奇如果没有JPA的帮助,如何在R2DBC中轻松地建立多个表之间的关联和连接。 - Wood
针对复杂的查询,使用R2dbc DatabaseClient执行原始SQL并处理结果,或使用JOOQ(自3.15版本起支持R2dbc)编写类型安全的查询 - Hantsy
那么,在命令和查询中都使用DatabaseClient怎么样?如果你只用它来写入数据到数据库,我认为你无法获得ORM的许多优势。那么,使用QueryDSL代替JOOQ呢?我认为它更受欢迎,并且(据我所知)支持r2dbc作为扩展。 - Wood
据我所知,QueryDSL并不像JOOQ那样活跃,并且它不支持R2dbc,请参见:https://github.com/querydsl/querydsl/issues/2468 - Hantsy

1

0
**Here are the changes I did :
I am trying to catch a trigger from postgres on updation of the table content.** 

**R2dbc Config**

@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 Config:**

@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;
    }
}

And my service class:

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());
    }
}

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