Thursday, May 2, 2024
HomeJavaEntity Views with Blaze Persistence

Entity Views with Blaze Persistence


Blaze Persistence’s Entity Views attempt to resolve a few of the commonest complaints about DTO projections in JPA and Hibernate. Most builders know that DTOs enhance the efficiency of their learn operations. However they usually keep away from utilizing them as a result of it requires boilerplate code and sometimes creates code that’s not simply maintainable. JPA’s DTO help additionally doesn’t present a cushty approach to map lists of related objects. Blaze Persistence’s Entity Views enhance on all this by supporting interface-based DTO definitions and versatile, annotation-based mappings.

To get essentially the most out of this text, you want a minimum of a primary understanding of Blaze Persistence’s Standards API. Should you’re new to Blaze Persistence, you must learn my introduction to Blaze Persistence’s Standards API earlier than you proceed with this text.

Maven dependencies

Earlier than utilizing Blaze Persistence’s Entity Views, you could add the required dependencies.

Entity Views are an addon to Blaze Persistence Core. So, you first want so as to add the core dependencies and the combination along with your JPA implementation. After that, you add the dependencies to Blaze Persistence Entity View.

<! – Blaze Persistence Core – >
<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>

<! – Blaze Persistence Entity Views – >
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-entity-view-api-jakarta</artifactId>
	<model>${model.blaze}</model>
	<scope>compile</scope>
</dependency>
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-entity-view-impl-jakarta</artifactId>
	<model>${model.blaze}</model>
	<scope>runtime</scope>
</dependency>
<dependency>
	<groupId>com.blazebit</groupId>
	<artifactId>blaze-persistence-entity-view-processor</artifactId>
	<model>${model.blaze}</model>
	<scope>supplied</scope>
</dependency>

After you add these dependencies to your mission, you’ll be able to outline your first entity views.

Defining and utilizing a primary Entity View

Much like a DTO object that you should utilize with plain JPA, an entity view is a wrapper for a set of attributes. In distinction to an entity projection, you’ll be able to outline use case-specific entity view projections that solely contains the required info. This may present big efficiency advantages in comparison with a managed entity projection.

The simplest approach to outline an entity view is to create an interface or summary class and annotate it with @EntityView. Every entity view is predicated on an entity class, and you could set a reference to that entity class because the parameter of the @EntityView annotation. Based mostly on that entity reference and the strategy’s title, Blaze Persistence creates a mapping between every getter methodology of the entity view projection and the corresponding entity attribute.

You’ll be able to outline a number of entity view projections for every entity class. That lets you outline use case particular entity views that solely mannequin the required attributes.

Let’s check out a primary instance that solely accommodates the id and title of a participant. The ChessPlayerView interface defines an entity view primarily based on the ChessPlayer entity class. Its getter strategies get mapped to entity attributes with matching names. E.g., the getFirstName methodology of the ChessPlayerView interface will get mapped to the firstName attribute of the ChessPlayer entity class.

@EntityView(ChessPlayer.class)
public interface ChessPlayerView {
    
    @IdMapping
    public Lengthy getId();

    public String getFirstName();

    public String getLastName();
}

An entity view also can embody attributes from related entities or the results of a database perform. The next sections will present you the right way to outline such a mapping. However for now, let’s preserve it easy and focus on the ChessPlayerView entity view.

The earlier code snippet exhibits an @IdMapping annotation. It’s an optionally available annotation that defines which attribute identifies the view object. This lets you use that entity view to be a part of a group mapping and to map collections themselves.

After you’ve gotten outlined your entity view projection, you could register your entity views earlier than you should utilize them with Blaze Persistence’s Standards API. You do this by instantiating an EntityViewConfiguration object and including your entity view definitions.

EntityViewConfiguration cfg = EntityViews.createDefaultConfiguration();
cfg.addEntityView(ChessPlayerView.class);
// add all entity view projections
EntityViewManager evm = cfg.createEntityViewManager(cbf);

After that’s completed, you create an EntityViewManager and use it to use your entity view projection to Blaze Persistence’s standards question. Should you’re not already aware of that API, you must take a look at Blaze Persistence Standards API information.

The question within the following code snippet returns the chess gamers “Fabiano Caruana” and “Magnus Carlsen” as a ChessPlayerView projection. And as you’ll be able to see within the code, the entity view projection and the question are impartial of one another. I first create a CriteriaBuilder for a question that returns ChessPlayer entities and apply the ChessPlayerView entity view to it earlier than executing the question.

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

CriteriaBuilder<ChessPlayer> cb = 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();
CriteriaBuilder<ChessPlayerView> playerViewBuilder = evm.applySetting(EntityViewSetting.create(ChessPlayerView.class), cb);
Listing<ChessPlayerView> playerViews = playerViewBuilder.getResultList();

playerViews.forEach(p -> log.information(p.getFirstName() + " " + p.getLastName()));

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

Once you execute your question, Blaze Persistence adjusts the SELECT clause primarily based in your projection. So, on this instance, it generates a question that solely selects the three attributes mapped by the ChessPlayerView class. These are every participant’s id, first title, and final title. This provides you a similar efficiency advantages as JPA’s DTO projection however primarily based on a extra comfy mapping definition.

12:34:35,966 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.firstName,
        c1_0.lastName 
    from
        ChessPlayer c1_0 
    the place
        (
            c1_0.firstName=? 
            and c1_0.lastName=?
        ) 
        or (
            c1_0.firstName=? 
            and c1_0.lastName=?
        )
12:34:35,966 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [Fabiano]
12:34:35,967 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [VARCHAR] - [Caruana]
12:34:35,967 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [3] as [VARCHAR] - [Magnus]
12:34:35,967 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [4] as [VARCHAR] - [Carlsen]
12:34:35,971 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen
12:34:35,971 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana

The entity view definition used on this instance was very primary. It mapped every getter methodology to an entity attribute with an identical title. That’s adequate for a lot of use instances, however Blaze Persistence’s entity view mapping is rather more versatile.

Aggregating knowledge in an EntityView

You’ll be able to add a @Mapping annotation to the getter methodology to offer a JPQL snippet that Blaze Persistence will use for the mapping. This gives you with nice flexibility. E.g., you should utilize it to reference an attribute with a distinct title or reference an attribute of an related entity class.

Let’s use it in an instance. The ChessGame entity class fashions 2 associations to the ChessPlayer entity. The playerWhite affiliation represents the participant who performed the white items, and the playerBlack affiliation references the participant with the black items.

@Entity
public class ChessGame {

	@Id
    @GeneratedValue(technique = GenerationType.SEQUENCE)
    personal Lengthy id;

    @ManyToOne(fetch = FetchType.LAZY)
    personal ChessPlayer playerWhite;
    
    @ManyToOne(fetch = FetchType.LAZY)
    personal ChessPlayer playerBlack;

    ...
}

If you wish to present a listing of video games in your UI, you probably don’t want your entire ChessGame and the two related ChessPlayer objects. The id of the sport and the names of each gamers ought to be all the knowledge you could present in such a listing.

Utilizing the @Mapping annotation, you’ll be able to simply outline an entity view projection that solely accommodates that info and doesn’t require your persistence layer to fetch any entity objects.

@EntityView(ChessGame.class)
public interface SimpleChessGameView 

As you’ll be able to see within the code snippet, I annotated the getPlayerWhite and getPlayerBlack strategies with @Mapping annotations and supplied 2 JPQL snippets. Every JPQL snippet traverses the to-one associations to a ChessPlayer entity, references their firstName and lastName attributes, and concatenates them to a String.

This solely exhibits a small subset of what you are able to do with a @Mapping annotation. In case your getter methodology returns a group kind, you possibly can even traverse a to-many affiliation to a different entity class and reference one among its attributes. As a rule of thumb, in case your mapping doesn’t match the default habits, you must first attempt fixing it with a @Mapping annotation.

After you’ve gotten outlined that entity projection, you should utilize it in the identical means as within the earlier instance. You want a CriteriaBuilder that returns ChessGame entities, use it to outline your question, and mix it with the SimpleChessGameView entity projection.

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

CriteriaBuilder<ChessGame> cb = cbf.create(em, ChessGame.class);
CriteriaBuilder<SimpleChessGameView> gameViewBuilder = evm.applySetting(EntityViewSetting.create(SimpleChessGameView.class), cb);
Listing<SimpleChessGameView> gameViews = gameViewBuilder.getResultList();

gameViews.forEach(g -> log.information(g.getPlayerWhite() + " - " + g.getPlayerBlack()));

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

Once you execute that question, you’ll be able to see within the log output that Blaze Persistence generated a question that makes use of the two associations to hitch the ChessGame and ChessPlayer tables. It selects the id of the ChessGame and concatenates the firstName and lastName of each gamers.

12:34:17,440 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        p1_0.firstName||' '||p1_0.lastName,
        p2_0.firstName||' '||p2_0.lastName 
    from
        ChessGame c1_0 
    left be part of
        ChessPlayer p1_0 
            on p1_0.id=c1_0.playerBlack_id 
    left be part of
        ChessPlayer p2_0 
            on p2_0.id=c1_0.playerWhite_id
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Magnus Carlsen
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Magnus Carlsen
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen - Anish Giri
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Anish Giri
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Jorden van Foreest
12:34:17,447 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Anish Giri

Mapping complicated varieties

One of the vital widespread complaints about JPA’s DTO mapping is that it solely helps flat knowledge constructions. You’ll be able to’t simply embody a singular attribute of the kind of one other DTO or entity class. And the mapping of assortment varieties is just not supported in any respect.

Blaze Persistence’s entity view mapping gives an enormous enchancment on that. You should utilize entities and entity views as attribute varieties, and you may even map to-many associations to a group of entities or entity views. When doing that, please remember that together with managed entities in your entity view mappings could cause LazyInitializationExceptions, if any of its attributes are fetched lazily.

Let’s take a better take a look at an instance of a to-one and a to-many affiliation to a different entity view projection.

Mapping to-one associations to an entity view projection

Within the earlier instance, we mapped every participant’s first and final title to 1 attribute of the entity view. You might additionally map every participant to the ChessPlayerView projection that I confirmed you within the first instance. You do this by making a ChessGameView entity view projection with the getter strategies ChessPlayerView getPlayerWhite() and ChessPlayerView getPlayerBlack(). The ChessGame entity class defines the attributes playerWhite and playerBlack, which map many-to-one associations to the ChessPlayer entity. Blaze Persistence will map the getter strategies to these attributes and apply the entity view mapping to every of them.

@EntityView(ChessGame.class)
public interface ChessGameView {
    
    @IdMapping
    public Lengthy getId();

    public ChessPlayerView getPlayerWhite();

    public ChessPlayerView getPlayerBlack();
}

Once you then create a question and assign the ChessGameView entity view projection to it, Blaze Persistence will generate a question that solely fetches the required info from the database. As you’ll be able to see within the following log output, that additionally contains the required JOIN clauses to attach the document within the ChessGame desk with the two corresponding data within the ChessPlayer desk.

12:33:44,669 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        c1_0.playerBlack_id,
        p1_0.firstName,
        p1_0.lastName,
        c1_0.playerWhite_id,
        p2_0.firstName,
        p2_0.lastName 
    from
        ChessGame c1_0 
    left be part of
        ChessPlayer p1_0 
            on p1_0.id=c1_0.playerBlack_id 
    left be part of
        ChessPlayer p2_0 
            on p2_0.id=c1_0.playerWhite_id
12:33:44,674 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Magnus Carlsen
12:33:44,674 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Magnus Carlsen
12:33:44,674 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen - Anish Giri
12:33:44,674 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Anish Giri
12:33:44,674 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Jorden van Foreest
12:33:44,675 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Anish Giri

Mapping to-many associations to entity view projections

You’ll be able to map to-many associations in nearly the identical means. You solely want to alter the return kind of your getter strategies to a group kind.

So, an entity view projection for a participant with all video games they performed may seem like this:

@EntityView(ChessPlayer.class)
public interface ChessPlayerWithGamesView 

As you’ll be able to see, mapping the gamesWhite and gamesBlack associations to a Listing of SimpleChessGameView entity view projections doesn’t require any annotations. Based mostly on the strategy’s names, Blaze Persistence finds the corresponding entity attributes and maps every object to a SimpleChessGameView object. And in case your projection doesn’t observe the default mapping conventions, you’ll be able to add a @Mapping annotation to customise it.

Let’s execute the next check case to provide this mapping a attempt.

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

CriteriaBuilder<ChessPlayer> cb = cbf.create(em, ChessPlayer.class);
CriteriaBuilder<ChessPlayerWithGamesView> playerViewBuilder = evm.applySetting(EntityViewSetting.create(ChessPlayerWithGamesView.class), cb);
Listing<ChessPlayerWithGamesView> playerViews = playerViewBuilder.getResultList();

playerViews.forEach(p ->  {
							log.information(p.getName());
							p.getGamesWhite().forEach(g -> log.information(g.getPlayerWhite() + " - " + g.getPlayerBlack()));
							p.getGamesBlack().forEach(g -> log.information(g.getPlayerWhite() + " - " + g.getPlayerBlack()));
						  }
					);

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

As you’ll be able to see within the following log output, Blaze Persistence doesn’t fetch any entities. It as an alternative generates a question that solely fetches the knowledge required by the entity view projection. Although that question may require a number of LEFT JOIN clauses, fetching solely the required info with 1 assertion is normally a lot quicker than executing a number of queries to fetch a graph of entities with all their attributes.

12:42:25,412 DEBUG [org.hibernate.SQL] - 
    choose
        c1_0.id,
        g1_0.id,
        p1_0.firstName||' '||p1_0.lastName,
        p2_0.firstName||' '||p2_0.lastName,
        g2_0.id,
        p3_0.firstName||' '||p3_0.lastName,
        p4_0.firstName||' '||p4_0.lastName,
        c1_0.firstName||' '||c1_0.lastName 
    from
        ChessPlayer c1_0 
    left be part of
        ChessGame g1_0 
            on c1_0.id=g1_0.playerBlack_id 
    left be part of
        ChessPlayer p1_0 
            on p1_0.id=g1_0.playerBlack_id 
    left be part of
        ChessPlayer p2_0 
            on p2_0.id=g1_0.playerWhite_id 
    left be part of
        ChessGame g2_0 
            on c1_0.id=g2_0.playerWhite_id 
    left be part of
        ChessPlayer p3_0 
            on p3_0.id=g2_0.playerBlack_id 
    left be part of
        ChessPlayer p4_0 
            on p4_0.id=g2_0.playerWhite_id
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Magnus Carlsen
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Magnus Carlsen
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Magnus Carlsen - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Jorden van Foreest - Magnus Carlsen
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Jorden van Foreest
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Anish Giri
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Jorden van Foreest
12:42:25,431 INFO  [com.thorben.janssen.TestBlazeEntityView] - Fabiano Caruana - Magnus Carlsen

Abstract

When working with JPA implementations like Hibernate, DTO projections present a lot better efficiency for read-only operations than entities. That’s as a result of they keep away from the administration overhead of entity objects, and you may design them to solely embody the knowledge required by your use case.

Blaze Persistence’s entity view projection improves that in 2 methods:

  1. It presents an annotation-based mapping definition on your DTO lessons. Sure, that’s proper. As you noticed on this article, an entity view is a DTO, not an entity. So, you keep away from any administration overhead, however you can also’t use it to implement write operations.
  2. An entity view projection doesn’t need to be a flat knowledge construction. It might probably map to-one and to-many associations to a number of different entity view projections.

Based mostly on these 2 enhancements, Blaze Persistence’s entity view projections present the identical advantages as JPA’s DTO projections however are rather more versatile and simpler to make use of.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments