DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • How to Store Text in PostgreSQL: Tips, Tricks, and Traps
  • Introduction to Couchbase for Oracle Developers and Experts: Part 2 - Database Objects
  • FHIR Data Model With Couchbase N1QL

Trending

  • Securing the Future: Best Practices for Privacy and Data Governance in LLMOps
  • Is Big Data Dying?
  • Using Python Libraries in Java
  • Bridging UI, DevOps, and AI: A Full-Stack Engineer’s Approach to Resilient Systems
  1. DZone
  2. Data Engineering
  3. Databases
  4. You Don’t Need Hibernate With Spring WebFlux and R2DBC

You Don’t Need Hibernate With Spring WebFlux and R2DBC

By 
Yuri Mednikov user avatar
Yuri Mednikov
·
Jun. 11, 20 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
40.4K Views

Join the DZone community and get the full member experience.

Join For Free

One of issues when you work with relational databases in Java is that they are unable to translate object relationships (such as composition), due to the tabular nature of data sources. That means that as developers, we usually tend to have an intermediate layer, which is responsible to abstract the data source’s data organization. This is called ORM or object-relational mapping. In Spring's ecosystem the de facto standard is Hibernate, however it is not [yet] available for new non-blocking the Spring R2DBC API.

Frankly speaking, you don’t need Hibernate to do object mapping, as R2DBC is easy to use and you can do it yourself. And moreover it is a great pleasure, especially because we – Java devs – tend to rely on tools that are not state of the art of software design. 

This post focuses on a problem of fetching composed entities from Postgresql (using INNER JOIN) and mapping it with Data Mapper pattern (as defined in P of EAA) with custom Spring ReactiveCrudRepository extension.

Problem

In the object-oriented programming, we work with objects that possess rich relationships between each other, based on aggregation, association, or composition. Imagine that you work on a task management app, where each task entity holds a reference to project entity. Or, another case – job app, where job entity points the corresponding employer object.

However, in relational databases, despite their name, data is organized in a tabular way. That means we need to have an intermediate layer between database and business logic. Such a mechanism is called object-relational mapping (or ORM), and it allows to access data entities in a way independent from how they are stored in data sources. In Java de facto choice is Hibernate, and as Spring developers we use it a lot with relational DBs, such as MySQL, Postgre, etc.

But, when you use non-blocking Webflux APIs and R2DBC to work with relational databases, you don’t have an ORM because Hibernate is not supported by R2DBC. Yet, maybe. And I think it is good – as Java developers we love to talk about software design, but we depend on tools, that are not best examples of software architectural principles. This post is about dealing with composition in entities without Hibernate in Spring R2DBC and PostgreSQL.

Solution

Consider the following example: we build a task management application, and we have Task and Project entities. Both are stored in separate tables, but are referenced using project_id key. When we want to retrieve a list of tasks, we want to have information about corresponding projects, so we could show it in client apps. Yet, we can have two separate repos and then combine data it is highly inefficient idea. The better approach is to write a custom TaskRepository that returns composed objects.

Step 1. Define a Custom Repository

By default, ReactiveCrudRepository is an entry point, that offers basic CRUD functionality, but it is limited to that. In order to provide custom queries, we need to extend it with a custom repository. For that, we first create a new interface that defines a contract for custom repository, and then extend the entry repository with it:

Java
xxxxxxxxxx
1
 
1
public interface CustomTaskRepository {    
2
  Flux<Task> findAllTasks (UUID userId);    
3
  Flux<Task> findTasksForDay (UUID userId, LocalDate day); }


The next step is to extend core TaskRepository with CustomTaskRepository, as shown below:

Java
xxxxxxxxxx
1
 
1
public interface TaskRepository extends ReactiveCrudRepository<Task, UUID>,    CustomTaskRepository {}


Step 2. Implement the Contract

Now, we can implement the aforementioned interface. Note, that implementations should have Impl endings due to Spring DI rules. In that component, we need to inject a DatabaseClient that is non-blocking client to handle database operations. In Spring Boot, things are pre-configured, so you just need to define a dependency and use constructor-based DI to make Spring inject it:

Java
xxxxxxxxxx
1
16
 
1
public class CustomTaskRepositoryImpl implements CustomTaskRepository {
2
3
    private DatabaseClient client;
4
5
    public CustomTaskRepositoryImpl(DatabaseClient client) {
6
        this.client = client;
7
    }
8
9
    public Flux<Task> findAllTasks (UUID userId) {
10
        return null;
11
    }
12
13
    public Flux<Task> findTasksForDay (UUID userId, LocalDate day) {
14
        return null;
15
    }
16
}


Step 3. Prepare SQL Queries

Fetching composed entities means that we need to use JOIN operations. PostgreSQL has 6 types of join operations, but this is out of scope of this post. Maybe in the future, I will add PostgreSQL as a topic of my blog, but not now. Here, we use the INNER JOIN operation that returns rows that match the given condition in both tables.

In our example, we have Task and Project entities connected with project_id. Take a look at the code snippet below:

Java
xxxxxxxxxx
1
 
1
public Flux<Task> findAllTasks (UUID userId) {
2
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
3
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE user_id = :userId";
4
    return null;
5
}


Step 4. Bind Params and Execute Query

Likewise, to do it in plain JDBC, we first prepare a query and then execute it. In R2DBC, we use DatabaseClient.execute() method for this. We also may bind some variables, like userId. This is done using the bind() method, which accepts two arguments: key (a name of variable in query) and a value.

Java
xxxxxxxxxx
1
 
1
public Flux<Task> findAllTasks (UUID userId) {
2
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
3
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE user_id = :userId";
4
    Flux<Task> result = client.execute(query)
5
                        .bind("userId", userId)
6
                        ///...
7
    return null;
8
}


Step 5. Use Mapper to Work With Results

This is cumberstone, as that is a reason why we use ORM frameworks. We need to map raw results to Java objects. For that (as well to promote reusability), we create a mapper. This design pattern, defined by Martin Fowler is used to move data between objects and a database while keeping them independent of each other and the mapper itself. The idea is displayed below:

Mapper implementation

In R2DC, reactive client mapping operation is performed by the map() method. It accepts a normal BiFunction that maps raw row results to corresponding Java model. We can implement it using a functional interface like this:

Java
xxxxxxxxxx
1
17
 
1
public class TaskMapper implements BiFunction<Row, Object, Job> {
2
3
    @Override
4
    public Task apply(Row row, Object o) {
5
        UUID taskId = row.get("task_id", UUID.class);
6
        String content = row.get("task_content", String.class);
7
        Boolean completed = row.get("is_completed", Boolean.class);
8
        LocalDate createdAt = row.get("task_date", LocalDate.class);
9
10
        UUID projectId = row.get("project_id", UUID.class);
11
        String projectName = row.get("project_name", String.class);
12
13
        Project project = new Project(projectId, projectName);
14
        Task task = new Task(taskId, content, complted, createdAt, project);
15
        return task;
16
    }
17
}


Next, we can add this component to our custom repository:

Java
xxxxxxxxxx
1
12
 
1
public Flux<Task> findAllTasks (UUID userId) {
2
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
3
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE tasks.user_id = :userId";
4
5
    TaskMapper mapper = new TaskMapper();
6
7
    Flux<Task> result = client.execute(query)
8
                        .bind("userId", userId)
9
                        .map(mapper:apply)
10
                        //...
11
    return null;
12
}


Step 6. Consume data

The final step is to call a terminal operation to consume the data pipeline. DatabaseClient has three operations to work with queries:

  • all() = returns all rows of the result.
  • first() = returns the first row of the entire result.
  • one() = returns exactly one result and fails if the result contains more rows.

In our example, we need all entities that satisfy the query, so we use the all() method, as shown below:

Java
xxxxxxxxxx
1
12
 
1
public Flux<Task> findAllTasks (UUID userId) {
2
    String query = "SELECT task_id, task_content, is_completed, user_id, task_date, task_project, project_id, project_name "
3
                    + " FROM tasks INNER JOIN projects ON task_project = project_id WHERE tasks.user_id = :userId";
4
5
    TaskMapper mapper = new TaskMapper();
6
7
    Flux<Task> result = client.execute(query)
8
                        .bind("userId", userId)
9
                        .map(mapper:apply)
10
                        .all();
11
    return result;
12
}


As you can see, this is not a rocket science to use R2DBC with complex composed objects without a need of ORM framework, like Hibernate. R2DBC provides a fluent API and is easy to use and to abstract database operations, so you can implement required persistence-layer logic yourself.

If you have questions regarding this post, don’t hesitate to drop a comment below or contact me. Have a nice day!

References

  • Mark Paluch Reactive programming with SQL databases (2019) Jaxenter access here.
  • Martin Fowler P of EAA: Data Mapper access here.
  • Piotr Mińkowski Introduction to reactive APIS with Postgres, R2DBC, Spring Data JDBC and Spring Webflux (2018) acces here.
Database Relational database Spring Framework Hibernate Java (programming language) Data (computing) sql Object (computer science)

Published at DZone with permission of Yuri Mednikov. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Architecture and Code Design, Pt. 1: Relational Persistence Insights to Use Today and On the Upcoming Years
  • How to Store Text in PostgreSQL: Tips, Tricks, and Traps
  • Introduction to Couchbase for Oracle Developers and Experts: Part 2 - Database Objects
  • FHIR Data Model With Couchbase N1QL

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!