By Moritz Becker on 06 March 2018
The latest release of Blaze-Persistence provides an integration with DeltaSpike Data. This enables the use of entity views in CDI-based repositories in a similar fashion as with our already existing Spring Data JPA integration.
Do you enjoy creating your application’s data access layer using the elegance and brevity of repositories as provided by DeltaSpike Data or Spring Data? Then you will be delighted to hear that now you can do so efficiently, fetching only the data you need - and all this in a type-safe manner powered by Blaze-Persistence Entity Views.
If you have no idea what I am talking about you might want to check out DeltaSpike Data or Spring Data JPA first.
The rest of this article focuses on DeltaSpike Data but rest assured that all of what I am presenting here works likewise with Spring Data JPA.
Plain old DeltaSpike Data
Let us begin with a simple example that uses DeltaSpike Data as is.
Example
Assume our well-tried cat family entity model:
@Entity public class Cat { private Long id; private String name; private Cat parent; private Long age; @Id public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @ManyToOne(fetch = FetchType.LAZY) public Cat getParent() { return parent; } public void setParent(Cat parent) { this.parent = parent; } public Long getAge() { return age; } public void setAge(Long age) { this.age = age; } }
Using DeltaSpike Data, it is easy to provide a wide range of basic data access methods for cats:
@Repository public class CatRepository extends FullEntityRepository<Cat, Long> { }
A repository can then be injected and used e.g. inside a JAX-RS resource:
@Path("cats") @RequestScoped public class MyRestResource { @Inject private CatRepository catRepository; @GET @Produces(MediaType.APPLICATION_JSON) public List<Cat> getCats(@QueryParam("offset") Integer offset, @QueryParam("limit") Integer limit) { if (offset == null && limit == null) { return catRepository.findAll(); } else { return catRepository.findAll( offset == null ? 0 : offset, limit == null ? 0 : limit ); } } @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public List<Cat> getCatById(@PathParam("id") Long id) { return catRepository.findBy(id); } }
Awesome! But there are a couple of caveats here.
Not so shiny after all
First, it is important to note that we have marked the parent
property to use lazy loading. If we did not do that, the query may unintentionally load the whole cat relation in case all cats are connected. This is something nobody wants.
But by using lazy loading we introduce a new problem since we rely on automatic JSON serialization in our JAX-RS resource. The serializer will access all entity properties, including the parent
property, causing our persistence provider to attempt to lazily initialize the property. Since our database transaction will be closed at that point, this attempt will fail with a lazy initialization exception.
Even if the database transaction was still open, the serialization process would again end up loading the whole table if the data allows it.
As a result, we are forced to introduce some kind of DTO model class and either manually map our entity properties to DTO properties or use some kind of automatic mapping library like MapStruct or Dozer. This way we get more control over the serialization process and are able to avoid the data loading issues. But it is still not nice. :(
It should be noted that JPA 2.1 EntityGraphs, a feature also supported by DeltaSpike Data, are an option for solving the above issues.
Second, even though our cat entity is quite compact for now there is certainly a lot of data one could store for a cat like weight, fur color, hair color, race, owner - you name it. It generally holds that the more real-world your application becomes, the more data you will need to pack into your entities. But the thing is that you almost never need the complete set of data for a single use-case.
Assume you want to display a simple drop down menu for selecting a cat in your view. Most likely, the cat’s name and its ID is enough information to accomplish this task but using the above JAX-RS resource we would be sending all data fields over the network which is a waste of bandwidth. Even if we decided to reduce the DTO content to only the id and the name, this solves the underlying issue only half-way because we still load all columns from the database and send them over the network only to throw away 90% of it later on.
The road to perfectionism
Let us now see how we can use entity views with DeltaSpike Data to solve all the issues described in the previous section.
We first define an entity view that maps exactly the data that we need for our task.
@EntityView(Cat.class) public interface CatNameView { @IdMapping Long getId(); String getName(); }
The new DeltaSpike Data integration allows us to do the following:
@Repository public class CatNameViewRepository extends FullEntityViewRepository<Cat, CatNameView, Long> { }
Finally, our JAX-RS resource looks like this:
@Path("cats") @RequestScoped public class MyRestResource { @Inject private CatNameViewRepository catNameViewRepository; @GET @Produces(MediaType.APPLICATION_JSON) public List<CatNameView> getCats(@QueryParam("offset") Integer offset, @QueryParam("limit") Integer limit) { if (offset == null && limit == null) { return catNameViewRepository.findAll(); } else { return catNameViewRepository.findAll( offset == null ? 0 : offset, limit == null ? 0 : limit ); } } @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public List<CatNameView> getCatById(@PathParam("id") Long id) { return catNameViewRepository.findBy(id); } }
That’s it. With this solution we manage to
-
Load/transfer only the data that we need - down to the database query level
-
Perform painless DTO mapping without additional dependencies
We may even decide to fetch some fields from each cat’s parent by using a subview mapping without running into the problem of loading the whole table.
The integration adds support for all read operations that DeltaSpike Data supports, including pagination. For example, you can also define your own repository methods using method name patterns and you can also mix the entity view return types within a repository.
@Repository public class CatNameViewRepository extends FullEntityViewRepository<Cat, CatNameView, Long> { CatNameView findAnyByName(String name); CatTableRowView findByName(String name); }
For more details, see the documentation for the Blaze-Persistence DeltaSpike Data integration
Conclusion
This article gave a quick recap of DeltaSpike Data and outlined some common pitfalls that are often overlooked. It then described how these issues can be overcome in a very transparent way using Blaze-Persistence Entity Views via the new DeltaSpike Data integration. While the resulting version of the example program is of the same length as the original, erroneous version, it allows for painless DTO mapping, avoids lazy loading issues and performs better as it optimizes the amount of data queried from the database.
If you liked this article, why not share it and give Blaze-Persistence a try in your next project? In any case, stay tuned for our upcoming releases!