Understanding JPA and Other Query Construction Frameworks
Hibernate vs. JPA: several frameworks are available to generate the request or the command, but only some options are the most relevant. Let's explore which.
Join the DZone community and get the full member experience.Join For Free
JPA is the standard that guides the developer towards an object conceptualization of his application and provides him with an abstraction of the relational model. However, once the model is in place, several frameworks are available to generate the request or the command. Which choices are the most relevant? Let's explore.
Once your dependencies are added, the Criteria API is immediately available. It is the most basic and requires knowing the API and its subtleties to produce requests. The use of these objects obliges to pass directly in character string the name of the attributes in order to impose restrictions. Errors are therefore quite frequent during development and during refactoring operations. Plus, because these errors happen at run time, they are often caught too late.
This API is mainly used in older Spring MVC applications. If you are relying on spring-boot, you will choose a "starter" dependency. The corresponding dependency will be spring-boot-starter-data-jpa. This directly imports the "Spring-Data" API.
For all simple requests, Spring-Data JPA is the solution to remember.
Why? Quite simply because it allows you to do the work directly for you!
Spring Data JPA is based on the notion of Repository and the DDD (Domain Drived Design) approach. No need for the developer to explicitly write a request as Spring Data does it automatically using the name of the methods written in the Repository interface. On the shelf, you will also find all the useful methods for filters, paginations, procedure initiation, auditing, etc.
That said, Spring Data also provides @Query annotations that allow you to write JPQL queries verbatim. If we use these annotations, we fall back on Criteria's "non-type-safe" pitfalls. So, let's reserve this annotation for very small apps, the model of which we will be sure to know by heart for the long term, and let's stick to this annotation for all other applications.
If a query is complex (i.e. not a classic CRUD operation that can be written with a single method name in my interface), what are my solutions?
List<User> findByAgeOrderByLastnameDesc(int age); List<User> findByAgeNotIn(Collection<Integer> ages); List<User> findByFirstnameEndsWith(String firstname);
Was the object model badly designed? Entities must not correspond to tables. Instead, they must correspond to objects that you manipulate in the application.
For example, retrieving records from a single entity can amount to joining 10 tables at the same time if the entity contains collections or maps, or if the data of an object is shared over several tables. To map all of its attributes into the entity, one will find in JPA a whole set of annotations (@OneToMany, @SecondaryTable, @Embedded, @CollectionOfElements, etc.)
Indicating in entities a reference to other objects using only their identifier should never happen. Instead, we expect references from other model objects, except when some microservice architectures where the data is non-redundant. We must also be able to find inheritance between objects, aggregation, chaining, etc., as well as whole bunch of complex concepts in a relational database, which serves the OOP and that JPA/Hibernate will map for you in the relational model. Once the object model has been reworked, complex queries may no longer be necessary.
In spite of all the rework of your object model, you may not be able to avoid redundancies. Several objects use a column or several objects use a table. This is particularly the case if an object corresponds to an assembly of information adjoining other types. It does not matter.
On the other hand, it is also possible to create views in the relational database and to match an entity (without setters) to this view. That said, the ORM does not know how to generate the view on its own. We must see if this solution is acceptable given the mode of updating the relational database.
Finally if the object model still does not meet the needs of complex queries, then we will write queries.
QueryDSL is a framework that allows you to write "type-safe" queries, thus avoiding inconvenience during refactoring phases and allowing developers to benefit from autocompletion in the IDE. QueryDSL offers solutions whatever the chosen persistence tool (JPA, JDO, etc.) and whatever the database (relational or not, SQL-NoSQL, Lucene). The principle is the same as for Criteria: each business entity will be associated with a corresponding meta-model class thanks to the APT (annotation processor tool).
For QueryDSL, the generated class will be named "QUuser" for a "User" business entity.
The syntax for constructing queries with QueryDSL is less verbose, faster to assimilate and more readable than that proposed by Criteria.
List<Person> persons = queryFactory.selectFrom(person) .where( person.firstName.eq("John"), person.lastName.eq("Doe")) .fetch();
Now Let's Chat About Micro-Services
Beyond the questions on JPA libraries, an additional question arises in the case of micro-service architectures.
The principle of micro-services is to have smaller applications, responsible for a more restricted business area. If a web application uses a collection of micro-services to function, it means that the entire relational model of the application has been broken up.
In this case, we already realize from the outset that complex requests on the model are no longer possible; it is the application that will have to perform the data assemblies itself by API calls or with events. We divide the load of a complex request into a series of simple requests, supported by a collection of micro-services.
Could we reproduce the same execution scheme if we use a single database with separate schemes? What if all our webapps are on the same machine? This would have the advantage of being able to cut the application later more easily. However, the execution would be less optimal in the meantime.
Keep in mind requesting a database several times is slower than requesting it once, which is why with micro-services, everyone has their own database and their own scalable machine.
To Go Further: CQRS
In this micro-services approach, we often ask ourselves the question: "From which webapp do we want to execute request or command, and which webapp will be responsible for which entity?"
The CQRS architecture questions us even more about the responsibility of an entity.
We can see that in all applications, database read requests are much more frequent than update commands. The idea of CQRS is to be able to scale the reads on one micro-service and the writes on another micro-service to be able to provide the system with the right amount of resources necessary for the executions.
If we also realize that the users who read the data and the users who update the data are not the same, then the CQRS is essential.
In this case, the JPA impact is as follows:
In a writing micro-service, we will find an entity containing its direct attributes. For the rest, only the keys of related objects, i.e. we ignore anyway the attributes of objects that are not useful for writing our entity.
In a reading micro-service, we will find the complete entity with all the related entities, i.e. there will be no setter in the entities.
Opinions expressed by DZone contributors are their own.