Einführung in Jinq mit Frühling

1. Einleitung

Jinq bietet einen intuitiven und praktischen Ansatz zum Abfragen von Datenbanken in Java. In diesem Tutorial erfahren Sie, wie Sie ein Spring-Projekt für die Verwendung von Jinq konfigurieren und einige seiner Funktionen anhand einfacher Beispiele veranschaulichen.

2. Maven-Abhängigkeiten

Wir müssen die Jinq-Abhängigkeit in die Datei pom.xml einfügen :

 org.jinq jinq-jpa 1.8.22 

Für Spring fügen wir die Spring ORM-Abhängigkeit in die Datei pom.xml ein :

 org.springframework spring-orm 5.2.5.RELEASE 

Schließlich verwenden wir zum Testen eine H2-In-Memory-Datenbank. Fügen wir also diese Abhängigkeit zusammen mit Spring-Boot-Starter-Data-Jpa zur Datei pom.xml hinzu:

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

3. Jinq verstehen

Jinq hilft uns dabei, einfachere und besser lesbare Datenbankabfragen zu schreiben, indem eine fließende API verfügbar gemacht wird, die intern auf der Java Stream-API basiert .

Sehen wir uns ein Beispiel an, in dem wir Autos nach Modell filtern:

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

Jinq übersetzt das obige Codefragment auf effiziente Weise in eine SQL-Abfrage. Die letzte Abfrage in diesem Beispiel lautet also:

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

Da wir zum Schreiben von Abfragen keinen Klartext verwenden und stattdessen eine typsichere API verwenden, ist dieser Ansatz weniger fehleranfällig.

Darüber hinaus möchte Jinq eine schnellere Entwicklung ermöglichen, indem allgemeine, einfach zu lesende Ausdrücke verwendet werden.

Trotzdem gibt es einige Einschränkungen hinsichtlich der Anzahl der Typen und Operationen, die wir verwenden können, wie wir als nächstes sehen werden.

3.1. Einschränkungen

Jinq unterstützt nur die Grundtypen in JPA und eine konkrete Liste von SQL-Funktionen. Dabei werden die Lambda-Operationen in eine native SQL-Abfrage übersetzt, indem alle Objekte und Methoden einem JPA-Datentyp und einer SQL-Funktion zugeordnet werden.

Daher können wir nicht erwarten, dass das Tool jeden benutzerdefinierten Typ oder alle Methoden eines Typs übersetzt.

3.2. Unterstützte Datentypen

Sehen wir uns die unterstützten Datentypen und unterstützten Methoden an:

  • Nur String - equals () , compareTo () - Methoden
  • Primitive Datentypen - arithmetische Operationen
  • Aufzählungen und benutzerdefinierte Klassen - unterstützt nur Operationen == und! =
  • java.util.Collection - enthält ()
  • Datums- API - entspricht nur () , vor () , nur nach () Methoden

Hinweis: Wenn wir die Konvertierung von einem Java-Objekt in ein Datenbankobjekt anpassen möchten , müssen wir unsere konkrete Implementierung eines AttributeConverter in Jinq registrieren.

4. Integration von Jinq in Spring

Jinq benötigt eine EntityManager- Instanz, um den Persistenzkontext abzurufen . In diesem Tutorial stellen wir Spring einen einfachen Ansatz vor, mit dem Jinq mit dem von Hibernate bereitgestellten EntityManager funktioniert .

4.1. Repository-Schnittstelle

Spring verwendet das Konzept von Repositorys, um Entitäten zu verwalten. Schauen wir uns unsere CarRepository- Oberfläche an, in der wir eine Methode zum Abrufen eines Autos für ein bestimmtes Modell haben:

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

4.2. Abstraktes Basis-Repository

Als Nächstes benötigen wir ein Basis-Repository , um alle Jinq-Funktionen bereitzustellen:

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. Repository implementieren

Jetzt benötigen wir für Jinq nur noch eine EntityManager- Instanz und die Entitätstypklasse.

Sehen wir uns die Implementierung des Car- Repositorys mit unserem soeben definierten Jinq-Basis-Repository an:

@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. Verdrahtung des JinqJPAStreamProviders

Um die JinqJPAStreamProvider- Instanz zu verkabeln , fügen wir die Jinq-Provider-Konfiguration hinzu:

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

4.5. Konfigurieren der Spring-Anwendung

Der letzte Schritt besteht darin , unsere Spring-Anwendung mit Hibernate und unserer Jinq-Konfiguration zu konfigurieren. Als Referenz sehen Sie unsere Datei application.properties , in der wir eine speicherinterne H2-Instanz als Datenbank verwenden:

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

5. Abfragehandbuch

Jinq bietet viele intuitive Optionen zum Anpassen der endgültigen SQL-Abfrage mit select, where, joins und mehr. Beachten Sie, dass diese dieselben Einschränkungen haben, die wir oben bereits eingeführt haben.

5.1. Wo

Die where- Klausel ermöglicht das Anwenden mehrerer Filter auf eine Datenerfassung.

Im nächsten Beispiel möchten wir Autos nach Modell und Beschreibung filtern:

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

Und dies ist das SQL, das Jinq übersetzt:

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

5.2. Wählen

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

Na, bitte. In diesem Artikel haben wir einen Ansatz zum Einrichten einer Spring-Anwendung mit Jinq mithilfe von Hibernate (minimal) gesehen.

Wir haben auch kurz die Vorteile von Jinq und einige seiner Hauptmerkmale untersucht.

Wie immer finden Sie die Quellen auf GitHub.