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.
Add Spring Data R2dbc Dependency
Beside adding the R2dbc drivers, when using Spring Data R2dbc, add the following dependencies into your project.
<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
Spring Data R2dbc provides a templated AbstractR2dbcConfiguration
class to simplify the configuration, override the connectionFactory()
to provide a ConnectionFactory
bean. You can also register the custom converters used to convert between data type and Java type.
@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
The R2dbcEntityTemplate
is a lightweight wrapper of DatabaseClient
, but it provides type safe operations and fluent query APIs instead of literal SQL queries.
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
To enable Repository
support, add a @EnableR2dbcRepositories
annotation on the configuration class.
@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.