Querying SQL Databases Declaratively [Video]

Alejandro Duarte - Aug 27 '21 - - Dev Community

Apache Delta Spike is a set of CDI extensions that includes a module to ease JPA usage. This includes the possibility to define repositories and queries declaratively. In this article, I'll explain how to add a query method to a repository interface without having to implement the query itself.

If you prefer, you can watch the video version:

Let's suppose you are working on a Jakarta EE application that has the Data module of Apache DeltaSpike in the classpath (check this article to learn how to configure a project). You probably have an Entity class. For example:

package com.example.app;

import javax.persistence.*;
import java.time.LocalDate;
import java.util.Objects;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "email")
    private String email;

    @Column(name = "birth_date")
    private LocalDate birthDate;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public LocalDate getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(LocalDate birthDate) {
        this.birthDate = birthDate;
    }
}
Enter fullscreen mode Exit fullscreen mode

This Entity class is mapped to a SQL table in MySQL, Oracle, PostgreSQL, or any other database that supports JDBC. You can use JPA to query the database. This would require you to implement queries with, for example, JPQL, and use the API to run the queries and get the results in the form of User objects. The Data module of Apache DeltaSpike allows you to write a Java interface that Apache DeltaSpike implements at runtime. Here's a first iteration of the new UserRepository interface that we need:

package com.example.app;

import org.apache.deltaspike.data.api.EntityRepository;
import org.apache.deltaspike.data.api.Repository;

@Repository
public interface UserRepository extends EntityRepository<User, Integer> {

}
Enter fullscreen mode Exit fullscreen mode

This interface extends EntityRepository which in turn includes several useful and frequently used methods for data manipulation:

Available methods

But the benefits of using this kind of repository interface don't end there. You can add custom queries to the interface without having to implement them. This works using a name convention. For example, let's add a new method to the UserRepository interface:

package com.example.app;

import org.apache.deltaspike.data.api.EntityRepository;
import org.apache.deltaspike.data.api.Repository;

import java.util.List;

@Repository
public interface UserRepository extends EntityRepository<User, Integer> {

    List<User> findByEmail(String email);

}
Enter fullscreen mode Exit fullscreen mode

Apache DeltaSpike inspects this interface and finds the method. The findBy part in the method's name is a convention, and it means, well, what it says! Find all the users that have the email passed as a parameter. Of course, in the case of an email, there's probably one, but maybe we need a method to find all the users whose email ends in @test.com. We can change the name of the method to achieve this:

package com.example.app;

import org.apache.deltaspike.data.api.EntityRepository;
import org.apache.deltaspike.data.api.Repository;

import java.util.List;

@Repository
public interface UserRepository extends EntityRepository<User, Integer> {

    List<User> findByEmailLike(String email);

}
Enter fullscreen mode Exit fullscreen mode

Now, when you read the name of the method, it should be clear that we want to find the users that have an email like the expression passed as a parameter. The word "like" refers to the SQL operator with the same name. We can use the repository as follows:

List<User> users = userRepository.findByEmailLike("%@test.com");
Enter fullscreen mode Exit fullscreen mode

The % character represents zero, one, or multiple characters. We can inject a reference of type UserRepository to, say, a backend service class or even to a UI class (you should probably add a service class in the middle, but I'll leave that as an exercise). For example, you can create a web graphical user interface in Java using Vaadin, inject the repository there, and show the results in a grid on the web browser allowing the user to filter by email:

package com.example.app;

import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.Route;

import javax.inject.Inject;
import java.util.List;

@Route("")
public class MainView extends VerticalLayout {

    @Inject
    public MainView(UserRepository userRepository) {
        Long count = userRepository.count();
        Notification.show("Users: " + count);

        Grid<User> grid = new Grid<>(User.class);
        grid.setColumns("email", "birthDate");
        grid.setItems(userRepository.findAll());

        TextField filter = new TextField();
        filter.setPlaceholder("Filter by email...");
        filter.addValueChangeListener(event -> {
            List<User> users = userRepository.findByEmailLike("%" + filter.getValue() + "%");
            grid.setItems(users);
        });


        add(filter, grid);
    }

}
Enter fullscreen mode Exit fullscreen mode

There are more name conventions in Apache DeltaSpike. For example, we could have a method countByBirthDate(LocalDate birthDate) to return a long with the number of users born on a certain date or removeByEmail(String email) to delete a user given its email. The same happens with the suffixes (likeEmail in the example). We can use EmailNotLike, EmailIgnoreCase, and many more. Check the official documentation for more examples and details on how to use method expressions.

. . . . . . . . . . . . . . . . . . . . . . . . . . . .