JPA Queries con Streams de Java

JPA Queries con Streams de Java


Desde la aparición de los streams en Java 8, estos han sido un imprescindible en nuestros desarrollos. Trabajamos de manera constante con ellos en nuestros procesos y al tratar datos, nos surge el inconveniente de guardar estos streams en Base de Datos ya que habrá que realizar algún procesamiento. Para esos casos podemos hacer uso de la librería JPAStreamer, que veremos en este artículo sobre JPA Queries con Streams de Java. Esta librería nos va a permitir crear queries en SQL a partir de nuestros Streams.

¿Qué es JPAStreamer?

JPAStreamer es una librería para expresar queries usando Streams de Java con JPA. Es compatible con Spring añadiendo dependencias. Además nos permitirá que nuestras queries sean más intuitivas y tener menos errores en su uso.

JPAstreamer proporciona una API totalmente segura para escribir consultas JPA lo que nos ayudará a detectar errores en tiempo de compilación.

JPAStreamer va a tratar nuestros streams en Java como si de consultas SQL se tratasen, vamos a ver unos ejemplos básico, para ver más casos no dudes en echar un ojo a la documentación oficial:

  • Obtener todos los registros de una tabla con JPAStreamer:
jpaStreamer.stream(User.class) 
    .forEach(System.out::println);
  • Filtrar los registros de una tabla con JPAStreamer:
jpaStreamer.stream(User.class)
         .filter(User$.age.greaterThan(10))
         .collect(Collectors.toList());
  • Ordenar por un campo con JPAStreamer:
streamer.stream(User.class)
         .filter(User$.age.greaterThan(10))
         .sorted(User$.name)
         .collect(Collectors.toList());

Hands On

A continuación vamos a crear un simple ejemplo en Spring Boot. Para ello vamos a crear tres tablas en una base de datos que estará mapeada por tres entidades en nuestra aplicación. La aplicación expondrá un CRUD y hara uso de JPA con JPAStreamer.

En nuestro ejemplo vamos a hacer uso de Lombok para inyectar dependencias y eliminar un poco de boilerplate de Java.

Nuestro ejemplo consistirá en tres entidades, user, payment y seat, si quieres puedes verlo en nuestro github.

Generar proyecto

Lo primero que vamos a hacer es generar nuestro proyecto con spring initializr añadiendo las dependecias de spring:

  • Spring web
  • Spring data
  • H2
  • Lombok

Con estas dependencias tendremos el esqueleto básico para comenzar con JPAStreamer.

Dependencias y configuración Maven para JPAStreamer

Una vez hemos generado nuestro proyecto, añadimos las dependencias maven de JPAStreamer:

<dependency>
  <groupId>com.speedment.jpastreamer</groupId>
  <artifactId>jpastreamer-core</artifactId>
  <version>${version}</version>
</dependency>
<dependency>
  <groupId>com.speedment.jpastreamer.integration.spring</groupId>
  <artifactId>spring-boot-jpastreamer-autoconfigure</artifactId>
  <version>${version}</version>
</dependency>

Creación de la entidades

Vamos a crear las tres entidades user, payment y seat. En todos los casos vamos a utilizar la carga de tipo EAGER, por simplicidad y porque vamos a tener pocos datos en nuestra base de datos.

Entidad user

@Getter
@Setter
@NoArgsConstructor
@Entity
@ToString
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    private String name;

    private String surname;

    private LocalDate birth;

    private String email;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonIgnoreProperties("user")
    private Set<Seat> seats;

    @OneToMany(mappedBy = "user")
    @JsonIgnoreProperties("user")
    private List<Payment> payments;

}

Entidad payment

@Getter
@Setter
@Entity
@ToString
public class Payment {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String status;

    @ManyToOne(fetch = FetchType.EAGER)
    private User user;

    @OneToOne(mappedBy = "payment")
    private Seat seat;

    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate date;

    private BigDecimal amount;

}

Entidad seat

@Entity
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
@NoArgsConstructor
public class Seat {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String ref;

    private String status;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "user_id")
    private User user;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "payment_id", referencedColumnName = "id")
    private Payment payment;
}

Creación de Queries con JPA y Java Stremas con JpaStreamer

Una vez creadas las entidades, vamos a crear una capa de servicio que será la encargada de realizar las queries con JpaStreamer.

Para ello vamos a crear consultas sobre user, seat y payments, por lo que primero tendremos que inyectar la dependencia de JPAstreamer, y hacer uso de su clase stream para realizar el acceso a base de datos.

Antes de ver la creación de los servicios vamos a ver algunas queries que genera JPAstreamer así como la consulta a base de datos que hacer Hibernate:

Obtener todos los seats:

jpaStreamer.stream(Seat.class)
         .collect(Collectors.toList());
Hibernate: 
    select
        seat0_.id as id1_1_,
        seat0_.payment_id as payment_4_1_,
        seat0_.ref as ref2_1_,
        seat0_.status as status3_1_,
        seat0_.user_id as user_id5_1_ 
    from
        seat seat0_

Vamos a mostrar un ejemplo de Join de payment con seat y user para filtrar por id, para ello vamos a ejecutar el siguiente endpoint de nuestro ejemplo, localhost:8090/payments/1.

        return jpaStreamer.stream(of(Payment.class)
                .joining(Payment$.seat)
                .joining(Payment$.user))
                .filter(payment -> payment.getId().equals(id))
                .findFirst()
                .orElseThrow();

La query que se va a ejecutar con las dos left join sería:

    select
        payment0_.id as id1_0_0_,
        user1_.id as id1_2_1_,
        seat2_.id as id1_1_2_,
        payment0_.amount as amount2_0_0_,
        payment0_.date as date3_0_0_,
        payment0_.status as status4_0_0_,
        payment0_.user_id as user_id5_0_0_,
        user1_.birth as birth2_2_1_,
        user1_.email as email3_2_1_,
        user1_.name as name4_2_1_,
        user1_.surname as surname5_2_1_,
        seat2_.payment_id as payment_4_1_2_,
        seat2_.ref as ref2_1_2_,
        seat2_.status as status3_1_2_,
        seat2_.user_id as user_id5_1_2_ 
    from
        payment payment0_ 
    left outer join
        user user1_ 
            on payment0_.user_id=user1_.id 
    left outer join
        seat seat2_ 
            on payment0_.id=seat2_.payment_id
{"id":1,"status":"PAID","user":{"id":1,"name":"noel","surname":"rodriguez","birth":null,"email":"refactorizando.web@gmail.com","seats":[{"id":1,"ref":null,"status":"FREE","payment":null}],"payments":[{"id":1,"status":"PAID","seat":null,"date":null,"amount":1.10}]},"seat":null,"date":null,"amount":1.10}

Creación de query filtrando por nombre de user, aplicamos un filter en el stream y lo convertimos a lista, el siguiente endpoint puede ser utilizado para ver el resultado, http://localhost:8090/users?name=noel

jpaStreamer.stream(User.class)
                .filter(user -> user.getName().equalsIgnoreCase(name))
                .collect(Collectors.toList());

Resultado:

[{"id":1,"name":"noel","surname":"rodriguez","birth":null,"email":"refactorizando.web@gmail.com","seats":[{"id":1,"ref":null,"status":"FREE","payment":null}],"payments":[{"id":1,"status":"PAID","seat":null,"date":null,"amount":1.10}]}]

Los servicios que se creen serán los encargados de inyectar la dependencia de JPAstreamer y definir los accesos a base de datos a través del stream que se genera. Es decir, se hará uso de JPAstreamer. Por ejemplo vamos a ver el servicio de user:

@RequiredArgsConstructor
@Service
@Slf4j
public class UserService {

    private final JPAStreamer jpaStreamer;

    public List<User> findAll() {

        return jpaStreamer.stream(User.class).collect(Collectors.toList());

    }

    public User findById(Long id) {

        return jpaStreamer.stream(User.class)
                .filter(user -> user.getId().equals(id))
                .findFirst()
                .orElseThrow();
    }

    public List<User> findByName(String name) {

        return jpaStreamer.stream(User.class)
                .filter(user -> user.getName().equalsIgnoreCase(name))
                .collect(Collectors.toList());
    }

}

La clase anterior se encargará de definir el JPAStreamer para ser invocado y poder hacer las queries contra la base de datos.

Si quieres ver el ejemplo completo echa un vistazo aquí.

Conclusión

El uso de JPA Queries con Streams de Java nos va a facilitar mucho nuestra interacción con nuestras bases de datos, ya que nos va a permitir haciendo uso de los streams de java poder conectarnos y realizar consultas.

JPAStreamer va a ser de gran utilidad en tus desarrollos si ya estas familiarizado con los streams de java, y sino lo estas su simplicidad y documentación te ayudará bastante para poder usarlo.

El ejemplo completo se puede visualizar en nuestro github.

Si necesitas más información puedes escribirnos un comentario o un correo electrónico a refactorizando.web@gmail.com o también nos puedes contactar por nuestras redes sociales Facebook o twitter y te ayudaremos encantados!


1 pensamiento sobre “JPA Queries con Streams de Java

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *