Introdução ao Jinq com Spring

Introdução ao Jinq com Spring

1. Introdução

Jinq fornece uma abordagem intuitiva e útil para consultar bancos de dados em Java. Neste tutorial, exploraremoshow to configure a Spring project to use Jinq e alguns de seus recursos ilustrados com exemplos simples.

2. Dependências do Maven

Precisaremos adicionarthe Jinq dependency no arquivopom.xml:


    org.jinq
    jinq-jpa
    1.8.22

Para Spring, vamos adicionarthe Spring ORM dependency no arquivopom.xml:


    org.springframework
    spring-orm
    5.0.3.RELEASE

Finalmente, para teste, usaremos um banco de dados H2 na memória, então vamos adicionarthis dependency ao arquivopom.xml:


    com.h2database
    h2
    1.4.196

3. Compreendendo Jinq

Jinq nos ajuda a escrever consultas de banco de dados mais fáceis e legíveis, expondo uma API fluente que é baseada internamente emthe Java Stream API.

Vejamos um exemplo em que filtramos carros por modelo:

jinqDataProvider.streamAll(entityManager, Car.class)
  .where(c -> c.getModel().equals(model))
  .toList();

Jinq translates the above code snippet into a SQL query in an efficient way, então a consulta final neste exemplo seria:

select c.* from car c where c.model=?

Como não estamos usando texto simples para escrever consultas e, em vez disso, usamos uma API com segurança de tipos, essa abordagem é menos sujeita a erros.

Além disso, o Jinq visa permitir um desenvolvimento mais rápido usando expressões comuns e fáceis de ler.

No entanto, ele tem algumas limitações no número de tipos e operações que podemos usar, como veremos a seguir.

3.1. Limitações

Jinq supports only the basic types in JPA and a concrete list of SQL functions. Ele funciona traduzindo as operações lambda em uma consulta SQL nativa, mapeando todos os objetos e métodos em um tipo de dados JPA e uma função SQL.

Portanto, não podemos esperar que a ferramenta traduza todos os tipos personalizados ou todos os métodos de um tipo.

3.2. Tipos de dados suportados

Vamos ver os tipos de dados e métodos suportados:

  • String -equals(), métodoscompareTo() apenas

  • Tipos de dados primitivos - operações aritméticas

  • Enumse classes personalizadas - suporta apenas operações == e! =

  • java.util.Collection – contém ()

  • Date API -equals(),before(), métodosafter() apenas

Observação: se quisermos personalizar a conversão de um objeto Java em um objeto de banco de dados, precisaríamos registrar nossa implementação concreta de umAttributeConverter em Jinq.

4. Integrando Jinq com Spring

Jinq needs an EntityManager instance to get the persistence context. Neste tutorial, vamos apresentar uma abordagem simples com Spring para fazer Jinq trabalhar comEntityManager fornecido porHibernate.

4.1. Interface do Repositório

Spring uses the concept of repositories to manage entities. Vejamos nossa interfaceCarRepository onde temos um método para recuperar umCar para um determinado modelo:

public interface CarRepository {
    Optional findByModel(String model);
}

4.2. Repositório de base abstrato

A seguir,we’ll need a base repository para fornecer todos os recursos do Jinq:

public abstract class BaseJinqRepositoryImpl {
    @Autowired
    private JinqJPAStreamProvider jinqDataProvider;

    @PersistenceContext
    private EntityManager entityManager;

    protected abstract Class entityType();

    public JPAJinqStream stream() {
        return streamOf(entityType());
    }

    protected  JPAJinqStream streamOf(Class clazz) {
        return jinqDataProvider.streamAll(entityManager, clazz);
    }
}

4.3. Implementando o Repositório

Agora, tudo o que precisamos para Jinq é uma instânciaEntityManagere a classe de tipo de entidade.

Vamos ver a implementação do repositórioCar usando nosso repositório base Jinq que acabamos de definir:

@Repository
public class CarRepositoryImpl
  extends BaseJinqRepositoryImpl implements CarRepository {

    @Override
    public Optional findByModel(String model) {
        return stream()
          .where(c -> c.getModel().equals(model))
          .findFirst();
    }

    @Override
    protected Class entityType() {
        return Car.class;
    }
}

4.4. Conectando oJinqJPAStreamProvider

Para conectar a instânciaJinqJPAStreamProvider, vamosadd the Jinq provider configuration:

@Configuration
public class JinqProviderConfiguration {

    @Bean
    @Autowired
    JinqJPAStreamProvider jinqProvider(EntityManagerFactory emf) {
        return new JinqJPAStreamProvider(emf);
    }
}

4.5. Configurando o aplicativo Spring

A etapa final éconfigure our Spring application using Hibernate and our Jinq configuration.. Como referência, consulte nosso arquivoapplication.properties, no qual usamos uma instância H2 na memória como banco de dados:

spring.datasource.url=jdbc:h2:~/jinq
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop

5. Guia de consulta

Jinq provides many intuitive options to customize the final SQL query with select, where, joins and more. Observe que eles têm as mesmas limitações que já apresentamos acima.

5.1. Onde

A cláusulawhere permite aplicar vários filtros a uma coleção de dados.

No próximo exemplo, queremos filtrar carros por modelo e descrição:

stream()
  .where(c -> c.getModel().equals(model)
    && c.getDescription().contains(desc))
  .toList();

E este é o SQL que Jinq traduz:

select c.model, c.description from car c where c.model=? and locate(?, c.description)>0

5.2. Selecione

Caso desejemos recuperar apenas algumas colunas / campos do banco de dados, precisamos usar a cláusulaselect.

Para mapear vários valores, Jinq fornece várias classesTuple com até oito valores:

stream()
  .select(c -> new Tuple3<>(c.getModel(), c.getYear(), c.getEngine()))
  .toList()

E o SQL traduzido:

select c.model, c.year, c.engine from car c

5.3. Junções

Jinq is able to resolve one-to-one and many-to-one relationships se as entidades estiverem devidamente vinculadas.

Por exemplo, se adicionarmos a entidade fabricante emCar:

@Entity(name = "CAR")
public class Car {
    //...
    @OneToOne
    @JoinColumn(name = "name")
    public Manufacturer getManufacturer() {
        return manufacturer;
    }
}

E a entidadeManufacturer com a lista deCars:

@Entity(name = "MANUFACTURER")
public class Manufacturer {
    // ...
    @OneToMany(mappedBy = "model")
    public List getCars() {
        return cars;
    }
}

Agora podemos obterManufacturer para um determinado modelo:

Optional manufacturer = stream()
  .where(c -> c.getModel().equals(model))
  .select(c -> c.getManufacturer())
  .findFirst();

Como esperado,Jinq will use an inner join SQL clause neste cenário:

select m.name, m.city from car c inner join manufacturer m on c.name=m.name where c.model=?

No caso de precisarmos ter mais controle sobre as cláusulasjoin para implementar relacionamentos mais complexos sobre as entidades, como uma relação muitos para muitos, podemos usar o métodojoin:

List> list = streamOf(Manufacturer.class)
  .join(m -> JinqStream.from(m.getCars()))
  .toList()

Finalmente, poderíamos usar uma cláusula SQL de junção externa esquerda usando o métodoleftOuterJoin em vez do métodojoin.

5.4. Agregações

Todos os exemplos que apresentamos até agora estão usando os métodostoList oufindFirst - para retornar o resultado final de nossa consulta no Jinq.

Além desses métodos,we also have access to other methods to aggregate results.

Por exemplo, vamos usar o métodocount para obter a contagem total de carros para um modelo concreto em nosso banco de dados:

long total = stream()
  .where(c -> c.getModel().equals(model))
  .count()

E o SQL final está usando o método SQLcount conforme o esperado:

select count(c.model) from car c where c.model=?

Jinq também fornece métodos de agregação comosum,average,min,max,epossibility to combine different aggregations.

5.5. Paginação

Caso desejemos ler os dados em lotes, podemos usar os métodoslimiteskip.

Vejamos um exemplo em que queremos pular os primeiros 10 carros e obter apenas 20 itens:

stream()
  .skip(10)
  .limit(20)
  .toList()

E o SQL gerado é:

select c.* from car c limit ? offset ?

6. Conclusão

Aqui vamos nós. Neste artigo, vimos uma abordagem para configurar um aplicativo Spring com o Jinq usando o Hibernate (minimamente).

Também exploramos brevemente os benefícios do Jinq e alguns de seus principais recursos.

Como sempre, as fontes podem ser encontradasover on GitHub.