By Christian Beikov on 24 November 2016
Complex data models as can be encountered in nearly every bigger project will at some point require some kind of DTOs to avoid loading the full state of an entity. Depending on how good a mapping of an entity model to DTO model is implemented, the performance and maintainability will vary.
Normally, when implementing a DTO approach you do the following steps
-
Define the structure of what you want to consume e.g. the DTO definition
-
Think of a base query as source for the DTO instances
-
Transform the base query so that it produces the projections needed for the DTOs
-
Do the mapping from the
ResultSet
-like structure to the DTO
Now you might skip 3. and instead of thinking of a base query like you should
in 2. you copy-paste some existing query, adapting only minor things, or write the query especially for that case.
You end up with a lot of boilerplate code and will likely live through some of these episodes
- You try to use the entity model everywhere and try to avoid DTOs because they are a pain
-
This might work at first but it will break with infamous `LazyInitializationException`s sooner or later because you forget to fetch relations for some use cases.
- You try to be smart and use an Object Mapper library like e.g. Dozer
-
That will help you with the boilerplate code for mapping data, but you still have to take care of the fetches otherwise you might run into the N + 1 queries problem.
-
Make the
EntityManager
available for the whole HTTP request to avoid `LazyInitializationException`s a.k.a Open Session in View antipattern -
This will sooner or later lead to the infamous N + 1 queries problem because you lazy load collections somewhere.
Blaze-Persistence Entity Views will handle all of these issues thus increasing performance and maintainability.
Setup
Before you can start, you have to prepare your application. The setup section will guide you through the necessary steps depending on the environment in which you want to use Entity Views.
In order to get started, you need dependencies on the Core module, the Entity View module, the jpa provider integration and optionally the DI framework integration.
Here are the Maven dependencies for Hibernate and CDI.
<!-- Core module --> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-core-api</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-core-impl</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> <!-- Entity View module --> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-api</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-impl</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> <!-- Entity View CDI integration --> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-entity-view-cdi</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> <!-- Hibernate 5.2 integration --> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-hibernate-5.2</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency>
Entities
The entity model that we are going to test with is intentionally simple. A cat with a name, mother and father.
@Entity public class Cat { @Id @GeneratedValue private Integer id; private String name; @ManyToOne(optional = true) private Cat father; @ManyToOne(optional = true) private Cat mother; // getters and setter omitted }
Entity Views
Now comes the interesting part, the Entity View for the entity.
@EntityView(Cat.class) public interface CatView { @IdMapping("id") Integer getId(); String getName(); }
The EntityView
annotation declares on which entity type this Entity View is based on e.g. for which it provides a projection for. It must have an IdMapping
containing the path expression to the attribute that should be used as identifier for this Entity View. Apart from that, you can declare any properties that you would like your Entity View to contain. It’s a DTO after all, so think about the target representation.
The name
attribute declared via getName()
could also use a more complex expression by declaring the expression in @Mapping
, but by default, it uses just the attribute name as expression, so in this case name
.
Entity View Usage
Using the Entity View is probably one of the nicest parts because it can be applied on any base query, as long as you can find an Entity View root for which the projection can de done.
CriteriaBuilder<Cat> cb = criteriaBuilderFactory.create(entityManager, Cat.class); cb.from(Cat.class, "theCat") .where("father").isNotNull() .where("mother").isNotNull(); EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class); List<CatView> list = entityViewManager .applySetting(setting, cb) .getResultList();
The base query in JPQL as defined through cb
would look like the following
SELECT theCat FROM Cat theCat WHERE theCat.father IS NOT NULL AND theCat.mother IS NOT NULL
Applying the Entity View on the CriteriaBuilder
will result in an optimized query to fulfill the projection.
SELECT theCat.id, theCat.name FROM Cat theCat WHERE theCat.father IS NOT NULL AND theCat.mother IS NOT NULL
Although the example is very simple, you can already see that only the relevant attributes are fetched from the database. The benefits are
-
Better performance - since less content has to be fetched and transferred
-
Good reuse - as the projection is transparently applied on an existing base query
-
Less boilerplate - because you don’t have to write the data plumbing yourself
Behind the scenes, the Entity View module generates a simple POJO with final fields that are initialized via a generated constructor.
Conclusion
Although this article only touched the very basics of Entity Views, the benefits already reveal themselves. Mappings in Entity Views are done on the JPQL level which is a big benefit regarding cross RDBMS compatibility.
In an upcoming article I will show you how relations can be mapped as subviews and how that will help you with LazyInitializationException
and N + 1 select problems.
Stay tuned!