Wednesday, May 15, 2024
HomeJavaCreate higher Standards queries with Blaze persistence

Create higher Standards queries with Blaze persistence


The Blaze Persistence venture gives an attention-grabbing different to JPA’s Standards API. Each APIs allow you to outline queries dynamically at runtime. Most builders use it to create question statements based mostly on person enter or the results of a enterprise operation. Sadly, JPA’s Standards API isn’t very fashionable as a result of it’s onerous to learn and write. As I’ll present you on this tutorial, Blaze Persistence’s Standards API gives an easier-to-use answer. It integrates with numerous JPA implementations and gives an enormous set of question options.

On this tutorial, I will provide you with a fast introduction to the Blaze Persistence Standards API. In future articles, we’ll take a better take a look at extra superior question options, like CTE and window features, the extensions to JPA’s Standards API, and the entity view characteristic.

Including Blaze Persistence to your venture

Blaze Persistence consists of a number of modules. You all the time want so as to add the core API and implementation to your venture. And also you mix it with an integration module on your particular JPA implementation. You will discover a full listing of all modules within the documentation.

For this text, I’m utilizing Blaze Persistence with Hibernate 6. So, I would like so as to add dependencies to Hibernate and Blaze Persistence’s core API, core implementation, and Hibernate 6 integration to my pom.xml file.

<dependency>
	<groupId>org.hibernate.orm</groupId>
	<artifactId>hibernate-core</artifactId>
	<model>${model.hibernate}</model>
</dependency>
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-core-api-jakarta</artifactId>
	<model>${model.blaze}</model>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-core-impl-jakarta</artifactId>
	<model>${model.blaze}</model>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-integration-hibernate-6.0</artifactId>
	<model>${model.blaze}</model>
	<scope>runtime</scope>
</dependency>

Within the subsequent step, you should instantiate a CriteriaBuilderFactory occasion. You’ll use that object in your persistence code to create new queries.

CriteriaBuilderConfiguration config = Standards.getDefault();
CriteriaBuilderFactory cbf = config.createCriteriaBuilderFactory(this.emf);

You need to instantiate the CriteriaBuilderFactory at software startup and solely create 1 occasion. Relying in your atmosphere, you may need to try this in a CDI producer or outline a singleton bean in Spring. The official documentation gives a number of detailed examples of this.

Let’s use the CriteriaBuilderFactory cbf to outline some queries.

Making a fundamental question

Utilizing Blaze Persistence, it solely takes 2 methodology calls to outline a easy question and execute it. The next code snippet exhibits you the creation and execution of a question that selects all ChessPlayer entities from the database.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class).getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

A name of the create methodology defines a question that shall be executed utilizing the supplied EntityManager occasion. The 2nd methodology parameter specifies the return kind of the question. And it additionally acts because the default definition of the SELECT and FROM clauses. If you wish to, you’ll be able to, in fact, overwrite these clauses.

You may see the executed SQL assertion in your log file while you execute this straightforward take a look at case and use my advisable logging configuration for Hibernate. As anticipated, the question selects all information from the ChessPlayer desk and returns all columns mapped by the ChessPlayer entity class.

17:53:39,968 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model 
    from
        ChessPlayer c1_0
17:53:40,037 INFO  [com.thorben.janssen.TestBlazeCriteria] - Magnus Carlsen
17:53:40,037 INFO  [com.thorben.janssen.TestBlazeCriteria] - Jorden van Foreest
17:53:40,037 INFO  [com.thorben.janssen.TestBlazeCriteria] - Anish Giri
17:53:40,037 INFO  [com.thorben.janssen.TestBlazeCriteria] - Fabiano Caruana

Hibernate then maps the question end result to managed ChessPlayer entity objects. You should use these objects in the identical method as any entity object you fetched utilizing a JPQL question or any of Hibernate’s APIs.

That is clearly a really fundamental question that you’ll not use in your software. You have to so as to add a WHERE clause, be a part of different tables, order the end result and use pagination. So, let’s try this subsequent.

Defining a WHERE clause

You may outline the WHERE clause of your question by calling the the place methodology on the CriteriaBuilder object returned by the create methodology.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class)
							   .the place("firstName").eq("Fabiano")
							   .the place("lastName").eq("Caruana")
							   .getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

As you’ll be able to see within the code snippet, Blaze Persistence gives a fluent API, and you’ll name the the place methodology a number of occasions. Every name returns a PredicateBuilder. The builder gives a set of strategies to outline predicates, like equals, larger than, much less, or equal than, and is empty. You may also examine if an entity is a member of an affiliation, outline subselects, and far more.

On this instance, I preserve it easy. I name the eq methodology to outline equal comparisons of the firstName and lastName attributes of my ChessPlayer entity.

The parameters that I present to the eq methodology make the definition of this WHERE clause attention-grabbing. As a substitute of defining a bind parameter, I present the parameter worth. With different question languages and APIs, e.g., JPQL, that’s one thing you shouldn’t do as a result of it causes SQL injection vulnerabilities. However you don’t have to fret about that when utilizing Blaze Persistence. It routinely provides a bind parameter to the question assertion and units the supplied worth because the bind parameter worth.

19:03:52,772 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model 
    from
        ChessPlayer c1_0 
    the place
        c1_0.firstName=? 
        and c1_0.lastName=?
19:03:52,824 INFO  [com.thorben.janssen.TestBlazeCriteria] - Fabiano Caruana

Within the take a look at case, I known as the the place methodology twice. As you’ll be able to see within the log output, Blaze Persistence generated a compound predicate for it and related the two predicates with AND.

Creating nested compound predicates

You may outline your personal compound predicates by calling the whereAnd or whereOr methodology. These strategies return a WhereAndBuilder or WhereOrBuilder occasion, which you should utilize to outline a nested compound predicate by calling the the place methodology a number of occasions. And after you’ve added all predicates to your compound predicate, you should name the endAnd or endOr methodology to shut your compound predicate.

Don’t fear; it’s a lot simpler than it’d sound. I exploit these strategies within the following instance to pick the chess gamers Fabiano Caruana and Magnus Carlsen.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class)
							   .whereOr()
									.whereAnd().the place("firstName").eq("Fabiano")
										.the place("lastName").eq("Caruana")
									.endAnd()
									.whereAnd().the place("firstName").eq("Magnus")
										.the place("lastName").eq("Carlsen")
									.endAnd()
							   .endOr()
							   .getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

Once I execute this take a look at case, you’ll be able to see within the log output that Blaze Persistence generated the anticipated question. The WHERE clause checks the firstName and lastName of the participant in opposition to 2 units of bind parameter values. And it connects these 2 teams of predicates with OR.

19:13:15,557 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model 
    from
        ChessPlayer c1_0 
    the place
        (
            c1_0.firstName=? 
            and c1_0.lastName=?
        ) 
        or (
            c1_0.firstName=? 
            and c1_0.lastName=?
        )
19:13:15,598 INFO  [com.thorben.janssen.TestBlazeCriteria] - Magnus Carlsen
19:13:15,598 INFO  [com.thorben.janssen.TestBlazeCriteria] - Fabiano Caruana

As you’ll be able to see, while you mix the the place, whereAnd, and whereOr strategies with the beforehand talked about strategies to outline predicates, you’ll be able to outline advanced WHERE clauses. Blaze Persistence even helps extra superior predicates than the JPA specification does. However these are a subject for one more tutorial.

Defining a FROM clause

Once I confirmed you the right way to outline a fundamental question, I discussed that Blaze Persistence generates a default FROM clause. It makes use of the entity class you referenced when calling the create methodology.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class).getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

So, on this instance, Blaze Persistence makes use of a default FROM clause based mostly on the ChessPlayer entity class. You may explicitly outline the foundation of your FROM clause by calling the from methodology on the CriteriaBuilder. In that case, the entity class you reference within the create methodology solely defines the return kind of your question. And the entity class referenced within the from methodology turns into your question root. That methodology additionally permits you to present a 2nd methodology parameter that defines the alias of your question root.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class)
							   .from(ChessPlayer.class, "p")
							   .getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

In case you name the from methodology a number of occasions, Blaze Persistence will add every referenced entity class to the FROM clause. This generates a cross be a part of. Within the following sections, I’ll present you the right way to outline internal and outer joins.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<Tuple> end result = cbf.create(em, Tuple.class)
							   .from(ChessPlayer.class, "p")
							   .from(ChessTournament.class, "t")
							   .choose("p", "particular person")
							   .choose("t", "event")
							   .getResultList();

end result.forEach(r -> log.data(((ChessPlayer)r.get("particular person")).getFirstName() + " " + ((ChessPlayer)r.get("particular person")).getLastName()));

em.getTransaction().commit();
em.shut();

As quickly as your question has greater than 1 root, you additionally have to specify the SELECT clause. You are able to do that by calling the choose methodology.

The first methodology parameter defines the ingredient you need to choose. This could be a reference to an entity, an entity attribute, a database perform, an expression, or an ObjectBuilder. For the scope of this tutorial, I’ll solely use entity and entity attribute references. I’ll clarify Blaze Persistence’s ObjectBuilder in additional element in a future article.

When calling the choose methodology, you’ll be able to present a 2nd methodology parameter to specify an alias. That makes it simpler to reference the chosen ingredient when processing the returned Tuple situations.

Utilizing an implicit be a part of

Just like JPQL, Blaze Persistence additionally helps implicit joins. You outline them by referencing an entity attribute that fashions an affiliation adopted by the trail operator “.” and the identify of an attribute on the related entity. Blaze Persistence then provides a LEFT JOIN clause for that affiliation to the question assertion.

I’m utilizing that within the following instance to outline an implicit be a part of from the ChessTournament to the ChessPlayer entity.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessTournament> tournaments = cbf.create(em, ChessTournament.class)
										.the place("gamers.firstName").eq("Fabiano")
											.the place("gamers.lastName").eq("Caruana")
										.getResultList();

tournaments.forEach(t -> log.data(t.getName()));

em.getTransaction().commit();
em.shut();

The WHERE clause of my question incorporates 2 implicit joins utilizing the identical affiliation. Blaze Persistence expects that these are an identical. The generated SQL assertion solely incorporates 1 LEFT JOIN from the ChessPlayer desk to the affiliation desk representing the many-to-many affiliation.

07:20:34,964 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.endDate,
        c1_0.identify,
        c1_0.startDate,
        c1_0.model 
    from
        ChessTournament c1_0 
    left be a part of
        (ChessTournament_ChessPlayer p1_0 
    be a part of
        ChessPlayer p1_1 
            on p1_1.id=p1_0.players_id) 
                on c1_0.id=p1_0.ChessTournament_id 
        the place
            p1_1.firstName=? 
            and p1_1.lastName=?
07:20:34,994 INFO  [com.thorben.janssen.TestBlazeCriteria] - Tata Metal Chess Event 2021

Utilizing an express be a part of

Along with the implicit joins, Blaze Persistence affords a number of variations of the innerJoin, leftJoin, and rightJoin strategies to outline internal, left, and proper be a part of clauses. You may even outline lateral joins and joins to subselects. Each are options not supported by JPQL and are out of scope for this fundamental introduction to Blaze Persistence’s question options.

Right here you’ll be able to see a easy instance that explicitly defines an internal be a part of from the ChessTournament to the ChessPlayer entity class. It additionally defines p because the alias of the joined ChessPlayer, which makes it simpler to reference the joined entity within the WHERE clause. By default, Blaze Persistence makes use of the lowercase model of the entity’s identify because the alias.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessTournament> tournaments = cbf.create(em, ChessTournament.class)
										.innerJoin("gamers", "p")
										.the place("p.firstName").eq("Fabiano")
											.the place("p.lastName").eq("Caruana")
										.getResultList();

tournaments.forEach(t -> log.data(t.getName()));

em.getTransaction().commit();
em.shut();

When executing this question, Blaze Persistence generates an SQL assertion with the corresponding joins and a WHERE clause that limits the question end result to all tournaments performed by Fabiano Caruana.

07:38:28,951 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.endDate,
        c1_0.identify,
        c1_0.startDate,
        c1_0.model 
    from
        ChessTournament c1_0 
    be a part of
        (ChessTournament_ChessPlayer p1_0 
    be a part of
        ChessPlayer p1_1 
            on p1_1.id=p1_0.players_id) 
                on c1_0.id=p1_0.ChessTournament_id 
        the place
            p1_1.firstName=? 
            and p1_1.lastName=?
07:38:28,992 INFO  [com.thorben.janssen.TestBlazeCriteria] - Tata Metal Chess Event 2021

Defining a JOIN FETCH

Along with the internal and outer joins you understand from SQL, the JPA specification helps a JOIN FETCH clause. It tells the persistence supplier to fetch the referenced affiliation when fetching the chosen entity object. This is likely one of the mostly used options to enhance the efficiency of learn operations, and Blaze Persistence helps it as effectively.

You may outline a JOIN FETCH clause equally to the beforehand described internal and outer joins. The one distinction is that you just now have to name the fetchinnerJoinFetchleftJoinFetch, or rightJoinFetch methodology.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessTournament> tournaments = cbf.create(em, ChessTournament.class)
										.fetch("gamers")
										.innerJoin("gamers", "p")
										.the place("p.firstName").eq("Fabiano")
											.the place("p.lastName").eq("Caruana")
										.getResultList();

tournaments.forEach(t -> log.data(t.getName()));

em.getTransaction().commit();
em.shut();

When doing that, please remember the fact that you need to use an extra be a part of to outline your WHERE clause. In any other case, your question will solely return the related entities that match the predicates of your WHERE clause. This initializes the affiliation incompletely.

I did that within the earlier code pattern. As you’ll be able to see within the log output, the executed SQL assertion joins the affiliation twice. The first be a part of fetches all related ChessPlayers, and the 2nd one limits the question end result to the tournaments by which Fabiano Caruana participated.

10:22:35,027 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.endDate,
        c1_0.identify,
        p1_0.ChessTournament_id,
        p1_1.id,
        p1_1.birthDate,
        p1_1.firstName,
        p1_1.lastName,
        p1_1.model,
        c1_0.startDate,
        c1_0.model 
    from
        ChessTournament c1_0 
    left be a part of
        (ChessTournament_ChessPlayer p1_0 
    be a part of
        ChessPlayer p1_1 
            on p1_1.id=p1_0.players_id) 
                on c1_0.id=p1_0.ChessTournament_id 
        be a part of
            (ChessTournament_ChessPlayer p2_0 
        be a part of
            ChessPlayer p2_1 
                on p2_1.id=p2_0.players_id) 
                    on c1_0.id=p2_0.ChessTournament_id 
            the place
                p2_1.firstName=? 
                and p2_1.lastName=?
10:22:35,070 INFO  [com.thorben.janssen.TestBlazeCriteria] - Tata Metal Chess Event 2021

The very last thing I need to present you on this article is the right way to paginate your question end result. You need to then have all of the information you should begin writing your first queries utilizing Blaze Persistence.

Paginating your question end result

Utilizing SQL, you’ll be able to select between offset pagination and keyset pagination. Blaze Persistence affords an easy-to-use API for each choices.

Most builders are conversant in offset pagination. You merely add a LIMIT and OFFSET clause or a FETCH and OFFSET clause to your question or name the setFirstResult and setMaxResult strategies on JPA’s question interface. Blaze Persistence’s CriteriaBuilder affords the identical strategies.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

Listing<ChessPlayer> gamers = cbf.create(em, ChessPlayer.class)
							   .setFirstResult(2)
							   .setMaxResults(3)
							   .getResultList();

gamers.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

As you’ll be able to see within the log output, calling the setFirstResult and setMaxResults strategies provides an OFFSET and a FETCH clause to the SQL assertion.

10:52:52,061 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model 
    from
        ChessPlayer c1_0 offset ? rows fetch first ? rows solely
10:52:52,112 INFO  [com.thorben.janssen.TestBlazeCriteria] - Anish Giri
10:52:52,113 INFO  [com.thorben.janssen.TestBlazeCriteria] - Fabiano Caruana

When processing that question, the database has to order the end result set first. It then iterates by way of the end result set and skips the variety of information outlined because the OFFSET. It then returns the next information till it reaches the outlined variety of rows. The draw back of this strategy is that the database all the time has to learn and skip the variety of information outlined by the OFFSET. Attributable to that, the question’s efficiency degrades based mostly on the configured OFFSET.

Keyset pagination gives higher efficiency for enormous OFFSET values. As a substitute of telling the database to skip an outlined variety of information, it excludes them from the question end result. This requires a singular order of your end result set and a WHERE clause that filters all information you’ll in any other case need to skip.

In case you’re unfamiliar with keyset pagination, I like to recommend studying this text by Markus Wienand. He explains in nice element how keyset pagination works and why it’s higher than offset pagination.

Utilizing Blaze Persistence, you’ll be able to name the web page methodology to use keyset pagination. It expects the earlier keyset web page and the firstResult and maxResult values you already know from offset pagination. If the earlier keyset web page is null, Blaze Persistence makes use of these values to use offset pagination. In any other case, it makes use of the decrease or higher certain of the earlier web page and the maxResults worth to get the subsequent or earlier web page.

I exploit that within the following instance to iterate by way of the listing of ChessPlayer entities in pages of two. As I discussed earlier, keyset pagination requires a singular order of the question end result. I, due to this fact, known as the orderByAsc methodology to get the ChessPlayers within the ascending order of their id attribute.

EntityManager em = emf.createEntityManager();
em.getTransaction().start();

PagedList<ChessPlayer> players1 = cbf.create(em, ChessPlayer.class)
									 .orderByAsc("id")
									 .web page(null, 0, 2)
									 .getResultList();

players1.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

PagedList<ChessPlayer> players2 = cbf.create(em, ChessPlayer.class)
									 .orderByAsc("id")
									 .web page(players1.getKeysetPage(), 2, 2)
									 .getResultList();

players2.forEach(p -> log.data(p.getFirstName() + " " + p.getLastName()));

em.getTransaction().commit();
em.shut();

Whenever you execute this take a look at case, you’ll be able to see within the log output that the first question used normal offset pagination. The 2nd one used the order standards to exclude all information that we’d in any other case need to skip after which used offset pagination with an offset of 0. So, it returns the primary 2 and avoids skipping any information.

11:31:07,257 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model,
        (choose
            depend(*) 
        from
            ChessPlayer c2_0) 
    from
        ChessPlayer c1_0 
    order by
        c1_0.id asc offset ? rows fetch first ? rows solely
11:31:07,259 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [INTEGER] - [0]
11:31:07,260 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [2]
11:31:07,288 INFO  [com.thorben.janssen.TestBlazeCriteria] - Magnus Carlsen
11:31:07,288 INFO  [com.thorben.janssen.TestBlazeCriteria] - Jorden van Foreest
11:31:07,508 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.birthDate,
        c1_0.firstName,
        c1_0.lastName,
        c1_0.model,
        (choose
            depend(*) 
        from
            ChessPlayer c2_0) 
    from
        ChessPlayer c1_0 
    the place
        (
            ?
        ) < (
            c1_0.id
        ) 
        and 0=0 
    order by
        c1_0.id asc offset ? rows fetch first ? rows solely
11:31:07,509 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [BIGINT] - [2]
11:31:07,509 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [0]
11:31:07,509 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [3] as [INTEGER] - [2]
11:31:07,513 INFO  [com.thorben.janssen.TestBlazeCriteria] - Anish Giri
11:31:07,513 INFO  [com.thorben.janssen.TestBlazeCriteria] - Fabiano Caruana

Utilizing this strategy, it doesn’t matter which a part of the question end result you attempt to fetch. The database by no means has to skip any information, which may drastically enhance the question’s efficiency.

Conclusion

Blaze Persistence gives a Standards API that lets you outline your question dynamically at runtime and is simpler to learn and write than JPA’s Standards API. It additionally extends the question capabilities supplied by Hibernate and JPA. We are going to take a better take a look at that in future articles.

Blaze Persistence makes use of many default values that simplify the definition of a question and scale back the required boilerplate code. Good examples are the usual FROM and SELECT clauses that use the entity reference you supplied when creating the question. You may, in fact, override these defaults in the event that they don’t match the necessities of your use case. This offers you full flexibility to outline the queries you want on your use case.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments