Introduction à Jinq avec Spring

Introduction à Jinq avec le printemps

1. introduction

Jinq fournit une approche intuitive et pratique pour interroger les bases de données en Java. Dans ce didacticiel, nous allons explorerhow to configure a Spring project to use Jinq et certaines de ses fonctionnalités illustrées par des exemples simples.

2. Dépendances Maven

Nous devrons ajouterthe Jinq dependency dans le fichierpom.xml:


    org.jinq
    jinq-jpa
    1.8.22

Pour Spring, nous ajouteronsthe Spring ORM dependency dans le fichierpom.xml:


    org.springframework
    spring-orm
    5.0.3.RELEASE

Enfin, pour les tests, nous utiliserons une base de données en mémoire H2, donc ajoutons égalementthis dependency au fichierpom.xml:


    com.h2database
    h2
    1.4.196

3. Comprendre Jinq

Jinq nous aide à écrire des requêtes de base de données plus faciles et plus lisibles en exposant une API fluide basée en interne surthe Java Stream API.

Voyons un exemple où nous filtrons les voitures par modèle:

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, donc la requête finale dans cet exemple serait:

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

Étant donné que nous n'utilisons pas de texte brut pour écrire des requêtes et que nous utilisons plutôt une API de type sécurisé, cette approche est moins sujette aux erreurs.

De plus, Jinq vise à permettre un développement plus rapide en utilisant des expressions courantes faciles à lire.

Néanmoins, il présente certaines limites quant au nombre de types et d’opérations que nous pouvons utiliser, comme nous le verrons ci-après.

3.1. Limites

Jinq supports only the basic types in JPA and a concrete list of SQL functions. Cela fonctionne en traduisant les opérations lambda en une requête SQL native en mappant tous les objets et méthodes dans un type de données JPA et une fonction SQL.

Par conséquent, nous ne pouvons pas nous attendre à ce que l’outil traduise chaque type personnalisé ou toutes les méthodes d’un type.

3.2. Types de données pris en charge

Voyons les types de données et les méthodes pris en charge:

  • String -equals(),compareTo() méthodes uniquement

  • Types de données primitifs - opérations arithmétiques

  • Enums et classes personnalisées - prend en charge les opérations == et! = uniquement

  • java.util.Collection – contient ()

  • APIDate - méthodesequals(),before(),after() uniquement

Remarque: si nous voulions personnaliser la conversion d'un objet Java en objet de base de données, nous devions enregistrer notre implémentation concrète d'unAttributeConverter dans Jinq.

4. Intégration de Jinq avec Spring

Jinq needs an EntityManager instance to get the persistence context. Dans ce didacticiel, nous allons introduire une approche simple avec Spring pour faire fonctionner Jinq avec lesEntityManager fournis parHibernate.

4.1. Interface de référentiel

Spring uses the concept of repositories to manage entities. Regardons notre interfaceCarRepository où nous avons une méthode pour récupérer unCar pour un modèle donné:

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

4.2. Référentiel de base abstraite

Ensuite,we’ll need a base repository pour fournir toutes les capacités 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. Implémentation du référentiel

Maintenant, tout ce dont nous avons besoin pour Jinq est une instanceEntityManager et la classe de type d'entité.

Voyons l'implémentation du référentielCar en utilisant notre référentiel de base Jinq que nous venons de définir:

@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. Câblage desJinqJPAStreamProvider

Afin de câbler l'instanceJinqJPAStreamProvider, nous allonsadd the Jinq provider configuration:

@Configuration
public class JinqProviderConfiguration {

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

4.5. Configuration de l'application Spring

La dernière étape consiste àconfigure our Spring application using Hibernate and our Jinq configuration. Comme référence, consultez notre fichierapplication.properties, dans lequel nous utilisons une instance H2 en mémoire comme base de données:

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

5. Guide des requêtes

Jinq provides many intuitive options to customize the final SQL query with select, where, joins and more. Notez que ceux-ci ont les mêmes limitations que nous avons déjà introduites ci-dessus.

5.1. Où

La clausewhere permet d'appliquer plusieurs filtres à une collection de données.

Dans l'exemple suivant, nous souhaitons filtrer les voitures par modèle et description:

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

Et voici le SQL que Jinq traduit:

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

5.2. Sélectionner

Dans le cas où nous voulons récupérer seulement quelques colonnes / champs de la base de données, nous devons utiliser la clauseselect.

Afin de mapper plusieurs valeurs, Jinq fournit un certain nombre de classesTuple avec jusqu'à huit valeurs:

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

Et le SQL traduit:

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

5.3. Se joint

Jinq is able to resolve one-to-one and many-to-one relationships si les entités sont correctement liées.

Par exemple, si nous ajoutons l'entité fabricant dansCar:

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

Et l'entitéManufacturer avec la liste deCars:

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

Nous pouvons désormais obtenir lesManufacturer pour un modèle donné:

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

Comme prévu,Jinq will use an inner join SQL clause dans ce scénario:

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

Au cas où nous aurions besoin d'avoir plus de contrôle sur les clausesjoin afin d'implémenter des relations plus complexes sur les entités, comme une relation plusieurs-à-plusieurs, nous pouvons utiliser la méthodejoin:

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

Enfin, nous pourrions utiliser une clause SQL de jointure externe gauche en utilisant la méthodeleftOuterJoin au lieu de la méthodejoin.

5.4. Agrégations

Tous les exemples que nous avons présentés jusqu'à présent utilisent les méthodestoList oufindFirst - pour renvoyer le résultat final de notre requête dans Jinq.

Outre ces méthodes,we also have access to other methods to aggregate results.

Par exemple, utilisons la méthodecount pour obtenir le nombre total de voitures pour un modèle concret dans notre base de données:

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

Et le SQL final utilise la méthode SQLcount comme prévu:

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

Jinq fournit également des méthodes d'agrégation telles quesum,average,min,max, et lespossibility to combine different aggregations.

5.5. Pagination

Dans le cas où nous voulons lire des données par lots, nous pouvons utiliser les méthodeslimit etskip.

Voyons un exemple où nous voulons ignorer les 10 premières voitures et obtenir seulement 20 éléments:

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

Et le SQL généré est:

select c.* from car c limit ? offset ?

6. Conclusion

Nous y voilà. Dans cet article, nous avons vu une approche permettant de configurer une application Spring avec Jinq en utilisant Hibernate (de manière minimale).

Nous avons également brièvement exploré les avantages de Jinq et certaines de ses principales fonctionnalités.

Comme toujours, les sources peuvent être trouvéesover on GitHub.