Introduzione a Jinq con la primavera

1. Introduzione

Jinq fornisce un approccio intuitivo e pratico per eseguire query sui database in Java. In questo tutorial, esploreremo come configurare un progetto Spring per utilizzare Jinq e alcune delle sue funzionalità illustrate con semplici esempi.

2. Dipendenze di Maven

Dovremo aggiungere la dipendenza Jinq nel file pom.xml :

 org.jinq jinq-jpa 1.8.22 

Per Spring, aggiungeremo la dipendenza ORM Spring nel file pom.xml :

 org.springframework spring-orm 5.2.5.RELEASE 

Infine, per i test, useremo un database in memoria H2, quindi aggiungiamo anche questa dipendenza, insieme a spring-boot-starter-data-jpa al file pom.xml:

 com.h2database h2 1.4.200   org.springframework.boot spring-boot-starter-data-jpa 2.2.6.RELEASE 

3. Capire Jinq

Jinq ci aiuta a scrivere query di database più facili e più leggibili esponendo un'API fluente basata internamente sull'API Java Stream.

Vediamo un esempio in cui filtriamo le auto per modello:

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

Jinq traduce lo snippet di codice sopra in una query SQL in modo efficiente , quindi la query finale in questo esempio sarebbe:

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

Poiché non utilizziamo testo normale per scrivere query e utilizziamo invece un'API indipendente dai tipi, questo approccio è meno soggetto a errori.

Inoltre, Jinq mira a consentire uno sviluppo più rapido utilizzando espressioni comuni e di facile lettura.

Tuttavia, ha alcune limitazioni nel numero di tipi e operazioni che possiamo usare, come vedremo in seguito.

3.1. Limitazioni

Jinq supporta solo i tipi di base in JPA e un elenco concreto di funzioni SQL. Funziona traducendo le operazioni lambda in una query SQL nativa mappando tutti gli oggetti e metodi in un tipo di dati JPA e una funzione SQL.

Pertanto, non possiamo aspettarci che lo strumento traduca ogni tipo personalizzato o tutti i metodi di un tipo.

3.2. Tipi di dati supportati

Vediamo i tipi di dati e i metodi supportati supportati:

  • String - solo metodi equals () , compareTo ()
  • Tipi di dati primitivi: operazioni aritmetiche
  • Enumerazioni e classi personalizzate: supporta solo le operazioni == e! =
  • java.util.Collection - contains ()
  • Date API - solo metodi equals () , before () , after ()

Nota: se volessimo personalizzare la conversione da un oggetto Java a un oggetto database, avremmo bisogno di registrare la nostra implementazione concreta di un AttributeConverter in Jinq.

4. Integrazione di Jinq con la primavera

Jinq necessita di un'istanza EntityManager per ottenere il contesto di persistenza. In questo tutorial, introdurremo un semplice approccio con Spring per far funzionare Jinq con EntityManager fornito da Hibernate.

4.1. Interfaccia del repository

Spring utilizza il concetto di repository per gestire le entità. Diamo un'occhiata alla nostra interfaccia CarRepository dove abbiamo un metodo per recuperare un'auto per un dato modello:

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

4.2. Repository di base astratta

Successivamente, avremo bisogno di un repository di base per fornire tutte le funzionalità 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. Implementazione del repository

Ora, tutto ciò di cui abbiamo bisogno per Jinq è un'istanza EntityManager e la classe del tipo di entità.

Vediamo l' implementazione del repository Car utilizzando il nostro repository di base Jinq che abbiamo appena definito:

@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. Cablaggio di JinqJPAStreamProvider

Per collegare l' istanza JinqJPAStreamProvider , aggiungeremo la configurazione del provider Jinq:

@Configuration public class JinqProviderConfiguration { @Bean @Autowired JinqJPAStreamProvider jinqProvider(EntityManagerFactory emf) { return new JinqJPAStreamProvider(emf); } }

4.5. Configurazione dell'applicazione Spring

Il passaggio finale consiste nel configurare la nostra applicazione Spring utilizzando Hibernate e la nostra configurazione Jinq. Come riferimento, vedere il nostro file application.properties , in cui usiamo un'istanza H2 in memoria come database:

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

5. Guida alle query

Jinq fornisce molte opzioni intuitive per personalizzare la query SQL finale con select, where, join e altro. Nota che questi hanno le stesse limitazioni che abbiamo già introdotto sopra.

5.1. Dove

La dove la clausola consente di applicare più filtri a una raccolta di dati.

Nel prossimo esempio, vogliamo filtrare le auto per modello e descrizione:

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

E questo è l'SQL che Jinq traduce:

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

5.2. Selezionare

In case we want to retrieve only a few columns/fields from the database, we need to use the select clause.

In order to map multiple values, Jinq provides a number of Tuple classes with up to eight values:

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

And the translated SQL:

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

5.3. Joins

Jinq is able to resolve one-to-one and many-to-one relationships if the entities are properly linked.

For example, if we add the manufacturer entity in Car:

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

And the Manufacturer entity with the list of Cars:

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

We're now able to get the Manufacturer for a given model:

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

As expected, Jinq will use an inner join SQL clause in this scenario:

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

In case we need to have more control over the join clauses in order to implement more complex relationships over the entities, like a many-to-many relation, we can use the join method:

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

Finally, we could use a left outer join SQL clause by using the leftOuterJoin method instead of the join method.

5.4. Aggregations

All the examples we have introduced so far are using either the toList or the findFirst methods – to return the final result of our query in Jinq.

Besides these methods, we also have access to other methods to aggregate results.

For example, let's use the count method to get the total count of the cars for a concrete model in our database:

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

And the final SQL is using the count SQL method as expected:

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

Jinq also provides aggregation methods like sum, average, min, max, and the possibility to combine different aggregations.

5.5. Pagination

In case we want to read data in batches, we can use the limit and skip methods.

Let's see an example where we want to skip the first 10 cars and get only 20 items:

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

And the generated SQL is:

select c.* from car c limit ? offset ?

6. Conclusion

Ci siamo. In questo articolo, abbiamo visto un approccio per impostare un'applicazione Spring con Jinq utilizzando Hibernate (minimo).

Abbiamo anche esaminato brevemente i vantaggi di Jinq e alcune delle sue caratteristiche principali.

Come sempre, i sorgenti possono essere trovati su GitHub.