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.