Update: Accessing RDBMS with Spring Data R2dbc

I have published an article on Medium several monthes ago to introduce Spring Data R2dbc , but it involved frequently, the R2dbc DatabaseClient is move to Spring core framework as an alternative of Jdbc.

This post is an update to the latest Spring 5.3 and Spring Data R2dbc 1.2, also as the 3rd post of the R2dbc series:

Add Spring Data R2dbc Dependency

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-r2dbc</artifactId>
</dependency>

For Spring Boot applications, add the Spring Boot starter for Spring Data R2dbc.

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

Configuring Spring Data R2dbc

@Configuration
@EnableTransactionManagement
public class DatabaseConfig extends AbstractR2dbcConfiguration {
@Override
@Bean
public ConnectionFactory connectionFactory() {
// postgres
return new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost")
.database("test")
.username("user")
.password("password")
.codecRegistrar(EnumCodec.builder().withEnum("post_status", Post.Status.class).build())
.build()
);
}
@Override
protected List<Object> getCustomConverters() {
return List.of(new PostReadingConverter(), new PostStatusWritingConverter());
}
@Bean
ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
return new R2dbcTransactionManager(connectionFactory);
}
@Bean
public ConnectionFactoryInitializer initializer(ConnectionFactory connectionFactory) {
ConnectionFactoryInitializer initializer = new ConnectionFactoryInitializer();
initializer.setConnectionFactory(connectionFactory);
CompositeDatabasePopulator populator = new CompositeDatabasePopulator();
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("schema.sql")));
populator.addPopulators(new ResourceDatabasePopulator(new ClassPathResource("data.sql")));
initializer.setDatabasePopulator(populator);
return initializer;
}
}

In above codes, the @EnableTransactionManagement annotation on the configuration class is used to activate the reactive transaction management support. To use @Transactional annotation, you have to declare a ReactiveTransactionManager bean, Spring provides an implementation for R2dbc - R2dbcTransactionManager. As described in the former post, a ConnectionFactoryInitializer bean is declared here to initialize the table schema and sample data.

In Spring Boot applications, simply configure the spring.r2dbc.url, spring.r2dbc.username, spring.r2dbc.password properties, Spring boot will autoconfigure these for you. Of course you can customize your own configuration by subclassing AbstractR2dbcConfiguration, check my example config fragment.

Next, we can use Spring Data R2dbc’s specific EntityTemplate or R2dbcRepository to perform CRUD operations on databases.

R2dbcEntityTemplate

A R2dbcEntityTemplate bean is declared in the AbstractR2dbcConfiguration class. So you can inject it directly.

@Component
@RequiredArgsConstructor
@Slf4j
public class PostRepository {
private final R2dbcEntityTemplate template; public Flux<Post> findByTitleContains(String name) {
return this.template.select(Post.class)
.matching(Query.query(where("title").like("%" + name + "%")).limit(10).offset(0))
.all();
}
public Flux<Post> findAll() {
return this.template.select(Post.class).all();
}
public Mono<Post> findById(UUID id) {
return this.template.selectOne(Query.query(where("id").is(id)), Post.class);
}
public Mono<UUID> save(Post p) {
return this.template.insert(Post.class)
.using(p)
.map(post -> post.getId());
}
public Mono<Integer> update(Post p) {
/*
return this.template.update(Post.class)
.matching(Query.query(where("id").is(p.getId())))
.apply(Update.update("title", p.getTitle())
.set("content", p.getContent())
.set("status", p.getStatus())
.set("metadata", p.getMetadata()));
*/
return this.template.update(
Query.query(where("id").is(p.getId())),
Update.update("title", p.getTitle())
.set("content", p.getContent())
Post.class
);
}
public Mono<Integer> deleteById(UUID id) {
return this.template.delete(Query.query(where("id").is(id)), Post.class);
}
}

Compare to the former codes using DatabaseClient, it is more concisely, it utilizes the entity class Post to simplify the binding work and conversions, and also include fluent Query APIs to escape from the raw SQL query strings.

Let’s have a look at the Post class.

@Data
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(value = "posts")
public class Post {
@Id
@Column("id")
private UUID id;
@Column("title")
private String title;
@Column("content")
private String content;
}

The Post class is annotated with a @Table annotation which accepts the mapped table name. @Id specifies it's the identifier of this entity, @Column defines the column name in the table.

Please note, @Table and @Column is from the spring-data-relational project, which is a common library for Spring Data Jdbc and Spring Data R2dbc, and @Id is from Spring Data Commons.

R2dbcRepository

@Configuration
@EnableR2dbcRepositories
class DatabaseConfig{}

If the entity classes are not in the same package or subpackages of the config class, you have to set the basePackages attribute to locate the entities.

In the Spring Boot applications, @EnableR2dbcRepositories is not a must.

A simple Repository looks like the following.

public interface PostRepository extends R2dbcRepository<Post, UUID> {
public Flux<Post> findByTitleContains(String name);
}

It also supports some common Spring data features, such as @Query annotations on methods and named or index-based parameters.

Get the code samples from my github.

Written by

Self-employed technical consultant, solution architect and full-stack developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store