2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Blaze Persistence - Entity View Module Christian Beikov, Blazebit – christian@blazebit.com Moritz Becker, Curecomp – moritz.becker@gmx.at 1.6.10-SNAPSHOT Copyright (C) 2014 - 2021 Blazebit Table of Contents Preface System requirements 1. Getting started 1.1. Setup 1.2. Quarkus integration 1.3. Environments 1.4. Supported Java runtimes 1.5. Supported environments/libraries 1.6. First entity view query 1.7. Summary 2. Architecture 2.1. Interfaces 2.2. Core module integration 2.3. Object builder pipeline 2.4. Updatable entity views 3. Mappings 3.1. Mapping types 3.2. Mapping defaults 3.3. Id mappings 3.4. Flat view id mappings 3.5. Basic mappings 3.6. Subview mappings 3.7. Subquery mappings 3.8. Parameter mappings 3.9. Entity mappings 3.10. Collection mappings 3.11. Singular collection type mappings 3.12. Limit mapping 3.13. Correlated mappings 3.14. Correlation mappings via entity array syntax 3.15. Special method attributes 3.16. Mapping expression extensions 3.17. Entity View constructor mapping 3.18. Inheritance mapping 3.19. Inheritance subview mapping 3.20. Using CTEs in entity views 3.21. Secondary entity view roots 4. Fetch strategies 4.1. Join fetch strategy 4.2. Select fetch strategy 4.3. Subselect fetch strategy 4.4. Multiset fetch strategy 5. Filter and Sorter API 5.1. Filter API 5.2. Sorter API 6. Querying and Pagination API 6.1. Querying entity views 6.2. Optional parameters and configuration https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 1/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 6.3. Applying entity views on specific relations 6.4. Fetching a data subset 7. Updatable Entity Views 7.1. Update mapping 7.2. Create mapping 7.3. API usage 7.4. Lifecycle and listeners 7.5. Attribute mappings 7.6. Locking support 7.7. Persist and Update cascading 7.8. Cascading deletes and orphan removal 7.9. Conversion support 8. BasicUserType SPI 8.1. Supported types 8.2. Type support for MULTISET fetching 8.3. Type support for write models 8.4. Type support for JPA managed types 8.5. Optimistic locking version type support 9. TypeConverter API 9.1. Builtin TypeConverters 10. Updatable Entity View Change Model 10.1. Change Model API overview 10.2. Transaction support 10.3. User type support 11. Entity View Builder API 12. Spring Data integration 12.1. Setup 12.2. Features 12.3. Spring Data WebMvc integration 12.4. Spring Data WebFlux integration 13. Spring HATEOAS integration 13.1. Setup 13.2. Features 14. DeltaSpike Data integration 14.1. Setup 14.2. Features 14.3. DeltaSpike Data Rest integration 15. JAX-RS integration 15.1. Setup 15.2. Features 16. GraphQL integration 16.1. Setup 16.2. Usage 16.3. Pagination support 17. Quarkus integration 17.1. Entity view and entity view listener discovery 17.2. CDI support 17.3. Multiple Blaze Persistence instances 17.4. Hot reload 17.5. Configuration properties 17.6. Customization 18. Serialization integration 18.1. Jackson integration 18.2. JSONB integration 19. Metamodel 20. Annotation processor 20.1. Static metamodel 20.2. Static implementation 20.3. Static builder https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 2/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 21. Configuration 21.1. Configuration properties 22. FAQ 22.1. Why do I get an optimistic lock exception when updating an updatable entity view? 22.2. Why do I get a "could not invoke proxy constructor" exception when fetching entity views? Preface JPA applications that use the entity model in every layer often suffer from the infamous LazyInitializationException or N + 1 queries issues. This is mainly due to the use of a too general model for a use case and is often solved by making use of a specialized DTO and adapting queries to that structure. The use of DTOs normally requires adapting many parts of an application and a lot of boilerplate code which is why people tend to do the wrong thing like making use of the open session in view anti-pattern. Apart from lazy loading issues, also the performance suffers due to selecting unnecessary data that a UI is never displaying. Blaze Persistence entity views try to solve these and many more problems a developer faces when having to implement efficient model mapping in a JPA application. It allows to define DTOs as interfaces and provides the mappings to the JPA model via annotations. It favors convention-overconfiguration by providing smart defaults that allow to omit most mappings. By applying DTOs to a query builder through the ObjectBuilder extension point it is possible to separate query logic from the projections while still enjoying high performance queries. System requirements Blaze Persistence entity views require at least Java 1.7 and at least a JPA 2.0 implementation. The entity view module depends on the core module and requires the use of the same versions for both modules. 1. Getting started This is a step-by-step introduction about how to get started with the entity view module of Blaze Persistence. The entity view module requires the Blaze Persistence core so if you have not read the getting started guide for the core yet, you might want to start your reading there. 1.1. Setup As already described in the core module setup, every module depends on the core module. So if you haven’t setup the core module dependencies yet, get back here when you did. To make use of the entity view module, you require all artifacts from the entity-view directory of the distribution. CDI and Spring users can find integrations in integration/entity-view that give a good foundation for configuring for these environments. Spring Data users can find a special integration in integration/entity-view which is described in more detail in a later chapter. This integration depends on all artifacts of the jpa-criteria module. 1.1.1. Maven setup We recommend you introduce a version property for Blaze Persistence which can be used for all artifacts. <properties> <blaze-persistence.version>1.6.10-SNAPSHOT</blaze-persistence.version> </properties> The required dependencies for the entity view module are <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> https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 3/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <scope>runtime</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-api-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-impl-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> Depending on the environment, there are some integrations that help you with configuration Annotation processor The annotation processor will generate static entity view metamodels, static entity view implementations and also static entity view builders. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-processor</artifactId> <version>${blaze-persistence.version}</version> <scope>provided</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-processor-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>provided</scope> </dependency> CDI integration <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-entity-view-cdi</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-entity-view-cdi</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> Spring integration <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-entity-view-spring</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta APIs and Spring 6+ <dependency> <groupId>com.blazebit</groupId> https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 4/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <artifactId>blaze-persistence-integration-entity-view-spring-6.0</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> Spring Data integration When you work with Spring Data you can additionally have first class integration by using the following dependencies. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-2.7</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> For Spring-Data version 2.6, 2.5, 2.4, 2.3, 2.2, 2.1, 2.0 or 1.x use the artifact with the respective suffix 2.6 , 2.5 , 2.4 , 2.3 , 2.2 , 2.1 , 2.0 , 1.x . If you are using Jakarta APIs and Spring Framework 6+ / Spring Boot 3+, use this <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-3.1</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> The Spring Data integration depends on the jpa-criteria module JPA Criteria <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-jpa-criteria-api</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-jpa-criteria-impl</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-jpa-criteria-api-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-jpa-criteria-impl-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> If a JPA provider that does not implement the JPA 2.1 specification like Hibernate 4.2 or OpenJPA is used, the following compatibility dependency is also required. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-jpa-criteria-jpa-2-compatibility</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 5/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Spring HATEOAS integration When you work with Spring HATEOAS you can additionally have first class support for generating keyset pagination aware links by using the following dependency. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-hateoas-webmvc</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta APIs and Spring Framework 6+ / Spring Boot 3+ use <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-hateoas-webmvc-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> More information about the integration can be found in the Spring HATEOAS chapter. 1.2. Quarkus integration To use the Quarkus extension you need to add the following Maven dependency to your Quarkus project: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-quarkus</artifactId> <version>${blaze-persistence.version}</version> </dependency> 1.3. Environments The entity view module of Blaze Persistence is usable in Java EE, Spring as well as in Java SE environments. 1.3.1. Java SE In a Java SE environment the EntityViewConfiguration as well as the EntityViewManager must be created manually as follows: EntityViewConfiguration cfg = EntityViews.createDefaultConfiguration(); cfg.addEntityView(EntityView1.class); // Add some more cfg.addEntityView(EntityViewn.class); EntityViewManager evm = cfg.createEntityViewManager(criteriaBuilderFactory); As you can see, the EntityViewConfiguration is used to register all the entity view classes that you want to make accessible within the an EntityViewManager . You may create multiple EntityViewManager instances with potentially different configurations. 1.3.2. Java EE For usage with CDI the integration module blaze-persistence-integration-entity-view-cdi provides a CDI extension which takes over the task of creating and providing an EntityViewConfiguration from which an EntityViewManager can be created like following example shows. @Singleton // from javax.ejb @Startup // from javax.ejb public class EntityViewManagerProducer { // inject the configuration provided by the cdi integration @Inject private EntityViewConfiguration config; // inject the criteria builder factory which will be used along with the entity view manager @Inject private CriteriaBuilderFactory criteriaBuilderFactory; private EntityViewManager evm; https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 6/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @PostConstruct public void init() { // do some configuration evm = config.createEntityViewManager(criteriaBuilderFactory); } @Produces @ApplicationScoped public EntityViewManager createEntityViewManager() { return evm; } } The CDI extension collects all the entity views classes and provides a producer for the pre-configured EntityViewConfiguration . When deploying a WAR file to an application server running on Java 11+ that doesn’t support MR-JARs, it will be necessary to use a special Java 9+ only artifact: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-impl</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> <!-- Use the 9 classifier to get the Java 9+ only artifact --> <classifier>9</classifier> </dependency> 1.3.3. CDI If EJBs aren’t available, the EntityViewManager can also be configured in a CDI 1.1 specific way similar to the Java EE way. @ApplicationScoped public class EntityViewManagerProducer { // inject the configuration provided by the cdi integration @Inject private EntityViewConfiguration config; // inject the criteria builder factory which will be used along with the entity view manager @Inject private CriteriaBuilderFactory criteriaBuilderFactory; private volatile EntityViewManager evm; public void init(@Observes @Initialized(ApplicationScoped.class) Object init) { // no-op to force eager initialization } @PostConstruct public void init() { // do some configuration evm = config.createEntityViewManager(criteriaBuilderFactory); } @Produces @ApplicationScoped public EntityViewManager createEntityViewManager() { return evm; } } 1.3.4. Spring You have to enable the Spring entity-views integration via annotation based config or XML based config and you can also mix those two types of configuration: Annotation Config @Configuration @EnableEntityViews("my.entityviews.base.package") public class AppConfig { } XML Config https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 7/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ev="http://persistence.blazebit.com/view/spring" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://persistence.blazebit.com/view/spring http://persistence.blazebit.com/view/spring/spring-entity-views-1.2.xsd"> <ev:entity-views base-package="my.entityviews.base.package"/> </beans> The Spring integration collects all the entity views classes in the specified base-package and provides the pre-configured EntityViewConfiguration for injection. This configuration is then used to create a EntityViewManager which should be provided as bean. @Configuration public class BlazePersistenceConfiguration { @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @Lazy(false) // inject the criteria builder factory which will be used along with the entity view manager public EntityViewManager createEntityViewManager(CriteriaBuilderFactory cbf, EntityViewConfiguration entityViewConfiguration) { return entityViewConfiguration.createEntityViewManager(cbf); } } 1.4. Supported Java runtimes The entity view module like all other modules generally follows what has already been stated in the core moduel documentation. Automatic module names for modules. Module Automatic module name Entity View API com.blazebit.persistence.view Entity View Impl com.blazebit.persistence.view.impl 1.5. Supported environments/libraries Generally, we support the usage in Java EE 6+ or Spring 4+ applications. The following table outlines the supported library versions for the integrations. Module Automatic module name Minimum version Supported versions CDI integration com.blazebit.persistence.integration.view.cdi CDI 1.0 1.0 - 1.2, 2.0, 3.0 Spring integration com.blazebit.persistence.integration.view.spring Spring 4.3 4.3, 5.0 - 5.3, 6.0 DeltaSpike Data integration com.blazebit.persistence.integration.deltaspike.data DeltaSpike 1.7 1.7 - 1.9 Spring Data integration com.blazebit.persistence.integration.spring.data Spring Data 1.11 1.11, 2.0 - 2.7, 3.1 Spring Data Rest integration com.blazebit.persistence.integration.spring.data.rest Spring Data 1.11, Spring Spring Data 1.11 + Spring MVC 4.3 MVC 4.3, Spring Data 2.0 2.7 + Spring MVC 5.0 - 5.3, Spring Data 3.1 + Spring MVC 6.0 1.6. First entity view query This section is supposed to give you a first feeling of how to use entity views. For more detailed information, please see the subsequent chapters. In the following we suppose cbf , em and evm to refer to an instance of CriteriaBuilderFactory , JPA’s EntityManager and EntityViewManager , respectively. Take a look at the environments chapter for how to obtain an EntityViewManager . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 8/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module An entity view can be thought of as the ORM world’s dual to a database table view. It enables the user to query just a subset of an entity’s fields. This enables developers to only query what they actually need for their current use case, thereby reducing network traffic and improving performance. Let’s start with a very simple example. Assume that in our application we want to display a list of the names of all the cats in our database. Using entity views we would first define a new view for this purpose: @EntityView(Cat.class) public interface CatNameView { @IdMapping public Long getId(); public String getName(); } The usage of the CatNameView could look like this: CriteriaBuilder<Cat> cb = cbf.create(em, Cat.class); CriteriaBuilder<CatNameView> catNameBuilder = evm.applySetting(EntityViewSetting.create(CatNameView.class), cb); List<CatNameView> catNameViews = catNameBuilder.getResultList(); Of course, you can apply further restrictions to your query by CriteriaBuilder means. E.g. you could avoid duplicate names in the above example by calling groupBy() on the CriteriaBuilder at any point after its creation. By default the abstract getter methods in the view definition map to same named entity fields. So the getName() getter in the above example actually triggers querying of the name field. If we want to use a different name for the getter method we would have to add an additional @Mapping annotation: @EntityView(Cat.class) public interface CatNameView { @IdMapping public Long getId(); @Mapping("name") public String getCatName(); } Of course, it is also possible to combine various views via inheritance. @EntityView(Cat.class) public interface CatKittens { @IdMapping public Long getId(); public List<Kitten> getKittens(); } @EntityView(Cat.class) public interface CatNameView { @IdMapping public Long getId(); @Mapping("name") public String getCatName(); } @EntityView(Cat.class) public interface CombinedView extends CatKittens, CatNameView { @Mapping("SIZE(kittens)") public Integer getKittenSize(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 9/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module An entity view does not have to be an interface, it can be any class. Moreover you can see that it is possible to use whole expressions inside the @Mapping annotations. The allowed expression will be covered in more detail in subsequent chapters. Another useful feature are subviews which is illustrated in following example. @EntityView(Landlord.class) public interface LandlordView { @IdMapping public Long getId(); public String getName(); public Integer getAge(); @Mapping("ownedProperties") public PropertyAddressView getHouses(); } @EntityView(Property.class) public interface PropertyAddressView { @IdMapping public Long getId(); public String getAddress(); } The last feature we are going to cover here are filters and sorters in conjunction with EntityViewSetting which allows the dynamic configuration of filters and sorters on your entity view and are also usable together with pagination. This makes them an ideal fit whenever you need to query data for display in a filterable and/or sortable data table. Following example illustrates how this looks like: @EntityView(Cat.class) @ViewFilters({ @ViewFilter(name = "customFilter", value = FilteredDocument.CustomFilter.class) }) public interface FilteredCatView { @AttributeFilter(ContainsFilter.class) public String getName(); public static class CustomFilter extends ViewFilterProvider { @Override public <T extends WhereBuilder<T>> T apply(T whereBuilder) { return whereBuilder.where("doctor.name").like().expression("Julia%").noEscape(); } } } In this example we once again define a view on our Cat entity and select the cat’s name only. But in addition we applied a filter on the name attribute. In this case we chose the ContainsFilter , one of the predefined filters. We also defined a custom filter where we check whether the cat’s doctor’s name starts with the string Julia. The next code snippet shows how we dynamically set the actual filter value by which the query should filter and how we paginate the resulting query. // Base setting EntityViewSetting<FilteredCatView, PaginatedCriteriaBuilder<FilteredCatView>> setting = EntityViewSetting.create(FilteredCatView.class, 0, 10); // Query CriteriaBuilder<Cat> cb = cbf.create(em, Cat.class); setting.addAttributeFilter("name", "Kitty"); PaginatedCriteriaBuilder<FilteredCatView> paginatedCb = evm.applySetting(setting, cb); PagedList<FilteredCatView> result = paginatedCb.getResultList(); 1.7. Summary If you want to go into more detail, you are now ready to discover the other chapters of the documentation or the API yourself. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 10/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 2. Architecture This is just a high level view for those that are interested about how Blaze Persistence entity view works. 2.1. Interfaces A quick overview that presents the interfaces that are essential for users and how they are related. Since entity views are mostly annotation driven and are about mapping attributes to entity attributes, there are not that many interfaces. The two most important ones are the EntityViewManager and the EntityViewSetting . A EntityViewManager is built once on startup during which it analyzes and validates the configured entity views. It is responsible for building implementations for the interfaces and abstract classes from the metamodel and caching object builder instances for entity views. The EntityViewSetting is a configuration that can be applied on a query builder through an EntityViewManager and contains information about The entity view Pagination Filters and sorters Parameters and properties 2.2. Core module integration The entity view module builds on top of the ObjectBuilder integration point offered by query builders of the core module. Every entity view is translated into a ObjectBuilder which is then applied on a query builder. 2.3. Object builder pipeline During startup the metamodel is built which is then used for building an object builder pipeline. For every entity view interface/class a ObjectBuilder template called ViewTypeObjectBuilderTemplate is created which is cached. From these templates a normal ObjectBuilder is built that can be applied on any query builder. Depending on the features a entity view uses, the resulting object builder might actually be a object builder pipeline i.e. it invokes multiple object builders in an ordered manner on tuples. In general, a object builder for an entity view just takes in the tuple and passes it to the constructor of the entity view implementation. As soon as subviews or collections are involved, it becomes a pipeline. The pipeline has two different forms, the abstract form represented by TupleTransformatorFactory and the concrete form TupleTransformator . When a object builder is created from a template, the concrete form is created from the abstract one which might involve building object builders for subviews. Every collection introduces a new transformation level i.e. elements of a collection must be materialized before the collection can be materialized. So the result is processed from the leafs(i.e. the elements) upwards(i.e. a collection) until objects of the target entity view type are materialized. 2.4. Updatable entity views Updatable entity views are still in flux and are not yet fully thought through, but here comes the essential idea. Similar to the object builder pipeline, a EntityViewUpdater is composed of several possibly nested attribute flushers. A EntityViewUpdater is built once and is responsible for flushing dirty attributes to the persistence context. After flushing, attributes are considered to be non-dirty but they can become dirty again either through a change or a transaction rollback. Dirty tracking is done either by remembering the initial state and comparing with changed state or not at all. Collections are tracked by using custom collection implementations that do action recording which is then replayed onto a collection of an entity reference. 3. Mappings As already mentioned in the Getting started section, the entity view module builds up on the core module. Some of the basics like implicit joins and the basic expression structure should be known to understand all of the following mapping examples. Entity views are to entities in ORM, what table views are to tables in an RDBMS. They represent projections on the entity model. In a sense you can say that entity views are DTOs 2.0 or DTOs done right. One of the unique features of entity views is that it only imposes a structure and the projections, but the base query defines the data source. Blaze Persistence tried to reduce as much of the boilerplate as possible for defining the structure and the projections by employing a convention over https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 11/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module configuration approach. The result of these efforts is that entity views are defined as interfaces or abstract classes mostly containing just getter definitions that serve as attribute definitions. To declare that an interface or an abstract class as entity view, you have to annotate it and specify the entity class for which this entity view provides projections. @EntityView(Cat.class) interface CatView { ... } So an entity view can be seen as a named wrapper for a bunch of attributes, where every attribute has some kind of mapping that is based on the attributes the entity type offers. An attribute is declared by defining a public abstract method in an entity view i.e. every abstract method is considered to be an attribute. @EntityView(Cat.class) interface CatView { String getName(); } Since every method of an interface is abstract and public, you can omit the abstract and public keywords. In this simple example you can see that the CatView has an attribute named name . The implicit mapping for the attribute is the attribute name itself, so name . This means that the entity view attribute name declared by the abstract method getName() is mapped to the entity attribute name . Since entity views and their mappings are validated during startup against the entity model, you should see any mapping related runtime errors and can be sure it works if it doesn’t fail to start One of the nice things about using interfaces is that you can have multiple inheritance. If you separate concerns in separate feature interfaces, you can effectively make use of multiple inheritance. interface NameView { String getName(); } interface AgeView { Long getAge(); } @EntityView(Cat.class) interface CatView extends NameView, AgeView { } In this example CatView has two attributes, name and age . Even though the interfaces are not entity views, they could have custom mappings. 3.1. Mapping types So far, you have mostly seen basic attribute mappings in entity views, but there is actually support for far many mapping types. Basic mappings - maps basic attributes from entities into entity views Subview mappings - maps a *ToOne relation of an entity to an entity view Flat view mappings - maps an embeddable or association of an entity to a flat entity view Subquery mappings - maps the result of a subquery to a basic attribute into entity views Parameter mappings - maps named query parameters into an entity view Entity mappings - maps *ToOne or *ToMany relations of an entity as is into an entity view Collection mappings - maps *ToMany relations of an entity into an entity view with support for basic, subview and embeddable types Correlated mappings - correlates an entity type by some key and maps it or an attribute of it into an entity view as subview or basic type respectively In general we do not recommend to make extensive use of entity mappings as it defeats the purpose of entity views and can lead to lazy loading issues Apart from mapping attributes, it is also possible have a constructor and map parameters when using an abstract class. One of the biggest use cases for this is for doing further transformations on the data that can’t be pushed to the DBMS like e.g. money formatting. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 12/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 3.2. Mapping defaults As mentioned before, the entity view module implements a convention over configuration approach and thus has some smart defaults for mappings. Whenever an attribute(getter method) without a mapping annotation is encountered, a default mapping to the same named entity attribute will be created. If there is none, it will obviously report an error. 3.3. Id mappings Id mappings declare that an attribute represents the identifier i.e. can be used to uniquely identify an entity view object. The id mapping is declared by annotating the desired attribute with @IdMapping and optionally specifying the mapping path. Having an id attribute allows an entity view to map collections, be mapped in collections and gives an entity view object a meaningful identity. If an entity view has no id mapping, it is considered to be a flat view which probably only makes sense for scalar results or embedded objects. It is generally recommended to always declare an id mapping if possible. When an id mapping is present, the generated entity view implementation’s equals-hashCode implementation will be based on it. Otherwise it will consider all attributes in the equals-hashCode implementation. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); } 3.4. Flat view id mappings A flat view id mapping is given when the type of the id attribute is a flat view type. This is the case when the view type has no id declared. It’s very similar to subview mappings and is mostly used when working with JPA embeddable types. Imagine the following model for illustration purposes. @EntityView(Cat.class) interface CatIdView { String getName(); } @EntityView(Cat.class) interface CatView { @IdMapping("this") CatIdView getId(); } This example already makes use of many concepts. It declares the CatIdView as flat view with a basic mapping and the CatView with a flat view id. The mapping for the flat view id in CatView uses to the this expression extension to allow the flat view to be based on the same entity that is backing the CatView . Since flat view types will consider all attributes in the equals-hashCode implementation, the type shouldn’t contain unnecessary attributes if possible. 3.5. Basic mappings A basic mapping is declared by annotating the desired attribute with @Mapping and specifying the mapping expression. An attribute that has no mapping annotations is only considered to have a basic mapping if it is of a basic type like e.g. Integer. Without a mapping annotation, the default mapping rules apply. In general, every non-collection and non-managed type is considered to be basic. Managed types are JPA managed types and entity view types. Although most example only use path expressions for the mapping, it is actually allowed to use any scalar expression that JPQL or Blaze Persistence allows. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @Mapping("UPPER(name)") String getUpperName(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 13/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module As you might expect, the expression UPPER(name) will upper-case the name, so getUpperName() will return the upper-cased name. Applying such an entity view on a simple query builder will show what happens behind the scenes. List<CatView> result = evm.applySetting( EntityViewSetting.create(CatView.class), cbf.create(em, Cat.class) ).getResultList(); SELECT cat.id, UPPER(cat.name) FROM Cat cat The expression in the mapping ends up as select item in the query just as expected. 3.6. Subview mappings Subview and embeddable view mappings are similar to basic mappings in the sense that the same rules apply, except for the allowed mappings. Since these mappings get their data from objects of managed types, only path expressions are allowed for their mappings. Path expressions can have arbitrary depth i.e. multiple de-references like relation.subRelation.otherRelation and path elements can be of the following types: Simple path elements that refer to entity type attributes TREAT expression like TREAT(..).subRelation Qualified expression like KEY(..).subRelation Array expression like relation[:param].subRelation A subview mapping is given when the type of the attribute is a entity view type. Since a entity view is always declared for a specific entity type, the target type of the subview mapping and the entity view’s entity type must be compatible. This means that you could apply a AnimalView to a Cat if it extends Animal but can’t apply a PersonView since it’s not compatible i.e. Cat is not a subtype of Person . @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); String getName(); } @EntityView(Animal.class) interface AnimalView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); AnimalView getFather(); } As you might imagine, the CatView will additionally select attributes of the father relation since they are requested by the AnimalView . In order to understand the following generated query, you should know what an implicit join does and how entity views make use of such implicit joins. Behind the scenes, the entity views runtime will apply a select on the criteria builder for the expressions cat.id , father.id and father.name . The expression father.name accesses an entity attribute is only accessible when actually joining the relation. This is why an implicit/default join is generated for the father relation. SELECT cat.id, father_1.id, father_1.name FROM Cat cat LEFT JOIN cat.father father_1 Since the father relation is optional or nullable, a (default) left join is created due to the rules of model awareness in implicit joins. This is a perfect fit for entity views as the subview object will be simply null if a cat has no father. If the implicit join worked like JPQL defines it, an inner join would have to be created. An inner join would mean that cats without a father would get filtered out which is an undesirable effect since we only want a projection on top of a base query. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 14/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Subviews can in turn have subviews again, so there is no limitation regarding the depth. The only requirement is that there is no cycle. 3.6.1. Flat view mappings A flat view mapping is given when the type of the attribute is a flat view type. This is the case when the entity view has no id declared. It’s very similar to subview mappings and is mostly used when working with JPA embeddable types. Note that a flat view can be used like a normal view except when it is used as view root i.e. the flat view is the entity view type used in EntityViewSetting , it is embedded in a flat view which in turn is the view root i.e. the parent is a flat view that is used in EntityViewSetting or it is used as subview for a non-indexed collection then the flat view can’t have collection attributes with fetch strategy JOIN . The reason is that the elements of the collection can’t be matched with the flat view as it has no identity it can use for matching. Imagine the following model for illustration purposes. @Embeddable class Name { String firstName; String lastName; } @Entity class Person { @Id @GeneratedValue Long id; @Embedded Name name; } @EntityView(Name.class) interface SimpleNameView { String getFirstName(); } @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); SimpleNameView getName(); } Applying a PersonView would produce a query like SELECT person.id, person.name.firstName FROM Person person Such a flat view can also be used with the this expression which is similar to JPAs @Embedded . A limitation in Hibernate actually requires the use of flat entity views for mapping of element collections i.e. you can map the element collection 1:1 to the entity view. Flat views for singular attributes are by default always created, even if all attributes of a flat view are null i.e. the flat view is empty. This can be overridden by annotating the attribute with @EmptyFlatViewCreation(false) or globally by specifying the configuration option CREATE_EMPTY_FLAT_VIEWS. When empty flat view creation is disabled, the attribute value will be set to null instead of an empty flat view. 3.7. Subquery mappings Subquery mappings allow to map scalar subqueries into entity views and are declared by annotating the desired attribute with @MappingSubquery and specifying a SubqueryProvider . The following example should illustrate the usage: @EntityView(Cat.class) interface CatView { @IdMapping https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 15/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Long getId(); @MappingSubquery(KittenCountSubqueryProvider.class) Long getKittenCount(); class KittenCountSubqueryProvider implements SubqueryProvider { @Override public <T> T createSubquery(SubqueryInitiator<T> subqueryBuilder) { return subqueryBuilder.from(Cat.class, "subCat") .select("COUNT(*)") .whereOr() .where("subCat.father.id").eqExpression("EMBEDDING_VIEW(id)") .where("subCat.mother.id").eqExpression("EMBEDDING_VIEW(id)") .endOr() .end(); } } } This entity view already comes into contact with the core API for creating subqueries. It produces just what it defines, a subquery in the select clause. SELECT cat.id, ( SELECT COUNT(*) FROM Cat subCat WHERE subCat.father.id = cat.id OR subCat.mother.id = cat.id ) FROM Cat cat In the subquery provider before you saw the usage of EMBEDDING_VIEW which is gone in the final query. This is because EMBEDDING_VIEW is a way to refer to attributes of the relation of the entity view into which the subquery is embedded without having to refer to the concrete the query alias. For more information on this check out the documentation of the EMBEDDING_VIEW function The subquery was just used for illustration purposes and could be replaced with a basic mapping SIZE(kittens) which would also generate a more efficient query. 3.8. Parameter mappings A parameter mapping is a convenient way to inject the values of query parameters or optional parameters into instances of an entity view. Introducing a parameter mapping with @MappingParameter will introduce a fake select item. If a parameter is not used in a query, NULL will be injected into the entity view. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @MappingParameter("myParam") String getMyParam(); } SELECT cat.id, NULLIF(1,1) FROM Cat cat Parameter mappings are probably most useful in constructor mappings where they can be used for some transformation logic. Optional parameters can be configured globally through setOptionalParameter() or for a specific use case through addOptionalParameter() . 3.9. Entity mappings Apart from having custom projections for entity or embeddable types through subviews, you can also map the JPA managed types directly. You can use the @Mapping annotation if desired and map any path expression as singular or plural attribute(i.e. collection) with managed types. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 16/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Cat getFather(); } SELECT cat.id, father_1 FROM Cat cat LEFT JOIN cat.father father_1 Beware that when using managed types directly, you might run into lazy loading issues when accessing uninitialized/un-fetched properties of the entity. You can however specify what properties should be fetched for such entity mappings by using the fetches configuration. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @Mapping(fetches = "kittens") Cat getFather(); } This will fetch the kittens of the father. SELECT cat.id, father_1 FROM Cat cat LEFT JOIN cat.father father_1 LEFT JOIN FETCH father_1.kittens kittens_1 3.10. Collection mappings One of the most important features of the Blaze Persistence entity view module is the possibility to map collections. You can map collections defined in the entity model to collections in the entity view model in multiple ways. 3.10.1. Simple 1:1 collection mapping The simplest possible mapping is a 1:1 mapping of e.g. a *ToMany collection. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); Set<Cat> getKittens(); } This will simply join the kittens collection. During entity view construction the elements are collected and the result is flattened as expected. SELECT cat.id, kittens_1 FROM Cat cat LEFT JOIN cat.kittens kittens_1 3.10.2. Subset basic collection mapping Most of the time, only a subset of the properties of a relation is needed. In case only a single property is required, the use of @Mapping to refer to the property within a collection can be used. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @Mapping("kittens.name") Set<String> getKittenNames(); } This will join the kittens collection and only select their name . SELECT cat.id, kittens_1.name FROM Cat cat https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 17/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module LEFT JOIN cat.kittens kittens_1 3.10.3. Subview collection mapping For the cases when multiple properties of a relation are needed, you can also use subviews. @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { Set<SimpleCatView> getKittens(); } Applying the CatView entity view will again join the kittens collection but this time select some more properties. SELECT cat.id, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 A subview within a collection can have subviews and collections of subviews again i.e. there is no limit to nesting. 3.10.4. Collection type re-mapping Another nice feature of Blaze Persistence entity views is the ability to re-map a collection to a different collection type. In the entity model one might for example choose to always use a java.util.Set for mapping collections, but to be able to make use of the elements in a UI, you might require e.g. a java.util.List . Although the kittens relation in the Cat entity uses a Set , you can map the kittens as List in the CatView . As you might expect, the order of the elements will then depend on the order of the query result. @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { List<SimpleCatView> getKittens(); } By executing the query with a custom ORDER BY clause, the result order can be made deterministic. List<CatView> result = entityViewManager.applySetting( EntityViewSetting.create(CatView.class), cb.create(Cat.class) .orderByAsc("name") .orderByAsc("kittens.name") ).getResultList(); SELECT cat.id, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 ORDER BY cat.name ASC NULLS LAST, kittens_1.name ASC NULLS LAST We do not recommend to rely on this behavior but instead make use of sorted collection mappings. 3.10.5. Ordered collection mapping https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 18/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Apart from changing the collection type to e.g. List it is also possible to get ordered results with sets. By specifying ordered = true for the collection via the annotation @CollectionMapping you can force a set implementation that retains the insertion order like a LinkedHashSet . @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { @CollectionMapping(ordered = true) Set<SimpleCatView> getKittens(); } The query doesn’t change, the only thing that does, is the implementation for the collection. SELECT cat.id, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 This oviously only makes sense when used along with an ORDER BY clause that orders the result set deterministically. 3.10.6. Sorted collection mapping In addition to ordering, the following sorted collection types are supported SortedSet and NavigableSet SortedMap and NavigableMap You can specify the comparator for the collection via the annotation @CollectionMapping @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); static class DefaultComparator implements Comparator<SimpleCatView> { @Override public int compare(SimpleCatView o1, SimpleCatView o2) { return String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()); } } } @EntityView(Cat.class) interface CatView extends SimpleCatView { @CollectionMapping(comparator = SimpleCatView.DefaultComparator.class) SortedSet<SimpleCatView> getKittens(); } This will ensure the correct ordering of the collection elements regardless of the query ordering. The query stays the same. SELECT cat.id, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 3.10.7. Indexed collection re-mapping Mapping an indexed collection like a java.util.Map or java.util.List with an @OrderColumn can happen in multiple ways. Let’s consider the following model. @Entity class Cat { @Id https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 19/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Long id; @OneToMany @OrderColumn List<Cat> indexedKittens; @ManyToMany Map<Cat, Cat> kittensBestFriends; } @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } Indexed mapping One way is to map the collections indexed again, i.e. a Map in the entity is mapped as Map in the entity view. @EntityView(Cat.class) interface CatView extends SimpleCatView { List<SimpleCatView> getIndexedKittens(); Map<SimpleCatView, SimpleCatView> getKittensBestFriends(); 1 } 1 Careful when mapping the key to a subview. This is only supported in the latest JPA provider versions SELECT cat.id, cat.name, INDEX(indexedKittens_1), indexedKittens_1.id, indexedKittens_1.name KEY(kittensBestFriends_1).id, KEY(kittensBestFriends_1).name, kittensBestFriends_1.id, kittensBestFriends_1.name FROM Cat cat LEFT JOIN cat.indexedKittens indexedKittens_1 LEFT JOIN cat.kittensBestFriends kittensBestFriends_1 Map-Key only mapping By using the qualified expression KEY() you can map the keys of a map to a collection by using @Mapping @EntityView(Cat.class) interface CatView extends SimpleCatView { @Mapping("KEY(kittensBestFriends)") List<SimpleCatView> getKittens(); } SELECT cat.id, cat.name, KEY(kittensBestFriends_1).id, KEY(kittensBestFriends_1).name FROM Cat cat LEFT JOIN cat.kittensBestFriends kittensBestFriends_1 Map-Value only mapping Simply mapping a path expression for a Map to a normal collection, will result in only fetching the map values. @EntityView(Cat.class) interface CatView extends SimpleCatView { @Mapping("kittensBestFriends") List<SimpleCatView> getBestFriends(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 20/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module SELECT cat.id, cat.name, kittensBestFriends_1.id, kittensBestFriends_1.name FROM Cat cat LEFT JOIN cat.kittensBestFriends kittensBestFriends_1 List-Value only mapping Sometimes it might be required to ignore the index of an indexed List when mapping it to a List again. To do so use ignoreIndex on @CollectionMapping @EntityView(Cat.class) interface CatView extends SimpleCatView { @Mapping("indexedKittens") @CollectionMapping(ignoreIndex = true) List<SimpleCatView> getKittens(); } SELECT cat.id, cat.name, indexedKittens_1.id, indexedKittens_1.name FROM Cat cat LEFT JOIN cat.indexedKittens indexedKittens_1 3.10.8. Custom indexed collection mapping Mapping an indexed collection like a java.util.Map or java.util.List in entity views does not necessarily require that the source collection must be of the same type. A custom index mapping can be specified by annotating the attribute with @MappingIndex . The index mapping is relative to the target mapping. Let’s consider the following model. @Entity class Cat { @Id Long id; int age; @OneToMany Set<Cat> kittens; } @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } Indexed-List mapping An indexed List can be mapped by specifying a @MappingIndex that resolves to a 0-based integer of the target mapping. @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("age") @Mapping("kittens") List<SimpleCatView> getKittensByAge(); } SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 Note that depending on the age values, there can be many null entries in the list. An indexed List is filled up with null entries for missing indexes. Map mapping https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 21/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module An Map indexed by some value can be mapped by specifying a @MappingIndex relative to the target mapping. @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("age") @Mapping("kittens") Map<Integer, SimpleCatView> getKittensByAge(); } SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 Since the age might not be unique for the kittens in a collection, some cats could be overwritten, which is prevented by throwing an exception. To avoid the exception and instead collect all kittens grouped by the index value, multi-collections can be used. Multi-collection mapping An indexed List or Map can specify a collection value to collect all values grouped by their index value. Valid types for the collections are Collection , Set , SortedSet and List . @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("age") @Mapping("kittens") Map<Integer, Set<SimpleCatView>> getKittensByAge(); } SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 Subview key mapping An indexed Map can specify a subview as key as well. Note how the this mapping is used for the index mapping which allows the key view to be based on the target mapping kittens . @EntityView(Cat.class) interface CatAgeView { int getAge(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("this") @Mapping("kittens") Map<CatAgeView, Set<SimpleCatView>> getKittensByAge(); } SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 22/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Ordered element collection mapping By specifying ordered = true for the element collection via the annotation @MultiCollectionMapping you can force a set implementation that retains the insertion order like a LinkedHashSet . @EntityView(Cat.class) interface CatAgeView { int getAge(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("this") @Mapping("kittens") @MultiCollectionMapping(ordered = true) Map<CatAgeView, Set<SimpleCatView>> getKittensByAge(); } The query doesn’t change, the only thing that does, is the implementation for the collection. SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 This oviously only makes sense when used along with an ORDER BY clause that orders the result set deterministically. Sorted element collection mapping You can specify the comparator for the collection via the annotation @MultiCollectionMapping @EntityView(Cat.class) interface CatAgeView { int getAge(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { @MappingIndex("this") @Mapping("kittens") @MultiCollectionMapping(comparator = SimpleCatView.DefaultComparator.class) Map<CatAgeView, Set<SimpleCatView>> getKittensByAge(); } This will ensure the correct ordering of the element collection elements regardless of the query ordering. The query stays the same. SELECT cat.id, cat.name, kittens_1.age, kittens_1.id, kittens_1.name FROM Cat cat LEFT JOIN cat.kittens kittens_1 3.11. Singular collection type mappings There are cases when the entity model defines a collection that is actually a singular entity attribute. This can happen when you use custom type implementations or JPA 2.1 attribute converters that produce collections. A custom type or converter could map a DBMS array, json, xml or any other type to a collection. Since such an entity attribute is not a relation, it can only be a singular attribute. By default Blaze Persistence entity views assume that an entity view attribute with a collection type is a plural attribute and the mapping refers to a plural entity attribute. In order to be able to map such special singular attribute collections, you have to specifically use @MappingSingular . @Entity class Cat { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 23/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @Id Long id; @Basic @Convert(converter = StringSetConverter.class) Set<String> tags; } class StringSetConverter implements AttributeConverter<String, Set<String>> { ... } @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @MappingSingular Set<String> getTags(); } Beware that you can’t re-map the collection type in this case although this might soon be possible. The query will not generate a join but simply select the tags since it’s a singular attribute. SELECT cat.id, cat.tags FROM Cat cat 3.12. Limit mapping Oftentimes it is not necessary to fetch all elements of a collection or correlation but only the top N values. To achieve that, the @Limit annotation can be used like in the following example: @EntityView(Cat.class) interface CatView extends SimpleCatView { @Mapping("kittens") @Limit(limit = "5", order = {"age DESC", "id DESC"}) List<SimpleCatView> getKittens(); } This will fetch only the 5 oldest kittens per cat, regardless of the used fetch strategy. A possible query for this view could look like this: SELECT cat.id, cat.name, kittens.id, kittens.name FROM Cat cat LEFT JOIN LATERAL Cat( SELECT kitten.age, kitten.father.id, kitten.id, kitten.mother.id, kitten.name FROM Cat kitten WHERE kitten MEMBER OF cat.kittens ORDER BY kitten.age DESC, kitten.id DESC LIMIT 5 ) kittens(age, father.id, id, mother.id, name) ON 1=1 3.13. Correlated mappings In some entity models, not every relation between entities might be explicitly mapped. There are multiple possible reasons for that like e.g. not wanting to have explicit dependencies, to keep it simple etc. Apart from unmapped relations, there is sometimes the need to correlate entities based on some criteria with other entities which are more of an ad-hoc nature than explicit relations. For these cases Blaze Persistence entity views introduces the concept of correlated mappings. These mappings can be used to connect entities through a custom criteria instead of through mapped entity relations. Correlated mappings can be used for any attribute type(basic, entity, subview, collection) although singular basic attributes can also be implemented as normal subqueries. A correlation mapping is declared by annotating the desired attribute with @MappingCorrelated or @MappingCorrelatedSimple . 3.13.1. General correlated mappings In order to map the correlation you need to specify some values correlationBasis - An expression that maps to the so called correlation key correlator - The CorrelationProvider to use for the correlation that introduces a so called correlated entity https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 24/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module By default, the correlated entity type is projected into the view. To map a specific property of the entity type, use the correlationResult attribute. There is also the possibility to specify a fetch strategy that should be used for the correlation. By default, the SELECT strategy is used. @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); @MappingCorrelated( correlationBasis = "age", correlator = PersonAgeCorrelationProvider.class, fetch = FetchStrategy.JOIN ) Set<Person> getSameAgedPersons(); static class PersonAgeCorrelationProvider implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) { 1 final String alias = builder.getCorrelationAlias(); builder.correlate(Person.class) .on(alias + ".age").inExpressions(correlationExpression) 2 .end(); } } } 1 2 getCorrelationAlias() defines the alias for the correlated entity correlationExpression represents the correlationBasis . We generally recommend to use the IN predicate through inExpressions() to be able to easily switch the fetch strategy Depending on the fetch strategy multiple other queries might be executed. Check out the different fetch strategies for further information. In this case, the JOIN strategy was used, so the following query is generated. SELECT cat.id, pers FROM Cat cat LEFT JOIN Person correlated_SameAgedPersons 1 ON cat.age = correlated_SameAgedPersons.age 2 1 This makes use of the so called entity join feature which is only available in newer JPA provider versions 2 Note that the IN predicate which was used in the correlation provider was rewritten to a equality predicate Since entity joins are required for using the JOIN fetch strategy with correlation mappings you have to make sure your JPA provider supports them. If your JPA provider does not support entity joins, you have to use a different fetch strategy instead. Entity joins are only supported in newer versions of JPA providers(Hibernate 5.1+, EclipseLink 2.4+, DataNucleus 5+) 3.13.2. Simple correlated mappings Since correlation providers are mostly static, Blaze Persistence also offers a way to define simple correlations in a declarative manner. The @MappingCorrelatedSimple annotation only requires a few values correlationBasis - An expression that maps to the so called correlation key correlated - The correlated entity type correlationExpression - The expression to use for correlating the correlated entity type to the view @EntityView(Person.class) public interface PersonView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) public interface CatView { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 25/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @IdMapping Long getId(); @MappingCorrelatedSimple( correlationBasis = "age", correlated = Person.class, correlationExpression = "age IN correlationKey" fetch = FetchStrategy.JOIN ) Set<PersonView> getSameAgedPersons(); 2 1 } 1 The expression uses the default name for the correlation key but could use a different name by specifying the attribute correlationKeyAlias 2 As you see here, it is obviously also possible to map subviews for correlated entity types Just like the general correlation, by default, the correlated entity type is projected into the view. To map a specific property of the entity type, use the correlationResult attribute. There is also the possibility to specify a fetch strategy that should be used for the correlation. By default, the SELECT strategy is used. 3.14. Correlation mappings via entity array syntax The easiest way to correlate an entity is by using the entity array syntax EntityName[predicate] which is explained in detail in the core documentation. Such a mapping can be used anywhere with all fetch strategies. Correlating with the current view is usually done with the VIEW macro which allows to refer to the current view. This is important because within the brackets of an entity array expression, the implicit root for path expressions is the joined entity itself. The previous example can be simplified to the following. @EntityView(Person.class) public interface PersonView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); @Mapping("Person[age IN VIEW(age)]") Set<PersonView> getSameAgedPersons(); } The result is very similar and roughly looks like this SELECT cat.id, Person__age_IN_VIEW_age__.id, Person__age_IN_VIEW_age__.name FROM Cat cat LEFT JOIN Person Person__age_IN_VIEW_age__ ON Person__age_IN_VIEW_age__.age = cat.age 3.15. Special method attributes There are some special methods that can be declared abstract in an entity view type which have special runtime support. 3.15.1. EntityViewManager getter An abstract method that returns EntityViewManager will not be considered to be an attribute. Such a method has special runtime support as it will always return the associated EntityViewManager . @EntityView(Person.class) public abstract class PersonView { @IdMapping public abstract Long getId(); abstract EntityViewManager getEntityViewManager(); public void someMethod() { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 26/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module // ... use getEntityViewManager() } } This is especially useful for Updatable Entity Views when a method wants to create a new instance of a subview or get a reference to a subview. 3.16. Mapping expression extensions Blaze Persistence entity views generally supports the full set of expressions that JPQL and Blaze Persistence core module supports, but in addition to that, also offers some expression extensions. 3.16.1. THIS Similar to the this expression in Java, in a mapping expression within entity views the this expression can be used to refer to the entity type backing the entity view. The expression can be used to implement embedded objects that are able to refer to the entity type of the entity view. @EntityView(Cat.class) interface EmbeddedCatView { @IdMapping Long getId(); String getName(); } @EmbeddableEntityView(Cat.class) interface ExternalInterfaceView { @Mapping("name") String getExternalName(); } @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @Mapping("this") EmbeddedCatView getEmbedded(); @Mapping("this") ExternalInterfaceView getAdapter(); } Both EmbeddedCatView and ExternalInterfaceView refer to the same Cat as their parent CatView . The query looks as if the types were directly embedded into the entity view. SELECT cat.id, cat.id, cat.name, cat.name FROM Cat cat 3.16.2. OUTER In Blaze Persistence core the OUTER function can be used to refer to the query root of a parent query from within a subquery. This is still the same with Blaze Persistence entity views but might lead to unintuitive behavior when the subquery provider uses OUTER and is used in a subview. The following example shows the unintuitive behavior. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); Set<KittenCatView> getKittens(); } @EntityView(Cat.class) interface KittenCatView { @IdMapping Long getId(); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 27/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @MappingSubquery(KittenCountSubqueryProvider.class) Long getKittenCount(); class KittenCountSubqueryProvider implements SubqueryProvider { @Override public <T> T createSubquery(SubqueryInitiator<T> subqueryBuilder) { return subqueryBuilder.from(Cat.class, "subCat") .select("COUNT(*)") .whereOr() .where("subCat.father.id").eqExpression("OUTER(id)") .where("subCat.mother.id").eqExpression("OUTER(id)") .endOr() .end(); } } } When applying the KittenCatView directly, everything works as expected. SELECT cat.id, ( SELECT COUNT(*) FROM Cat subCat WHERE subCat.father.id = cat.id OR subCat.mother.id = cat.id ) FROM Cat cat But when using KittenCatView as subview within CatView , it starts to break. SELECT cat.id, kittens_1.id, ( SELECT COUNT(*) FROM Cat subCat WHERE subCat.father.id = cat.id OR subCat.mother.id = cat.id ) FROM Cat cat LEFT JOIN cat.kittens kittens_1 1 1 OUTER resolved to cat instead of kittens_1 The OUTER function doesn’t know about the entity view structure and will remain to refer to the query root. It is often best to make use of the EMBEDDING_VIEW function instead, which refers to the relation of the embedding view. 3.16.3. VIEW The VIEW function can be used to refer to the relation backed by the current view. Usually this is not necessary as the relation of the current view is the implicit root for path expressions, but within the brackets of an entity array expression the implicit root is the joined entity. In such a case it is necessary to use the VIEW function to refer to attributes of the relation of the current view in the predicate. For an example usage, go to the entity array expression correlation section. 3.16.4. EMBEDDING_VIEW The EMBEDDING_VIEW function can be used to refer to the relation backed by the embedding view. In case of a subquery provider, this will refer to the relation of the view, using the subquery provider. In case of a normal subview, this will refer to the relation of the view which contains the subview. One of the main use cases for this function is when using subquery mappings. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); Set<KittenCatView> getKittens(); } @EntityView(Cat.class) interface KittenCatView { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 28/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @IdMapping Long getId(); @MappingSubquery(KittenCountSubqueryProvider.class) Long getKittenCount(); class KittenCountSubqueryProvider implements SubqueryProvider { @Override public <T> T createSubquery(SubqueryInitiator<T> subqueryBuilder) { return subqueryBuilder.from(Cat.class, "subCat") .select("COUNT(*)") .whereOr() .where("subCat.father.id").eqExpression("EMBEDDING_VIEW(id)") .where("subCat.mother.id").eqExpression("EMBEDDING_VIEW(id)") .endOr() .end(); } } } When applying the KittenCatView directly, everything works as expected, just like it did before with OUTER . SELECT cat.id, ( SELECT COUNT(*) FROM Cat subCat WHERE subCat.father.id = cat.id OR subCat.mother.id = cat.id ) FROM Cat cat But when using KittenCatView as subview within CatView , EMBEDDING_VIEW plays out it’s unique properties. SELECT cat.id, kittens_1.id, ( SELECT COUNT(*) FROM Cat subCat WHERE subCat.father.id = kittens_1.id OR subCat.mother.id = kittens_1.id ) FROM Cat cat LEFT JOIN cat.kittens kittens_1 1 1 EMBEDDING_VIEW resolved to kittens_1 whereas OUTER would resolve to cat Make sure you understand the <<anchor-select-fetch-strategy-view-root-or-embedding-view,implication> of the EMBEDDING_VIEW function when using the batched SELECT fetch strategy as this might affect performance. Note that the use of the EMBEDDING_VIEW function in a top level view will result in an exception since there is no embedding view. 3.16.5. VIEW_ROOT The VIEW_ROOT function can be used to refer to the relation for which the main entity view is applied. Normally this will resolve to the query root, but beware that the entity view root might not always be the query root. One of the main use cases for this function is when using correlated subview mappings. For further information on applying a different entity view root take a look into the querying chapter. The VIEW_ROOT function can be used in a correlation provider to additionally refer to a view root. @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); @MappingCorrelated( https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 29/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module correlationBasis = "age", correlator = CatAgeCorrelationProvider.class ) Set<Cat> getSameAgedCats(); static class CatAgeCorrelationProvider implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) { final String correlatedCat = builder.getCorrelationAlias(); builder.correlate(Cat.class) .on(correlatedCat + ".age").inExpressions(correlationExpression) .on(correlatedCat + ".id").notInExpressions("VIEW_ROOT(id)") 1 .end(); } } } 1 We generally recommend to use the IN predicate through inExpressions() or notInExpressions() to be able to easily switch the fetch strategy The VIEW_ROOT function is usable with every fetch strategy. In case of the JOIN fetch strategy the result is just as expected. SELECT cat.id, correlatedCat FROM Cat cat LEFT JOIN Cat correlatedCat ON correlatedCat.age = cat.age AND correlatedCat.id <> cat.id 1 1 Again, the IN predicate was rewritten to an equality predicate Make sure you understand the <<anchor-select-fetch-strategy-view-root-or-embedding-view,implication> of the VIEW_ROOT function when using the batched SELECT fetch strategy as this might affect performance. 3.17. Entity View constructor mapping So far, all mapping examples used interfaces for entity views, but as outlined in the beginning, Blaze Persistence entity views also has support for abstract classes. There are multiple use cases for using abstract classes for entity views, but in general we recommend to use an interface as often as possible. The biggest advantage of using abstract classes is that you can have a custom constructor which can further apply transformations on data. 3.17.1. Abstract class Entity View with custom equals-hashCode Abstract classes, contrary to interfaces, can define an implementation for the equals and hashCode methods which is normally generated for the runtime implementations of Entity Views. If you decide to have a custom implementation you have to fulfill the general requirement, that the equals and hashCode methods use Only the attribute mapped with @IdMapping if there is one Otherwise use all attributes of the Entity View Not following these requirements could lead to unexpected results so it is generally best to rely on the default implementation. For every custom implementation that is detected during the bootstrap a warning message will be logged. 3.17.2. Map external data model with view constructor One of those use cases for a view constructor is integrating with an existing external data model. class CatRestDTO { private final Long id; private final String name; public CatRestDTO(Long id, String name) { this.id = id; this.name = name; } public Long getId() { return id; } public String getName() { return name; https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 30/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module } } In general we recommend to use the entity view types directly instead of an external data model, because of the additional boilerplate code needed. Note that the creators of Blaze Persistence are not generally against external data models since it is reasonable to have them e.g. in API projects that shouldn’t expose a library dependency. @EntityView(Cat.class) public abstract class CatView extends CatRestDTO { public CatView( @Mapping("id") Long id, @Mapping("name") String name ) { super(id, name); } } Now you can use the CatView for efficient querying but still have objects that are an instance of CatRestDTO and can thus be used like normal CatRestDTO instances. To decouple the actual entity view CatView from the data access or service one normally uses method signatures like interface CatDAO { <T> List<T> findAll(Class<T> entityViewClass); 1 <T> List<T> findAll(EntityViewSetting<T, CriteriaBuilder<T>> entityViewSetting); 2 } 1 Create the EntityViewSetting within the implementation 2 Supply a custom EntityViewSetting which can also have filters, sorts, optional parameters and pagination information By using one of these approaches you can have a projection independent implementation for CatDAO and let the consumer i.e. a REST endpoint decide about the representation. 3.17.3. Additional data transformation in view constructor Another use case for view constructors is the transformation of data. Sometimes it is just easier to do the transformation in Java code instead of through a JPQL expression, but then there are also times when there is no other way than doing it in Java code. Let’s assume you want to have an attribute that contains different text based on the age. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); @Mapping("CASE WHEN age = 0 THEN 'newborn' WHEN age < 10 THEN 'child' WHEN age < 18 THEN 'teenager' ELSE 'adult' END") String getText(); } As you can see, the CASE WHEN expression can be used to implement that, but if the text is only static, there is no need to use that kind of expression. You can instead just inject the age as constructor parameter and do the mapping to the text in Java code. @EntityView(Cat.class) public abstract class CatView { private final String text; public CatView(@Mapping("age") long age) { if (age == 0) { this.text = "newborn"; } else if (age < 10) { this.text = "child"; } else if (age < 18) { this.text = "teenager"; } else { this.text = "adult"; } } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 31/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @IdMapping public abstract Long getId(); public String getText() { return text; } } Since that kind of mapping logic is normally externalized, Blaze Persistence entity views also offers a way to inject external services. You can provide services to entity views via optional parameters like EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class); setting.addOptionalParameter("ageMapper", new AgeToTextMapper()); List<CatView> result = entityViewManager.applySetting(setting, cbf.create(em, Cat.class)) .getResultList(); The services, or optional parameters in general can be consumed either as attributes or as constructor parameters with @MappingParameter . If the parameter is not supplied, null is injected. @EntityView(Cat.class) public abstract class CatView { private final String text; public CatView( @Mapping("age") long age, @MappingParameter("ageMapper") AgeToTextMapper mapper ) { this.text = ageMapper.map(age); } @IdMapping public abstract Long getId(); public String getText() { return text; } } 3.17.4. Multiple named constructors So far, the example always used no or just a single constructor, but it is actually possible to have multiple constructors. Every constructor in an entity view must have a name defined via @ViewConstructor . The default name is init and is used for constructors that have no @ViewConstructor annotation. @EntityView(Cat.class) public abstract class CatView { private final String text; public CatView( @Mapping("age") long age, @MappingParameter("ageMapper") AgeToTextMapper mapper ) { this.text = ageMapper.map(age); } @ViewConstructor("special") public CatView(@Mapping("age") long age) { this.text = age > 80 ? "oldy" : "normal"; } @IdMapping public abstract Long getId(); public String getText() { return text; } } The constructor name can be chosen when constructing a EntityViewSetting via create() . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 32/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module EntityViewSetting.create(CatView.class, "special"); 3.17.5. Using attribute getters in constructor Since mapping constructor parameters can become very cumbersome and oftentimes you need a value not only in the constructor but also accessible directly via a getter, Blaze Persistence came up with a solution that allows you to use the getters of attributes in the constructor. It might not be immediately obvious why this is a special thing. Since entity views are declared as abstract classes you can imagine that the runtime has to actually create concrete classes. These concrete classes normally initialize fields after calling the super constructor, thus making it impossible for the super constructor to actually retrieve values by using the attribute getters. The JVM enforces that fields can only be accessed after the super constructor has been called, so normally there is no way that the getter implementations that serve the fields can return non-null values in the super constructor. Fortunately, Blaze Persistence entity views found a way around this limitation of the JVM by making use of the infamous sun.misc.Unsafe to define a class that would normally fail bytecode verification. The trick is, that the implementations that are generated will set the fields before calling the super constructor thus making the values available to the super constructor. By default, all abstract classes will be defined through sun.misc.Unsafe . If you don’t want that behavior and instead want bytecode verifiable implementations to be generated, you can always disable this strategy by using a configuration property. @EntityView(Cat.class) public abstract class CatView { private final String text; public CatView(@MappingParameter("ageMapper") AgeToTextMapper mapper) { this.text = ageMapper.map(getAge()); 1 } @IdMapping public abstract Long getId(); public abstract Long getAge(); public String getText() { return text; } } 1 If the unsafe proxy is used, getAge() will return the actual value, otherwise it will return null Note that instead of using this unsafe approach which can’t be used when generating entity view implementations through the annotation processor, one can make use of the @Self annotation to inject a view of the state to a constructor. The previous example defined in a safe way would look like this: @EntityView(Cat.class) public abstract class CatView { private final String text; public CatView(@Self CatView self, @MappingParameter("ageMapper") AgeToTextMapper mapper) { this.text = ageMapper.map(self.getAge()); } @IdMapping public abstract Long getId(); public abstract Long getAge(); public String getText() { return text; } } Instead of calling the getter on the this instance which is not yet initialized, one uses the @Self annotated instance to access the state. The type of the instance is a serializable subclass of the entity view with the only purpose to serve as "self" instance. The construction of the "self" instance will not invoke any user code as it is constructed via deserialization to bypass constructor calls. 3.18. Inheritance mapping Entity views can have an inheritance relationship to subtypes via an inheritance mapping. This relationship allows instances of an entity view subtype to be materialized when a selection predicate defined by an inheritance mapping is satisfied. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 33/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module The inheritance feature for an entity view is activated by annotating @EntityViewInheritance on an entity view. By default, all subtypes of the entity view are considered as inheritance subtypes and thus require a so called inheritance mapping. An inheritance mapping is defined by annotating the subtype with @EntityViewInheritanceMapping and defining a selection predicate that represents the condition on which decides the instantiation of that subtype. The predicate is a normal JPQL predicate expression and can refer to all attributes of the mapped entity type. Consider the following example @EntityView(Cat.class) @EntityViewInheritance public interface BaseCatView { String getName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age < 18") public interface YoungCatView extends BaseCatView { @Mapping("mother.name") String getMotherName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age > 18") public interface OldCatView extends BaseCatView { @Mapping("kittens.name") List<String> getKittenNames(); } When querying for entity views of the type BaseCatView , the selection predicates age < 18 and age > 18 are merged into a type discriminator expression that returns a type index. The type index refers to the entity view type into which a result should be materialized. The resulting JPQL query for such an entity view looks like the following SELECT CASE WHEN age < 18 THEN 1 WHEN age > 18 THEN 2 ELSE 0 END, cat.name, mother_1.name, kittens_1.name FROM Cat cat LEFT JOIN cat.mother mother_1 LEFT JOIN cat.kittens kittens_1 The type index 0 refers to the base type BaseCatView , hence instances of BaseCatView are materialized when the age of a result equals 18. Since it might not be desirable to use all entity view subtypes for the inheritance relationship, it is possible to explicitly declare the subtypes in the @EntityViewInheritance annotation on the super type. @EntityView(Cat.class) @EntityViewInheritance({ YoungCatView.class }) public interface BaseCatView { String getName(); } This has the effect, that only BaseCatView or YoungCatView instances are materialized for a result. 3.19. Inheritance subview mapping Similarly to specifying the entity view inheritance subtypes at the declaration site, i.e. BaseCatView , it is also possible to define subtypes at the use site, i.e. at the subview attribute. By annotating the subview attribute with @MappingInheritance , it is possible to delimit and override the entity view subtype mappings that are considered for materialization from the result. When using the @MappingInheritance annotation, it is required to list all desired subtypes via @MappingInheritanceSubtype annotations that can optionally override the inheritance mapping. @EntityView(Person.class) interface PersonView { String getName(); @MappingInheritance({ @MappingInheritanceSubtype(mapping = "age <= 18", value = YoungCatView.class) https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 34/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module }) Set<BaseCatView> getCats(); } @EntityView(Cat.class) @EntityViewInheritance public interface BaseCatView { String getName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age < 18") public interface YoungCatView extends BaseCatView { @Mapping("mother.name") String getMotherName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age > 18") public interface OldCatView extends BaseCatView { @Mapping("kittens.name") List<String> getKittenNames(); } When querying for PersonView , YoungCatView instances will be materialized if the cat’s age is lower or equal to 18 and otherwise instances of BaseCatView will be created. By setting the annotation property onlySubtypes to true, instances of the base type BaseCatView aren’t materialized but a null is propagated. Apart from skipping the base type, it is also possible to define the base type via @MappingInheritanceSubtype which allows to specify the inheritance mapping for the base type. When no @MappingInheritanceSubtype elements are given, only the base type is materialized which can be used to disable the inheritance feature for an attribute. It is illegal to set onlySubtypes to true and have an empty set of subtype mappings as that would always result in a null object. 3.19.1. Inheritance mapping with constructors Entity view inheritance is not limited to interface types but can also be used with custom constructors. If a view constructor is used, all entity view inheritance subtypes must have a view constructor with the same name. In case of just a single constructor the @ViewConstructor does not have to be applied, as the name init is chosen by default as name. @EntityView(Cat.class) @EntityViewInheritance public abstract class BaseCatView { private final String parentName; public BaseCatView(@Mapping("father.name") String parentName) { this.parentName = parentName; } public abstract String getName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age < 18") public abstract class YoungCatView extends BaseCatView { public YoungCatView(@Mapping("mother.name") String parentName) { super(parentName); } @Mapping("mother.name") public abstract String getMotherName(); } @EntityView(Cat.class) @EntityViewInheritanceMapping("age > 18") public abstract class OldCatView extends BaseCatView { public OldCatView() { super("None"); } @Mapping("kittens.name") https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 35/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module public abstract List<String> getKittenNames(); } 3.19.2. Inheritance mapping and JPA inheritance The most obvious use case for entity view inheritance is mapping JPA entities that use an inheritance relationship. Blaze Persistence supports this and also makes use of defaults for the inheritance mapping in case a entity view subtype uses an entity subtype in the @EntityView annotation. @EntityView(Animal.class) @EntityViewInheritance public interface AnimalView { String getName(); } @EntityView(Cat.class) public interface CatView extends AnimalView { String getKittyName(); } @EntityView(Dog.class) public interface DogView extends AnimalView { String getDoggyName(); } The DogView uses the entity Dog and CatView the entity Cat which are both subtypes of Animal . In this case no inheritance mapping needs to be provided as Blaze Persistence will generate a type constraint like TYPE(this) = Dog or TYPE(this) = Cat for the respective entity view subtypes DogView and CatView . The resulting JPQL query when using AnimalView might look like the following SELECT CASE WHEN TYPE(animal) = Cat THEN 1 WHEN TYPE(animal) = Dog THEN 2 ELSE 0 END, animal.name, TREAT(animal AS Cat).kittyName, TREAT(animal AS Dog).doggyName FROM Animal animal As can be seen, the expressions for the access of the subtype properties rightfully make use of the TREAT operator. An entity view could also be modelled flat i.e. not mirroring the entity inheritance relationship as entity views, but just put the desired properties on a single entity view type. This can be done by making use of the TREAT operator and the this expression in the entity view mappings just as expected. @EntityView(Animal.class) @EntityViewInheritance public interface MyAnimalView { String getName(); @Mapping("TREAT(this AS Cat).kittyName") String getKittyName(); @Mapping("TREAT(this AS Dog).doggyName") String getDoggyName(); } The generated query looks approximately like this SELECT animal.name, TREAT(animal AS Cat).kittyName, TREAT(animal AS Dog).doggyName FROM Animal animal and in case an animal is not of the treated type, a null value will be produced. 3.20. Using CTEs in entity views https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 36/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module A CTE can be used in an entity view by correlating the CTE entity type, but it is still necessary to define the CTE. This can be done on the underlying Blaze Persistence core CriteriaBuilder before applying an entity view. Doing that is not always ideal e.g. when considering repositories for Spring Data or DeltaSpike Data, there is usually no access to the CriteriaBuilder . The @With annotation can be applied on an entity view class to register a CTEProvider to the entity view class. When applying an entity view, it’s corresponding registered CTEProvider instances are invoked that can define CTEs @EntityView(Cat.class) @With(MyCteProvider.class) public interface BaseCatView { class MyCteProvider implements CTEProvider { @Override public void applyCtes(CTEBuilder<?> builder, Map<String, Object> optionalParameters) { builder.with(MyCTE.class); // ... } } } For more information about CTEs refer to the CTE section in the core documentation. 3.21. Secondary entity view roots Up until now, mappings were always relative to the entity type of the entity view within which the mappings are defined, except for entity array expression correlations. Although entity array expressions are very mighty, they always imply a left join and there is no way to define a limit on elements to be joined. This is where secondary entity view roots come in. A secondary entity view root has an name and is defined on the entity view class level through the @EntityViewRoot and @EntityViewRoots annotations. The name is very important as secondary entity view roots are registered on the underlying query builder with the name as alias. Roughly speaking, secondary entity view roots can be thought of as a way to define joins that are registered and make them available through the defined name to mappings of an entity view. @EntityView(Cat.class) @EntityViewRoot(name = "v1", entity = Cat.class, condition = "id = VIEW(id)", joinType = JoinType.INNER) @EntityViewRoot(name = "v2", expression = "Cat[id = VIEW(id)]", limit = "1", order = "id DESC") @EntityViewRoot(name = "v3", correlator = CatView.TestCorrelator.class) public interface CatView { @IdMapping Long getId(); String getName(); @Mapping("v1.name") String getV1Name(); @Mapping("v2.name") String getV2Name(); @Mapping("v3.name") String getV3Name(); class TestCorrelator implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder correlationBuilder, String correlationExpression) { correlationBuilder.correlate(Cat.class) .on(correlationBuilder.getCorrelationAlias()).eqExpression(correlationExpression) .end(); } } } The generated query for such an entity view will roughly look like the following: SELECT cat.id cat.name, v1.name, v2.name, v3.name FROM Cat cat JOIN Cat v1 ON v1.id = cat.id LEFT JOIN LATERAL Cat( https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 37/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module SELECT v2_sub.age, v2_sub.father.id, v2_sub.id, v2_sub.mother.id, v2_sub.name FROM Cat v2_sub WHERE v2_sub.id = cat.id ORDER BY v2_sub.id DESC LIMIT 1 ) v2(age, father.id, id, mother.id, name) ON 1=1 LEFT JOIN Cat v3 ON v3.id = cat.id The big differences to using entity array expressions directly are: Possibility to specify the join type Possibility to specify a limit/offset with an order to implement a TOP-N per category join Possibility to use subqueries in the ON clause through a CorrelationProvider A view root can be either defined through an entity class with a condition: @EntityViewRoot( name = "root1", entity = Document.class, condition = "id = VIEW(documentId)" ) an expression with an optional condition: @EntityViewRoot( name = "root2", expression = "Document[id = VIEW(documentId)]", condition = "root2.age > 10" ) or through a correlator: @EntityViewRoot( name = "root3", correlator = MyCorrelationProvider.class ) Paths that are not fully qualified i.e. relative paths that use no root alias, are prefixed with the entity view root alias. The entity view root name must be unique across all entity view types that are accessible through attributes. During boot, every CorrelationProvider is probed to figure out the correlation type. If the CorrelationProvider is dynamic, you can optionally define the type through the entity attribute. NOTE: Using this feature requires a JPA provider that supports entity joins. Using JoinType.INNER can be a workaround as that is emulated through cross joins if needed. 4. Fetch strategies There are multiple different fetch strategies available for fetching. A fetch strategy can be applied to all kinds of mappings, except for @MappingParameter and @MappingSubquery . These mappings will always use a JOIN strategy, i.e. the mapping will be put into the main query. Any attribute in an entity view can be fetched separately by specifying a fetch strategy other than JOIN . Every fetch strategy has some pros and cons but most of the time, the JOIN fetch strategy is a good choice. Unless you can’t use the JOIN strategy because your JPA provider doesn’t support entity joins, you should always stick with it by default and only change the strategy on a case by case basis. 4.1. Join fetch strategy If your JPA provider supports entity joins, the JOIN strategy usually makes sense most of the time. In case of correlated mappings it will result in a LEFT JOIN entity join of the correlated entity type. The correlation expression created by the CorrelationProvider will be used in the ON condition. For an example query that is generated by this strategy take a look at the correlation mappings chapter. Entity joins are only supported in newer versions of JPA providers(Hibernate 5.1+, EclipseLink 2.4+, DataNucleus 5+) https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 38/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 4.2. Select fetch strategy In general, the SELECT strategy will create a separate query for every attribute that uses it. It will collect up to N distinct correlation basis values and then will execute that query to actually fetch the values for the attributes of the instances. The parameter N is the batch size that can be configured at multiple levels. Let’s look at an example that shows what happens @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); @BatchFetch(20) 1 @MappingCorrelated( correlationBasis = "age", correlator = PersonAgeCorrelationProvider.class, fetch = FetchStrategy.SELECT ) Set<Person> getSameAgedPersons(); static class PersonAgeCorrelationProvider implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) { final String pers = builder.getCorrelationAlias(); builder.correlate(Person.class) .on(pers + ".age").inExpressions(correlationExpression) .end(); } } } 1 Defines the batch size to use for loading When using this entity view, there are 2 queries that are generated. SELECT cat.id, cat.age FROM Cat cat The main query will fetch the correlationBasis and all the other attributes. SELECT correlationParams, correlated_SameAgedPersons FROM Person correlated_SameAgedPersons, Long(20 VALUES) correlationParams WHERE correlated_SameAgedPersons.age = correlationParams The correlation query on the other hand will select the correlation value and the Person instances with an age matching any of the correlationParams values. What you see here is the use of the VALUES clause for making multiple values available like a table for querying which is required when wanting to select the correlation value. Selecting the actual correlation value via correlationParams along with the Person is necessary to be able to correlate the instances to the instance of the main query. Depending on how many different values for age there are(cardinality), the correlation query might get executed multiple times. In general, the runtime will collect up to batch size different values and then execute the correlation query for these values. Results for a correlation value are cached during the querying to avoid querying the same correlation values multiple times in different batches. This strategy works best when the cardinality of the correlationBasis is low i.e. there are only a few distinct values. If the cardinality is high and the batch size is too low, this can lead to something similar as an N + 1 select known from lazy loading of collection elements. You could theoretically choose a very big batch size to be able to handle more correlation values per query, but beware that there are limits to the efficiency of this approach. Also beware that the amount of possible parameters might be limited by the DBMS. A value of 1000 for the batch size shouldn’t generally be a problem for a DBMS, but before you configure such a high value, look into the subselect strategy which might be more appropriate for higher cardinalities. 4.2.1. Select fetch strategy with batching Apart from using the @BatchFetch annotation, there are some other ways to define a batch size for fetching of an attribute. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 39/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Batch size default per entity view A default batch size can be defined by setting the property com.blazebit.persistence.view.batch_size via EntityViewSetting.setProperty() . The value serves as default value and can be overridden on a per attribute basis. Batch size per entity view attribute The batch size for a specific attribute can be defined either by using the @BatchFetch annotation or by setting the com.blazebit.persistence.view.batch_size property suffixed with the attribute name. In order to set the batch size for an attribute named someAttribute you have to set the property com.blazebit.persistence.view.batch_size.someAttribute via EntityViewSetting.setProperty() . The path to the attribute is based on the entity view which is queried and can also be deep i.e. someSubview.someAttribute . 4.2.2. Select fetch strategy with VIEW_ROOT or EMBEDDING_VIEW One possible problem with this strategy might arise when using the VIEW_ROOT or EMBEDDING_VIEW function. The use of two correlation keys i.e. the view root or embedding view and the correlation basis, will affect the way the batching can be done. Before querying for the correlated date, the runtime will determine the cardinality of the view ids and the correlation basis values. After that, it will group the values with higher cardinality by the values with lower cardinality to be able to do efficient batching. Let’s see what that means @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); Set<KittenCatView> getKittens(); } @EntityView(Cat.class) public interface KittenCatView { @IdMapping Long getId(); @BatchFetch(20) @MappingCorrelated( correlationBasis = "age", correlator = CatAgeCorrelationProvider.class, fetch = FetchStrategy.SELECT ) Set<Cat> getSameAgedCats(); static class CatAgeCorrelationProvider implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) { final String correlatedCat = builder.getCorrelationAlias(); builder.correlate(Cat.class) .on(correlatedCat + ".age").inExpressions(correlationExpression) .on(correlatedCat + ".id").notInExpressions("VIEW_ROOT(id)") .end(); } } } In this example the batching might happen either for view roots or correlation basis values depending on the data. If the number of distinct view root ids is lower than the number of distinct correlation basis values, the correlation basis values are grouped by view root ids. The runtime will then execute a batched query for every view root id. The good thing is, the runtime will adapt based on the data to minimize the number of queries, but still, if the cardinality is high, this can result in many queries being executed. Batching expectation fine tuning By default the runtime assumes that neither the VIEW_ROOT nor the EMBEDDING_VIEW function are used and generates a query that batches correlation basis values. If this assumption fails because the VIEW_ROOT or EMBEDDING_VIEW function is used and the batching is done based on view root or embedding view ids, a new query has to be built. The way the VIEW_ROOT and EMBEDDING_VIEW functions are implemented, it is required to invoke the CorrelationProvider again for building the new query. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 40/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module To avoid this unnecessary rebuilding of the query, you can specify the batch expectation for all attributes by setting the property com.blazebit.persistence.view.batch_mode via EntityViewSetting.setProperty() to view_roots if batching is expected to be done on a view root id basis or embedding_views if batching is expected to be done on a embedding view id basis. The value serves as default value and can be overridden on a per attribute basis by suffixing the property name with the attribute name. In order to set the batch expectation for an attribute named someAttribute you have to set the property com.blazebit.persistence.view.batch_mode.someAttribute via EntityViewSetting.setProperty() . The path to the attribute is based on the entity view which is queried and can also be deep i.e. someSubview.someAttribute . 4.3. Subselect fetch strategy The SUBSELECT strategy will create one query for every attribute that uses it and is especially efficient for bigger collections. It creates a separate query based on the outer query and applies the CorrelationProvider to it. Let’s look at an example that shows what happens @EntityView(Cat.class) public interface CatView { @IdMapping Long getId(); @MappingCorrelated( correlationBasis = "age", correlator = PersonAgeCorrelationProvider.class, correlationResult = "pers", fetch = FetchStrategy.SUBSELECT ) Set<Person> getSameAgedPersons(); static class PersonAgeCorrelationProvider implements CorrelationProvider { @Override public void applyCorrelation(CorrelationBuilder builder, String correlationExpression) { final String pers = builder.getCorrelationAlias(); builder.correlate(Person.class) .on(pers + ".age").inExpressions(correlationExpression) .end(); } } } When using this entity view, there are 2 queries that are generated. SELECT cat.id, cat.age FROM Cat cat The main query will fetch the correlationBasis and all the other attributes. SELECT cat.age, correlated_SameAgedPersons FROM Cat cat, Person correlated_SameAgedPersons WHERE correlated_SameAgedPersons.age = cat.age The correlation query looks very similar since it’s based on the main query, but has a custom select clause. It selects the correlation key as well as the attributes for the target representation in the main entity view. 4.4. Multiset fetch strategy The MULTISET strategy will use the TO_MULTISET function which aggregates tuples to a e.g. JSON/XML which is very efficient for big collections and wide rows. Note that using this strategy puts some restrictions on the attributes contained in the view types of the MULTISET fetched attribute: The types of the attributes all must have a BasicUserTypeStringSupport implementation which is the case for most basic types Entity types are not allowed because a BasicUserTypeStringSupport implementation is not possible for such types Let’s look at an example that shows what happens https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 41/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @EntityView(Cat.class) public interface CatNameView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) public interface CatView extends CatNameView { @Mapping(fetch = FetchStrategy.MULTISET) Set<CatNameView> getKittens(); } When using this entity view, only one query is generated. SELECT cat.id, cat.name, TO_MULTISET(( SELECT kittens_1.id, kittens_1.name FROM cat.kittens kittens_1 )) FROM Cat cat Behind the scenes, depending on the DBMS support, this will use JSON/XML functions to aggregate the subquery rows to a CLOB-like value. This aggregation comes with a certain cost, so this strategy is not perfect. This strategy outperforms the JOIN strategy only when the rows are very wide(i.e. take a lot of space) or there are nested collections. Wide rows are a problem for JOIN because these rows have to be duplicated for every collection element which is a problem from the network bandwidth and Java memory consumption perspective. Using JOIN fetching when selecting a list of 10 elements, each having a collection of 10 sub-elements will result in 100 rows being produced in a JDBC result. When each of the sub-elements have one or more collection again with overall 20 elements, 2000 rows are being produced in a JDBC result. Fetching 2000 rows is not a big deal for most DBMS and is usually pretty fast, but if the rows are very wide e.g. row size > 1kB network bandwidth and memory usage might slowly become a problem. With MULTISET fetching of the collection of the sub-elements, the JDBC result size will go down to 100 rows again and save a lot of bandwidth and memory because tuples don’t have to be duplicated. Unfortunately, the aggregation is not as efficient as fetching the collection separately. Overall, the MULTISET strategy will still mostly outperforms the SELECT and SUBSELECT fetch strategy due to the reduced latency and fewer query executions. 5. Filter and Sorter API Apart from mapping projections, Blaze Persistence entity views also provides support for filtering and sorting on attribute-level. Implementing the filtering and sorting based on attributes allows to completely encapsulate the entity model behind an entity view. The structure of an entity view is driven by the consumer and basing the filtering and sorting aspects on that very same structure is only natural for a consumer. The filter and sorter API is provided via com.blazebit.persistence.view.EntityViewSetting and allows filtering and sorting to be applied to entity views dynamically. Dynamic in this context means that the filters and sorters can be added/enabled without the need to explicitly modify the entity view type itself or the criteria builder which the entity view is based on. Let’s consider the following data access method for an example <V, C extends CriteriaBuilder<V>> getHungryCats(EntityViewSetting<V, C> settings) { ... } It implements the basic business logic of how to obtain all hungry cats via a CriteriaBuilder from the database. The method supports entity views to allow fetching only the fields which are needed for a concrete use cases. For example when displaying the cats in a dropdown, their names might be sufficient but when displaying them in a table it might be desirable to include more details. Likewise, one might want to retrieve the cats sorted by name or by age depending on the use case. Having to introduce 2 new methods for this purpose would be painful: <V, C extends CriteriaBuilder<V>> getHungryCatsSortedByName(EntityViewSetting<V, C> settings); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 42/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <V, C extends CriteriaBuilder<V>> getHungryCatsSortedByAge(EntityViewSetting<V, C> settings); The above approach does not even account for different sort orders so in reality we might rather go for parameterizing the original method which is painful nevertheless: <V, C extends CriteriaBuilder<V>> getHungryCats(EntityViewSetting<V, C> settings, String sortField, String sortOrder); Instead it is possible to apply the sorting to the EntityViewSetting instance that is passed to your data access layer: settings.addAttributeSorter("name", com.blazebit.persistence.view.Sorters.ascending()); dataAccess.getHungryCats(settings); All attribute names specified using the filter or sorter API refer to the entity view attribute names rather than entity attribute names. 5.1. Filter API The filter API allows to enable and parameterize a filter for entity view attributes. Entity view filters are defined by annotating respective entity view attributes with @AttributeFilter or @AttributeFilters for multiple named filters. In the annotation you can supply an optional filter name and a filter provider class which needs to extend AttributeFilterProvider . An attribute filter’s name must be unique for the attribute it is annotated on. The attribute filter without a filter name is the default filter. Only a single default attribute filter per attribute is allowed. Example: @EntityView(Cat.class) public interface CatView { @IdMapping Integer getId(); @AttributeFilters({ AttributeFilter(ContainsIgnoreCaseFilter.class), AttributeFilter(name = "containsCaseSensitive", value = ContainsFilter.class) }) String getName(); } Default attribute filters are enabled by calling addAttributeFilter(String attributeName, Object filterValue) whereas named filters require calling addAttributeFilter(String attributeName, String filterName, Object filterValue) . The supplied object values are used by the filter provider to append the appropriate restrictions to the query builder. EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class); setting.addAttributeFilter("name", "kitty"); 1 setting.addAttributeFilter("name", "containsCaseSensitive", "kitty"); 2 1 Enables the default filter ContainsIgnoreCaseFilter , so e.g. KITTY matches 2 Enables the named filter ContainsFilter , so e.g. KITTY doesn’t match Blaze Persistence provides a number of built-in filter providers in the com.blazebit.persistence.view.filter package: Built-in filters Supported filter value types GreaterOrEqualFilter Number, Date, String LessOrEqualFilter Number, Date, String GreaterThanFilter Number, Date, String LessThanFilter Number, Date, String BetweenFilter Range<?> or Object[] with: Number, Date, String https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 43/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Built-in filters Supported filter value types EqualFilter Any StartsWithFilter String EndsWithFilter String ContainsFilter String StartsWithIgnoreCaseFilter String EndsWithIgnoreCaseFilter String ContainsIgnoreCaseFilter String NullFilter Boolean - true includes NULLs, false excludes NULLs It is also possible to filter by subview attributes. The following example illustrates this: @EntityView(Cat.class) public interface CatView { @IdMapping Integer getId(); ChildCatView getChild(); } @EntityView(Cat.class) public interface ChildCatView { @IdMapping Integer getId(); @AttributeFilter(LessOrEqualFilter.class) Integer getAge(); } CriteriaBuilderFactory cbf = ...; EntityViewManager evm = ...; EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class); // by adding this filter, only cats with a child of age <= 10 will be selected setting.addAttributeFilter("child.age", "10"); Currently there is no support for collection filters like "has at least one" semantics. This is planned for a future version. When applying an attribute filter on a collection attribute or a subview attribute contained in a collection, the collection’s elements will currently be filtered. In the meantime, collection filters can be implemented by creating a custom attribute filter, applying restrictions directly on the entity view’s base query or by using a view filter. 5.1.1. View filters View filters allow filtering based on attributes of the view-backing entity as opposed to attribute filters which relate to entity view attributes. For example, the following entity view uses a view filter to filter by the age entity attribute of the Cat entity without this attribute being mapped in the entity view. @EntityView(Cat.class) @ViewFilter(name = "ageFilter", value = AgeFilterProvider.class) public interface CatView { @IdMapping Integer getId(); String getName(); class AgeFilterProvider extends ViewFilterProvider { @Override public <T extends WhereBuilder<T>> T apply(T whereBuilder) { return whereBuilder.where("age").gt(2L); } } } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 44/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module View filters need to be activated via the EntityViewSetting : setting.addViewFilter("ageFilter"); 5.1.2. Custom filters If the built-in filters do not satisfy your requirements you are free to implement custom attribute filters by extending AttributeFilterProvider with either one constructor accepting Class<?> - The attribute type Object - The filter value Class<?> and Object - The attribute type and the filter value Have a look at how a range filter could be implemented: public class MyCustomFilter extends AttributeFilterProvider { private final Range range; public MyCustomFilter(Object value) { this.value = (Range) value; } protected <T> T apply(RestrictionBuilder<T> restrictionBuilder) { return restrictionBuilder.between(range.lower).and(range.upper); } public static class Range { private final Number lower; private final Number upper; public Range(Number lower, Number upper) { this.lower = lower; this.upper = upper; } } } The filter implementation only uses the filter value in the constructor and assumes it to be of the Range type. By accepting the attribute type, a string to object conversion for the filter value can be implemented. 5.2. Sorter API The sorter API allows to sort entity views by their attributes. A sorter can be applied for an attribute by invoking addAttributeSorter(String attributeName, Sorter sorter) For an example of how to use the sorter API refer to the introductory example. Blaze Persistence provides default sorters via the static methods in the Sorters class. These methods allow to easily create any combination of ascending/descending and nulls-first/nulls-last sorter. At most one attribute sorter can be enabled per attribute. Sorting by subquery attributes (see ??) is problematic for some DBs. Currently, sorting by correlated attribute mappings (see ??) is also not fully supported. 5.2.1. Custom sorter If the built-in sorters do not satisfy your requirements you are free to create a custom sorter by implementing the Sorter interface. An example for a custom sorter might be a case insensitive sorter public class MySorter implements com.blazebit.persistence.view.Sorter { private final Sorter sorter; https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 45/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module private MySorter(Sorter sorter) { this.sorter = sorter; } public static Sorter asc() { return new MySorter(Sorters.ascending()); } public static Sorter desc() { return new MySorter(Sorters.descending()); } public <T extends OrderByBuilder<T>> T apply(T sortable, String expression) { return sorter.apply(sortable, "UPPER(" + expression + ")"); } } 6. Querying and Pagination API The main entry point to entity views is via the EntityViewSetting.create() API. There are multiple different variants of the static create() method that allow to construct a EntityViewSetting . create(Class<?> entityViewClass) Creates a simple entity view setting without pagination. create(Class<T> entityViewClass, int firstResult, int maxResults) Creates a entity view setting that will apply pagination to a CriteriaBuilder via page(int firstResult, int maxResults) create(Class<T> entityViewClass, Object entityId, int maxRows) Creates a entity view setting that will apply pagination to a CriteriaBuilder via pageAndNavigate(Object entityId, int maxResults) Every of the variants also has an overload that additionally accepts a viewConstructorName to be able to construct entity views via named constructors. A EntityViewSetting essentially is configuration that can be applied to a CriteriaBuilder and contains the following aspects Projection and DTO construction based on the entity view class Entity view attribute based filtering Entity view attribute based sorting Query pagination Allowing the actual data consumer i.e. the UI to specify these aspects is essential for efficient and easy to maintain data retrieval. For a simple lookup by id there is also a convenience EntityViewManager.find() method available that allows you to skip some of the CriteriaBuilder ceremony and that works analogous to how EntityManager.find() works, but with entity views. CatView cat = entityViewManager.find(entityManager, CatView.class, catId); To get just a reference to an entity view similar to what an entity reference retrieved via EntityManager.getReference() represents, it is possible to use EntityViewManager.getReference() . Note that the returned object will only have the identifier set, all other attributes will have their default values. This is usually useful when wanting to compare a list of elements with some entity view type against an entity id or also for setting *ToOne relationships. CatView cat = entityViewManager.getReference(CatView.class, catId); To get a reference to an entity form an entity view one can use EntityViewManager.getEntityReference() which will return the entity reference object retrieved via EntityManager.getReference() for the given entity view object. Cat cat = entityViewManager.<Cat>getEntityReference(entityManager, catView); 6.1. Querying entity views https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 46/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Code in the presentation layer is intended to create an EntityViewSetting via the create() API and pass the entity view setting to a data access method. The data access method then applies the setting onto a CriteriaBuilder instance which it created to build a query. We know that the current state of the EntityViewSetting API requires some verbose generics and we are going to fix that in 2.0. For further information also see #371 6.1.1. Normal CriteriaBuilder use Depending on the need for pagination, an EntityViewSetting object is normally created like this EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting; // Use this if no pagination is required setting = EntityViewSetting.create(CatView.class); // Apply filters and sorters on setting List<CatView> list = catDataAccess.findAll(setting); The implementation of the catDataAccess is quite simple. It creates a query with the CriteriaBuilder API as usual, and finally applies the setting on the builder through the EntityViewManager.applySetting() method. // Inject these somehow CriteriaBuilderFactory criteriaBuilderFactory; EntityViewManager entityViewManager; public <V, Q extends CriteriaBuilder<V>> List<V> findAll(EntityViewSetting<V, Q> setting) { CriteriaBuilder<Cat> criteriaBuilder = criteriaBuilderFactory.create(Cat.class); // Apply business logic filters criteriaBuilder.where("deleted").eq(false); return entityViewManager.applySetting(setting, criteriaBuilder) .getResultList(); } 6.1.2. Paginating entity view results When data pagination is required, the firstResult and maxResults parameters are required to be specified when creating the EntityViewSetting object EntityViewSetting<CatView, PaginatedCriteriaBuilder<CatView>> setting; // Paginate and show only the 10 first records by doing this setting = EntityViewSetting.create(CatView.class, 0, 10); // Apply filters and sorters on setting PagedList<CatView> list = catDataAccess.findAll(setting); To actually be able to get the PagedList instead of a normal list, the following data access implementation is required // Inject these somehow CriteriaBuilderFactory criteriaBuilderFactory; EntityViewManager entityViewManager; public <V, Q extends PaginatedCriteriaBuilder<V>> PagedList<V> findAll(EntityViewSetting<V, Q> setting) { CriteriaBuilder<Cat> criteriaBuilder = criteriaBuilderFactory.create(Cat.class); // Apply business logic filters criteriaBuilder.where("deleted").eq(false); return entityViewManager.applySetting(setting, criteriaBuilder) .getResultList(); } The only difference to the former implementation is that this method uses the PaginatedCriteriaBuilder as upper bound for the type variable and a different return type. By using a different type variable bound, the EntityViewManager.applySetting() will return an instance of PaginatedCriteriaBuilder . It’s getResultList() returns a PagedList instead of a normal list. 6.1.3. Keyset pagination with entity views The EntityViewSetting API also comes with an integration with the keyset pagination feature. A EntityViewSetting that serves for normal offset based pagination, can be additionally enriched with a KeysetPage by invoking withKeysetPage(KeysetPage keysetPage) . Supplying a keyset page allows the runtime to choose keyset pagination instead of offset pagination based on the requested page and the supplied keyset page. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 47/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module To be able to use keyset pagination, it is required to remember the last known keyset page. When using a server side UI technology, this can be done very easily by simply saving the keyset page in the HTTP session. With e.g. CDI the KeysetPage could simply be declared as field of a session-like scoped bean. EntityViewSetting<CatView, PaginatedCriteriaBuilder<CatView>> setting; int maxResults = ...; // elements per page int firstResult = ...; // (pageNumber - 1) * elementsPerPage setting = EntityViewSetting.create(CatView.class, firstResult, maxResults); // Apply filters and sorters on setting setting.withKeysetPage(previousKeysetPage); PagedList<CatView> list = catDataAccess.findAll(setting); previousKeysetPage = list.getKeysetPage(); When using a more stateless approach like it is often the case with RESTful backends, the keyset page has to be serialized to the client and deserialized back when reading from the client. Depending on your requirements, you can serialize the KeysetPage directly into e.g. a JSON object and should be able to deserialize it with the most common serialization libraries. Another possible way to integrate this, is to generate URLs that contain the keyset in some custom format which should then be used by the client to navigate to the next or previous page. Any of these approaches will require custom implementations of the KeysetPage and Keyset interfaces. The GraphQL, JAX-RS and Spring MVC, as well as Spring WebFlux integrations have proper implementations for serializing and deserializing entity view types. 6.1.4. Entity page navigation with entity views Sometimes it is necessary to navigate to a specific entry with a specific id. When required to also display the entry in a paginated table marked as selected, it is necessary to determine the page at which an entry with an id is located. This feature is implemented by the navigate to entity page feature and can be used by creating an EntityViewSetting via create(Class<T> entityViewClass, Object entityId, int maxResults) . EntityViewSetting<CatView, PaginatedCriteriaBuilder<CatView>> setting; setting = EntityViewSetting.create(CatView.class, catId, maxResults); // Apply filters and sorters on setting // Use this to activate keyset pagination setting.withKeysetPage(null); PagedList<CatView> list = catDataAccess.findAll(setting); previousKeysetPage = list.getKeysetPage(); 6.2. Optional parameters and configuration Apart from the already presented aspects, a EntityViewSetting also contains so called optional parameters and configuration properties. Optional parameters are set on a query if no value is set and also injected into entity views if requested by a parameter mapping and are a very good integration point for dependency injection into entity views. They can be set with the addOptionalParameter(String parameterName, Object value) method. Configuration properties denoted as being always applicable can be set via setProperty(String propertyName, Object value) and allow to override or fine tune configuration time behavior for a single query. 6.3. Applying entity views on specific relations Up until now, an entity view setting has always been applied on the query root of a CriteriaBuilder which might not always be doable because of the way relations are mapped or how the query is done. Fortunately, Blaze Persistence entity views also allow to apply a setting on a relation of the query root via EntityViewManager.applySetting(EntityViewSetting setting, CriteriaBuilder criteriaBuilder, String entityViewRoot) . Let’s consider the following example. @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 48/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module String getName(); } Mapping this entity view on e.g. the father relation like CriteriaBuilderFactory criteriaBuilderFactory = ...; EntityViewManager entityViewManager = ...; CriteriaBuilder<Cat> criteriaBuilder = criteriaBuilderFactory.create(Cat.class); criteriaBuilder.where("father").isNotNull(); List<CatView> list = entityViewManager.applySetting( EntityViewSetting.create(CatView.class), criteriaBuilder, "father" ); This will map all fathers of cats to the CatView and roughly produce a query like the following SELECT father_1.id, father_1.name FROM Cat cat LEFT JOIN cat.father father_1 WHERE father_1 IS NOT NULL 6.4. Fetching a data subset Although an entity view already represents a significantly state-reduced version of an entity, it might still be desirable to reduce the state even further. Imagine an UI that allows to configure visible columns in a table where entity view data is presented. Wouldn’t it be great if data that isn’t shown is not fetched at all? On a EntityViewSetting you can specify entity view attributes that you would like to fetch via the fetch(String path) method. As soon as you call it once, you will have to specify all attributes that you want to be fetched. Here a simple example: @EntityView(Cat.class) interface CatView { @IdMapping Long getId(); String getName(); PersonView getOwner(); @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); String getName(); @Mapping("cats.id") Set<Long> getCatIds(); } } Normally, when you fetch this, you will get a query like the following SELECT cat.id, cat.name, owner_1.id, owner_1.name, cats_1.id FROM Cat cat JOIN cat.owner owner_1 LEFT JOIN owner_1.cats cats_1 But when you use the following settings instead CriteriaBuilderFactory criteriaBuilderFactory = ...; EntityViewManager entityViewManager = ...; CriteriaBuilder<Cat> criteriaBuilder = ...; EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting; setting = EntityViewSetting.create(CatView.class); setting.fetch("name"); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 49/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module setting.fetch("owner.name"); List<CatView> list = entityViewManager.applySetting(setting, criteriaBuilder); You will instead only get the mentioned attributes and the identifiers by which the objects are reachable SELECT cat.id, cat.name, owner_1.id, owner_1.name, NULL FROM Cat cat JOIN cat.owner owner_1 Even the join was omitted because of this change. You still get the same CatView objects returned, but the getOwner().getCatIds() is simply empty. 7. Updatable Entity Views Updatable entity views represent DTOs for the write concern of an application. Updatable entity views are like a normal entity views, except that changes to attributes are tracked and can be inspected through the Change Model API or flushed to the backing data store. Updatable entity views are also a lot like normal entities and can be thought of being similar to what is sometimes referred to as sub-entities. The main idea is to model use-case specific representations with a limited scope of attributes that can change. Usually, when using an entity type, many more attributes are exposed as being changable to the consumer of the type, although they might not even need to be always changeable. Updatable entity views allow for perfect reuse of attribute declarations thanks to it’s use of interfaces but also brings a lot more to the table than using plain entities. Apart from a concept for updating existing objects, Blaze Persistence also has a notion for creating new objects. With only JPA, a developer is often left with some open question like e.g. how to implement equals-hashCode for entities. Thanks to the first class notion of creatable entity views, this question and others can be easily answered as discussed below. 7.1. Update mapping To declare an entity view as being updatable, it is required to additionally annotate it with @UpdatableEntityView . By default an updatable entity view will do full updates i.e. always update all (owned) updatable attributes if at least one (owned) attribute is dirty. Owned attributes are the ones that belong to the backing entity type like e.g. basic typed attributes. Collections or inverse attributes aren’t owned in this sense and are thus independent. This behavior can be configured by setting the mode attribute on the @UpdatableEntityView : PARTIAL - The mode will only flush values of actually changed attributes LAZY - The default, will flush all updatable values if at least one attribute is dirty FULL - Always flushes all updatable attributes, regardless of dirtyness The flushing, by default, is done by executing JPQL DML statements, but can be configured to use entities instead by setting the strategy attribute on the @UpdatableEntityView : QUERY - The default, will flush changes by executing JPQL DML statements. Falling back to entity flushing if necessary ENTITY - Will flush changes by loading the dirty entity graph and applying changes onto it 7.2. Create mapping To declare an entity view as being creatable, it is required to additionally annotate it with @CreatableEntityView . Note that updatable entity views for embeddable types are implicitly also creatable, yet the @CreatableEntityView annotation can still be applied for further configuration. By default, a creatable entity view is validated against the backing model regarding it’s persistability i.e. it is checked if an instance could be successfully persisted regarding the non-null constraints of the entity model. This allows to catch errors early that occur when adding new attributes to the entity model but forgetting to do so in the entity view. The validation can be disabled by setting the validatePersistability attribute on the @CreatableEntityView to false but can also be controlled in a fine grained manner by excluding specific entity attributes from the validation via the excludedEntityAttributes attribute. The latter is useful for attributes that are known to be set on the entity model through inverse relationship, entity listeners or entity view listeners. Creatable views are converted to their context specific declaration type after persisting. This mean that if a creatable entity view is used as value for an attribute of an updatable entity view, the instance is replaced by an equivalent instance of the type that is declared for the attribute. Consider the following example model for illustration. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 50/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @UpdatableMapping(cascade = CascadeType.PERSIST) OwnerView getOwner(); void setOwner(OwnerView owner); } @EntityView(Person.class) interface OwnerView { @IdMapping Long getId(); String getName(); } @CreatableEntityView @EntityView(Person.class) interface OwnerCreateView extends OwnerView { void setName(String name); } When flushing an instance of the type CatUpdateView that contains an owner of the creatable entity view type OwnerCreateView the following happens 1. A Person entity is created with the defined properties of the OwnerCreateView 2. The Person entity is persisted via EntityManager.persist() 3. The generated identifier is set on the OwnerCreateView object 4. The OwnerCreateView object is converted to the context specific declared type OwnerView 5. The OwnerCreateView object is replaced by the OwnerView object The same replacing happens for creatable entity views that are contained in a collection, thus developers don’t need to think about possible problems related to primary key based equals-hashCode implementations. Since the object is properly replaced, the assignment of a generated primary key, which would change the object regarding equals-hashCode, is not problematic. Still, the object can safely make use of the primary key based equalshashCode implementation that is generated for all entity views by default. 7.3. API usage An updatable as well as an creatable entity view is flushed by invoking EntityViewManager.save(EntityManager em, Object view) or one of its variants and will flush changes according to the flush strategy and mode. Changes to collections are flushed depending on the collection mapping, flush strategy and JPA provider support. The query flush strategy requires support for collection DML queries which is currently only provided with Hibernate. If the provider doesn’t support collection DML, or you choose to do entity flushing, the owning entity is loaded and changes are applied to that. For collections that are not owned by the containing entity i.e. use a mappedBy, changes will be applied by creating/updating/deleting the target entities. INFO: Blaze Persistence will manage inverse relationships automatically and even update the parent object in the child object if mapped. Creatable entity views are constructed via EntityViewManager.create(Class type) and always result in a persist when being flushed directly or through an updatable attribute having the CascadeType.PERSIST enabled. Deletion of entities through view types works either by supplying an existing view object to EntityViewManager.remove(EntityManager em, Object view) or by entity id via EntityViewManager.remove(EntityManager em, Class viewType, Object id) . The big advantage of using the remove APIs is that Blaze Persistence will reduce the amount of queries significantly, especially if the a view object is passed that already provides information about the object graph. 7.4. Lifecycle and listeners An entity view, similar to a JPA entity, also has something like a lifecycle, though within entity views, the states correspond to different entity view java types, rather than a transaction state. There are essentially 3 different kinds of entity views: new An instance of a creatable entity view type( @CreatableEntityView ) that is created via EntityViewManager.create(Class) . After flushing of such an instance, the instance transitions to the updatable state if the entity view java type is also updatable( @UpdatableEntityView ) otherwise to the read-only state. If it is used within an updatable view, it is then converted to the context specific type which replaces the creatable entity view instance. read-only A normal entity view without updatable or creatable configuration( @UpdatableEntityView , @CreatableEntityView ). https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 51/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module updatable An entity view with updatable configuration( @UpdatableEntityView ). load An entity view is loaded by applying an EntityViewSetting to a CriteriaBuilder which also happens implicitly when using EntityViewManager.find() . Another way to load is to get a reference for an entity view via EntityViewManager.getReference() but note that this does not invoke the @PostLoad lifecycle listener. remove Removing is done explicitly by calling EntityViewManager.remove() or implicitly when delete cascading or orphan removal is activated. create Creating of entity view instances is done by calling EntityViewManager.create() . save Flushing/Updating happens when invoking EntityViewManager.save() / EntityViewManager.saveTo() / EntityViewManager.saveWith() / EntityViewManager.saveWithTo() or EntityViewManager.saveFull() / EntityViewManager.saveFullTo() / EntityViewManager.saveFullWith() / EntityViewManager.saveFullW as well as implicitly for CascadeType.UPDATE enabled attributes. convert Conversion happens when calling EntityViewManager.convert() which implicitly happens for creatable entity views within a context after persisting. For most of the operations it is possible to register a listener which is invoked before or after an operation. The listeners can react to specific events but in some cases also alter the state of the corresponding object. A listener can be defined within an entity view class but within a class hierarchy there may only be one listener of a kind. If multiple listeners of a single kind from e.g. super interfaces are inherited, the entity view type must declare a listener to disambiguate the situation. The listener then can invoke other parent listener methods or skip them. Most listeners can be defined for a specific update or remove operation to react to change events in a particular manner for a specific use case, but it is also possible to register listeners globally. The globally registered listeners can be used to implement cross cutting concerns like soft-deletion, auditing, etc. Global listeners are registered via one of the EntityViewConfiguration.addEntityViewListener methods or discovered in a CDI or Spring environment if the classes are annotated with @EntityViewListener or @EntityViewListeners . A global listener must implement one or more of the following interfaces: PostPersistEntityListener PostPersistListener PostRemoveListener PostRollbackListener PostUpdateListener PrePersistEntityListener https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 52/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module PrePersistListener PreRemoveListener PreUpdateListener PostCommitListener 7.4.1. Post create listener Within an entity view type a concrete method annotated with @PostCreate is considered to be a post create listener. It may optionally define a parameter of the type EntityViewManager and must have a return type of void . Such a listener is usually used for creatable entity view types to setup default values. enum LifeState { ALIVE, DEAD; } @CreatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); LifeState getState(); void setState(LifeState state); @PostCreate default void init() { setState(LifeState.ALIVE); } } 7.4.2. Post convert listener Within an entity view type a concrete method annotated with @PostConvert is considered to be a post convert listener. It may optionally define a parameter of the type EntityViewManager and of the type Object for the source view and must have a return type of void . Such a listener is usually used to transfer transient state from a previous object. @CreatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); @MappingParameter("source") CatUpdateView getSource(); void setSource(CatUpdateView source); @PostConvert default void postConvert(Object source) { setSource((CatUpdateView) source); } } 7.4.3. Post load listener Within an entity view type a concrete method annotated with @PostLoad is considered to be a post load listener. It may optionally define a parameter of the type EntityViewManager and must have a return type of void . Such a listener is usually used for computing values based on the entity view state. @EntityView(Cat.class) abstract class CatUpdateView { private String shortName; @IdMapping https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 53/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module public abstract Long getId(); public abstract String getName(); public String getShortName() { return shortName; } @PostLoad void init() { this.shortName = getName().substring(0, 10) + "..."; } } 7.4.4. Pre remove listener Within an entity view type a concrete method annotated with @PreRemove is considered to be a pre remove listener. It may optionally define a parameter of the type EntityViewManager and of the type EntityManager and may have a return type of boolean or void . When the method returns true , the element is going to be removed. By returning false the removal can be cancelled. When the removal is cancelled, the view will be saved by calling EntityViewManager.save . Such a listener is usually used for implementing soft-deletion by cancelling the actual removal and instead doing an update. enum LifeState { ALIVE, DEAD; } @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); LifeState getState(); void setState(LifeState state); @PreRemove default boolean preRemove() { setState(LifeState.DEAD); return false; } } Additional listeners can be attached for an save or remove operation by using the EntityViewManager.saveWith(EntityManager em, Object view) or EntityViewManager.removeWith(EntityManager em, Object view) methods. CatUpdateView view = //... entityViewManager.removeWith(em, view) .onPreRemove(CatUpdateView.class, new PreRemoveListener<CatUpdateView>() { public boolean preRemove(EntityViewManager evm, EntityManager em, CatUpdateView view) { view.setState(LifeState.DEAD); return false; } }) .flush(); } 7.4.5. Post remove listener Within an entity view type a concrete method annotated with @PostRemove is considered to be a post remove listener. It may optionally define a parameter of the type EntityViewManager and of the type EntityManager and must have a return type of void. Such a listener is usually used for doing cleanups on e.g. external systems. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 54/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module void setName(String name); @PostRemove default void postRemove(EntityManager em) { em.persist(new ClearResourcesJob(getId())); } } Additional listeners can be attached for an save or remove operation by using the EntityViewManager.saveWith(EntityManager em, Object view) or EntityViewManager.removeWith(EntityManager em, Object view) methods. CatUpdateView view = //... entityViewManager.removeWith(em, view) .onPostRemove(CatUpdateView.class, new PostRemoveListener<CatUpdateView>() { public void postRemove(EntityViewManager evm, EntityManager em, CatUpdateView view) { em.persist(new ClearResourcesJob(view.getId())); } }) .flush(); } 7.4.6. Pre persist listener Within an entity view type a concrete method annotated with @PrePersist is considered to be a pre persist listener. It may optionally define parameters of the type EntityViewManager , of the type EntityManager or the entity type of the entity view and must have a return type of void. Such a listener is usually used for implementing setting default values or unmapped entity attributes that should only be set during creation. @CreatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); Calendar getCreationDate(); void setCreationDate(Calendar creationDate); @PrePersist default void prePersist(Cat c) { c.setAge(1); setCreationDate(Calendar.getInstance()); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPrePersist(CatUpdateView.class, new PrePersistListener<CatUpdateView>() { public void prePersist(EntityViewManager evm, EntityManager em, CatUpdateView view) { view.setCreationDate(Calendar.getInstance()); } }) .flush(); } Next to this entity view only pre persist listener there is also a variation of the listener type that allows to update the entity object. There is no annotation that can be used to create such a listener method within the entity view type as that would expose the JPA model to a method signature. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPrePersist(CatUpdateView.class, Cat.class, new PrePersistEntityListener<CatUpdateView, Cat>() { public void prePersist(EntityViewManager evm, EntityManager em, CatUpdateView view, Cat entity) { entity.setCreationDate(Calendar.getInstance()); } }) .flush(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 55/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Such a listener is usually used for setting attributes on an entity that shouldn’t be exposed through an entity view like e.g. a tenant. 7.4.7. Post persist listener Within an entity view type a concrete method annotated with @PostPersist is considered to be a post persist listener. It may optionally define parameters of the type EntityViewManager , of the type EntityManager or the entity type of the entity view and must have a return type of void. Such a listener is usually used for calling external systems. @CreatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); @PostPersist default void postPersist(EntityManager em) { em.persist(new ReplicationJob(view.getId())); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPostPersist(CatUpdateView.class, new PostPersistListener<CatUpdateView>() { public void postPersist(EntityViewManager evm, EntityManager em, CatUpdateView view) { em.persist(new ReplicationJob(view.getId())); } }) .flush(); } 7.4.8. Pre update listener Within an entity view type a concrete method annotated with @PreUpdate is considered to be a pre update listener. It may optionally define a parameter of the type EntityViewManager and of the type EntityManager and must have a return type of void. Such a listener is usually used for implementing automatic setting of e.g. modification dates. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); Calendar getModificationDate(); void setModificationDate(Calendar creationDate); @PreUpdate default void preUpdate() { setModificationDate(Calendar.getInstance()); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPreUpdate(CatUpdateView.class, new PreUpdateListener<CatUpdateView>() { public void preUpdate(EntityViewManager evm, EntityManager em, CatUpdateView view) { view.setState(LifeState.DEAD); } }) https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 56/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module .flush(); } 7.4.9. Post update listener Within an entity view type a concrete method annotated with @PostUpdate is considered to be a post update listener. It may optionally define a parameter of the type EntityViewManager and of the type EntityManager and must have a return type of void. Such a listener is usually used for calling external systems. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); @PostUpdate default void postUpdate(EntityManager em) { em.persist(new ReplicationJob(view.getId())); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPostUpdate(CatUpdateView.class, new PostUpdateListener<CatUpdateView>() { public void postUpdate(EntityViewManager evm, EntityManager em, CatUpdateView view) { em.persist(new ReplicationJob(view.getId())); } }) .flush(); } 7.4.10. Post commit listener Within an entity view type a concrete method annotated with @PostCommit is considered to be a post commit listener. It may optionally define parameters of the type EntityViewManager , of the type EntityManager or the type ViewTransition and must have a return type of void. The @PostCommit annotation can define the view transitions( PERSIST , UPDATE , REMOVE ) for which the listener should be invoked. Such a listener is usually used for calling external systems. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); @PostCommit(transitions = ViewTransition.UPDATE) default void postCommit(EntityManager em) { em.persist(new ReplicationJob(view.getId())); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPostCommit(CatUpdateView.class, new PostCommitListener<CatUpdateView>() { public void postCommit(EntityViewManager evm, EntityManager em, CatUpdateView view, ViewTransition transition) { em.persist(new ReplicationJob(view.getId())); } }) .flush(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 57/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module There are various short-had variants to register post commit listeners for specific view transitions like e.g. onPostCommitPersist() . 7.4.11. Post rollback listener Within an entity view type a concrete method annotated with @PostRollback is considered to be a post rollback listener. It may optionally define parameters of the type EntityViewManager , of the type EntityManager or the type ViewTransition and must have a return type of void. The @PostRollback annotation can define the view transitions( PERSIST , UPDATE , REMOVE ) for which the listener should be invoked. Such a listener is usually used for calling external systems or resetting state. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); boolean getDone(); void setDone(boolean done); @PostRollback(transitions = ViewTransition.UPDATE) default void postRollback() { setDone(false); } } Additional listeners can be attached for an save operation by using the EntityViewManager.saveWith(EntityManager em, Object view) method. CatUpdateView view = //... entityViewManager.saveWith(em, view) .onPostRollback(CatUpdateView.class, new PostRollbackListener<CatUpdateView>() { public void postRollback(EntityViewManager evm, EntityManager em, CatUpdateView view, ViewTransition transition) { view.setDone(false); } }) .flush(); } There are various short-had variants to register post rollback listeners for specific view transitions like e.g. onPostRollbackPersist() . 7.5. Attribute mappings When an entity view has @UpdatableEntityView annotated, every attribute for which a setter method exists, is considered to be updatable. For an attribute to be updatable means that changes done to the attribute of an entity view, can be flushed to the entity attribute they map to. There is also a notion of mutable attributes which means that an attribute is updatable and/or the type of the attribute’s value might be mutable. An unknown type is mutable by default and needs to be configured by registering a basic user type. Entity view types are only considered being mutable if they are updatable( @UpdatableEntityView ) or creatable( @CreatableEntityView ). Entity types are always considered to be mutable. The mappings for updatable attributes must follow some rules May not use complex expressions like arithmetic or functions May not access elements or attributes of elements through a collection e.g. kittens.name The general understanding is that mappings should be bi-directional i.e. it should be possible to map a value back to a specific entity attribute. To prevent an attribute being considered updatable, it can be annotated with @UpdatableMapping(updatable = false) . Sometimes, it’s also useful to annotate plural attributes i.e. collection attributes with @UpdatableMapping(updatable = true) when a setter is inappropriate. Note that updatable and creatable entity view types require an id mapping to work properly, which is validated during the building of the metamodel. The getters and setters of abstract entity view classes may use the protected or default visibility setting which allows to encapsulate the access to these attributes properly. 7.5.1. Basic type mappings Singular attributes with a basic type(all types except entity view types, entity types or collection types) do not have a nested domain structure since they are basic. Values of such types usually change by setting a different value, though there are some mutable types as well. Basic types in general are https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 58/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module handled by registered basic user types and define the necessary means to safely handle values of such types. Values set for a basic type entity view attribute are only flushed to the entity attribute it refers to, if the entity view attribute is updatable. This means that even if the type is mutable, a basic type attribute is never considered to be updatable as long as there is no setter or an explicit @UpdatableMapping(updatable = true) present. If a type is immutable, an attribute with such a type obviously needs a setter to be considered updatable as there would otherwise be no way to change a value. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); } Changes made via calls to e.g. setName() can be flushed later in a different persistence context. The following shows a simple example // Load the updatable entity view CatUpdateView view = entityViewManager.find(entityManager, CatUpdateView.class, catId); // Update the name of the view view.setName("newName"); // Flush the changes to the persistence context eventityViewManager.save(entityManager, view); Depending on the configured flush strategy, this will either load the Cat entity and apply changes to it or create an update query that set’s the updatable attributes. UPDATE Cat cat SET cat.name = :name WHERE cat.id = :id 7.5.2. Subview mappings Just like *ToOne relationships can be mapped in entities, it is possible to map these relationships as subviews. In general, Blaze Persistence distinguishes between two concepts regarding updatability Updatability of the relationship role i.e. the attribute owner or more specifically the owner_id column Updatability of the relation type represented by the entity view PersonView or more specifically the row in the person table The following example illustrates a case where the relation type PersonView is not updatable, but the relationship represented by the attribute owner is updatable. @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); String getName(); } @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); PersonView getOwner(); void setOwner(PersonView owner); } Even if the PersonView had a setName() method, changes done to that attribute would not be flushed, since PersonView is not updatable( @UpdatableEntityView ). https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 59/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Having only an updatable relationship role is very common, because it is rarely necessary to do cascading updates. It is so common, that by default, the subview types used for owned *ToOne relationships are not allowed to be updatable i.e. annotated with @UpdatableEntityView as that would break the idea of updatable views per use-case. An owned ToOne relationship is a link to an existing object which shouldn’t normally be altered as part of the object owning the *ToOne relationship. To illustrate this, let’s consider the entities Cat and Person . A Cat might have a @ManyToOne relationship called *owner that refers to Person . When considering the use case of editing a cat, one would normally expect to be able to change attributes like the name or age, maybe even the owner link, but never any attributes of the linked owner. Changing attributes of linked objects is usually a separate use case which deserves a separate model. One can always convert from one entity view model to another with EntityViewManager.convert(Class, Object, ConvertOption…), so it is not necessary to reload the data to be able to initiate another use case. The important part is that the altering of *ToOne linked objects is another use case and is by default not allowed to be done within an updatable entity view. An inverse OneToOne relationship is not owned and thus not linked which is why it is possible to have an updatable subview type for these relationships. As there might be models out there, that do not fit this requirement, it is possible to disable this strict check via @AllowUpdatableEntityViews on a per-attribute level or globally via the configuration property. Note that it is also possible to just make the entity view type PersonView updatable(annotate @UpdatableEntityView ) without the setter setOwner() . That way, the relationship role wouldn’t be allowed to change, but the changes to the underlying Person would be cascaded. When the subview type is updatable( @UpdatableEntityView ), updates are by default cascaded. If the subview type is also creatable( @CreatableEntityView ), persists are also cascaded. To disable or fine tune this behavior, it is possible to annotate the attribute getter with @UpdatableMapping and specify the cascade attribute. Apart from defining which CascadeType is enabled, it is also possible to restrict the allowed subtypes via the attributes subtypes , persistSubtypes and updateSubtypes . By default, instances of the declared type i.e. the compile time attribute type, are allowed to be set as attribute values. Subtypes that are non-updatable and non-creatable are also allowed. If the attribute defines UPDATE cascading or the declared type is updatable( @UpdatableEntityView ), all updatable subtypes that don’t introduce a cycle are also allowed. If the attribute defines PERSIST cascading or the declared type is creatable( @CreatableEntityView ), all creatable subtypes that don’t introduce a cycle are also allowed. When using immutable/non-updatable subview types the method EntityViewManager.getReference(Class viewType, Object id) might come in handy. This method allows to retrieve an instance of the given view type having the defined identifier. This is very useful for cases when just a relationship role like e.g. owner should be set without the need to query PersonView objects. A common use case might be to set the tenant which owns an object. There is no need to query the tenant as the information is unnecessary for simply setting the relationship role, but the tenant’s identity is known. To be able to encapsulate the creation of subviews or the access to references for subviews it is recommended to make use of the special EntityViewManager getter method. The idea is to define an abstract getter method with protected or default visibility returning an EntityViewManager . Methods that create subviews or want a reference to a subview by id can then invoke the getter to get access to the EntityViewManager . The following encapsulated updatable entity views illustrate the usage: @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); String getName(); } @UpdatableEntityView @EntityView(Cat.class) abstract class CatUpdateView { @IdMapping public abstract Long getId(); public abstract String getName(); @Mapping("owner") protected abstract PersonView getOwnerInternal(); protected abstract void setOwnerInternal(PersonView owner); protected abstract EntityViewManager evm(); public PersonView getOwner() { return getOwnerInternal(); } public void setOwner(PersonView owner) { setOwnerInternal(evm().convert(PersonView.class, owner)); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 60/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module } public void setOwnerId(Long id) { setOwnerInternal(evm().getReference(PersonView.class, id)); } } 7.5.3. Flat view mappings Updatable flat view mappings are currently only supported for embeddable types. An updatable flat view type is also always creatable. Flat views are always flushed as whole objects, which means that an updatable flat view should always at least map all attributes as read-only. Read-only i.e. nonupdatable attributes are passed-through to the embeddable object when recreating it. Apart from that, a flat view is just like a normal subview. 7.5.4. Subquery & parameter mappings Since subqueries and parameter mappings aren’t bidirectional, attributes using these kinds of mappings are never considered to be updatable. 7.5.5. Entity mappings Entity types are similar to subview types as they have an identity and are specially handled when loading and merging data. Since entity types are mutable by design, PERSIST and UPDATE cascading are by default enabled for attributes that use entity types. The cascading can be overridden by defining the cascade type via a @UpdatableMapping annotation on the attribute. Note that the handling of entity types can be fine tuned by registering a basic user type. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); @UpdatableMapping(cascade = { CascadeType.UPDATE }) Cat getFather(); void setFather(Cat father); 1 } 1 Defines that only updates are cascaded. Unknown i.e. new Cat instances aren’t persisted Changes that are done via setFather() will update the father attribute in the entity model when flushed. If query flushing is configured, a query like the following will be generated when updating the father relation. UPDATE Cat cat SET cat.father = :father WHERE cat.id = :id Since dirty tracking heavily relies on the equals and hashCode implementations, we recommend you implement equals and hashCode of your entity types based on the primary key. 7.5.6. Collection mappings Updatable collection mappings must be simple paths referring to a collection of the backing entity type. Paths to a nested collection like e.g. owner.kittens are not allowed. Currently, a collection attribute is considered to be updatable if a setter for the attribute exists, or @UpdatableMapping is declared on the getter method of an attribute. At this point, collections can not be remapped automatically yet, so you have to use the same collection type as in the entity model. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); Set<Cat> getKittens(); void setKittens(Set<Cat> kittens); } Any modification done to a collection https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 61/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module CatUpdateView view = ...; // Update the view Cat newKitten = entityManager.find(Cat.class, 2L); view.getKittens().add(newKitten); // Flush the changes to the persistence context entityViewManager.save(entityManager, view); Will be applied on the collection of an entity reference during EntityViewManager.save() as if the following was done. CatUpdateView view = ...; // Actually a query that loads the graph being dirty is issued Cat cat = entityManager.find(Cat.class, view.getId()); cat.getKittens().add(newKitten); Since the kittens collection is dirty i.e. a new kitten was added and the collection is owned by the Cat entity, the collection will be loaded along with the Cat when using the entity flush strategy. WIth query flushing, instead of loading and adding, the new kitten will be added via a collection DML statement INSERT INTO Cat.kittens(id, kittens.id) SELECT :ownerId, :kittenId FROM Integer(1 VALUES) If kittens were an inverse collection, it wouldn’t need loading during flushing even with the entity flush strategy as adding the new kitten would be a matter of issuing an update query or persisting an entity. 7.5.7. Inverse mappings Changes to inverse relations like OneToOne’s and *ToMany collections are flushed by persisting, updating or removing the inverse relation objects. There is no special mapping required. If the entity model defines that an attribute is an inverse mapping by specifying a mappedBy, updatable entity view attributes mapping to such attributes automatically discover the mappedBy configuration and will cause the attribute being maintained by managing inverse relation objects. There are several strategies that can be configured to handle the removal of elements via the removeStrategy attribute of @MappingInverse IGNORE - Ignores elements that have been removed i.e. does not maintain the relationship automatically. REMOVE - Removes the inverse relation object when determined to be removed from the inverse relationship. SET_NULL - The default. Sets the mappedBy attribute to NULL on the inverse relation object when found to be removed from the inverse relationship. @UpdatableEntityView @EntityView(Person.class) interface PersonUpdateView { @IdMapping Long getId(); // mappedBy = "owner" @MappingInverse(removeStrategy = InverseRemoveStrategy.REMOVE) Set<Cat> getKittens(); void setKittens(Set<Cat> kittens); } A modification of the kittens collection… PersonUpdateView view = ...; // Update the view view.getKittens().remove(someKitten); // Flush the changes to the persistence context entityViewManager.save(entityManager, view); will cause the Cat someKitten to be removed. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 62/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module DELETE Cat c WHERE c.id = :someKittenId If the SET_NULL strategy were used, the owner would be set to NULL UPDATE Cat c SET owner = NULL WHERE c.id = :someKittenId 7.5.8. Correlated mappings The only difference between correlated mappings and other mappings is that there is no relationship that is updated. Cascading will happen the same way for entities, updatable and creatable entity views. Although there is no relationship to update for correlation mappings, adding or removing elements to a correlated attribute with updatable types, will be constrained by updatability like normal mappings. If a correlated attribute isn’t updatable by means of @UpdatableMapping(updatable = false) , setting a value or adding/removing to a collection will fail. Consider the following simple example. @UpdatableEntityView @EntityView(Person.class) interface PersonView { @IdMapping Long getId(); String getName(); void setName(String name); } @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); String getName(); @MappingCorrelatedSimple( correlated = Person.class, correlationBasis = "owner.id", correlationExpression = "id IN correlationKey" ) PersonView getOwner(); void setOwner(PersonView owner); } When changing the name of a correlated owner CatUpdateView view = ...; // Update the view view.getOwner().setName("newName"); // Flush the changes to the persistence context entityViewManager.save(entityManager, view); The update of the CatUpdateView will cascade to the correlated object. UPDATE Person p SET p.name = :name WHERE p.id = :personId Note that a future version might allow to treat correlated mappings as custom inverse mappings. 7.5.9. Updatable mapping defaults The default mappings follow the concept of what you see is what you get. If the type of an attribute is a @UpdatableEntityView , changes done to that object will be flushed during an update. Unsupported configurations will fail during boot. Basic types are either simple value types like Integer , String or JPA managed types i.e. entities or embeddables. Unless an immutable user type was registered via the BasicUserType SPI, a basic type is by default considered to be mutable. A JPA entity type has identity which makes updatability independent from update cascading. Types without identity are either both updatable and update cascaded or immutable. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 63/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module An attribute does update cascading if changes done to an instance reached through that attribute are flushed during update. An attribute does persist cascading if a new object reached through that attribute is persisted during update. The following tables should help illustrate the defaults and are also a good reference. Basic simple type Relationship Update cascaded Persist cascaded no no no no no no yes no no yes no no no no no no no no updatable @EntityView(Entity.class) interface View { String getName(); } @EntityView(Entity.class) interface View { String getName(); void setName(String name); } @EntityView(Entity.class) @UpdatableEntityView interface View { String getName(); void setName(String name); } @EntityView(Entity.class) @UpdatableEntityView interface View { java.util.Date getDate(); // Mutable } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) String getName(); void setName(String name); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) java.util.Date getDate(); // Mutable } Using a JPA embeddable type Embeddable https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 64/120 2023. 06. 29. 22:08 Basic JPA embeddable type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no no no yes yes no yes yes no no no no no no no Relationship Update cascaded Persist cascaded no no no no no no updatable @EntityView(Entity.class) interface View { Embeddable getEmbeddable(); } @EntityView(Entity.class) interface View { Embeddable getEmbeddable(); void setEmbeddable(Embeddable embeddable); } @EntityView(Entity.class) @UpdatableEntityView interface View { Embeddable getEmbeddable(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Embeddable getEmbeddable(); void setEmbeddable(Embeddable embeddable); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Embeddable getEmbeddable(); void setEmbeddable(Embeddable embeddable); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Embeddable getEmbeddable(); } Using a JPA entity type Entity2 Basic JPA entity type updatable @EntityView(Entity.class) interface View { Entity2 getEntity2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Entity2 getEntity2(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 65/120 2023. 06. 29. 22:08 Basic JPA entity type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no yes no yes yes no no no no no no no Relationship Update cascaded Persist cascaded no no no no no no updatable @EntityView(Entity.class) interface View { Entity2 getEntity2(); void setEntity2(Entity2 entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { Entity2 getEntity2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Entity2 getEntity2(); void setEntity2(Entity2 entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Entity2 getEntity2(); void setEntity2(Entity2 entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Entity2 getEntity2(); } Using a read-only entity view type View2 that looks like @EntityView(Entity2.class) interface View2 { @IdMapping Integer getId(); String getName(); void setName(String name); } results in the following default behavior View type updatable @EntityView(Entity.class) interface View { @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) View2 getView2(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 66/120 2023. 06. 29. 22:08 View type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no no no yes no [1] no [2] updatable View2 getView2(); } @EntityView(Entity.class) interface View { View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { View2 getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) View2 getView2(); } 1. If a subtype of View2 that is an updatable view type exists and is set, updates will cascade 2. If a subtype of View2 that is a creatable view type exists and is set, the object will be persisted no no no no no no A type that isn’t allowed for whatever reason will not be allowed to be set on the attribute. Using an updatable entity view type View2 that looks like @EntityView(Entity2.class) @UpdatableEntityView interface View2 { @IdMapping Integer getId(); String getName(); void setName(String name); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 67/120 2023. 06. 29. 22:08 View type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no no no no yes no yes yes no [3] updatable @EntityView(Entity.class) interface View { View2 getView2(); } @EntityView(Entity.class) interface View { View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { View2 getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) View2 getView2(); void setView2(View2 view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) View2 getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(cascade = {}) View2 getView2(); } 3. If a subtype of View2 that is a creatable view type exists and is set, the object will be persisted no yes no no yes no no no no 7.5.10. Updatable collection mapping defaults Collections are different, as they are mutable by default. Since it is rarely necessary to make the relationship updatable, collections aren’t updatable by default just because they are mutable by design. In order for a collection relationship to be considered updatable, it must have a setter, be annotated with @UpdatableMapping(updatable = true) or have an element type that is @CreatableEntityView . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 68/120 2023. 06. 29. 22:08 Basic simple type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no no no yes no no yes no no no no no no no no Relationship Update cascaded Persist cascaded no no no no no no updatable @EntityView(Entity.class) interface View { Set<String> getNames(); } @EntityView(Entity.class) interface View { Set<String> getNames(); void setNames(Set<String> names); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<String> getNames(); void setNames(Set<String> names); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<java.util.Date> getDates(); // Mutable } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<String> getNames(); void setNames(Set<String> names); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<java.util.Date> getDates(); // Mutable } Using a JPA embeddable type Embeddable Basic JPA embeddable type updatable @EntityView(Entity.class) interface View { Set<Embeddable> getEmbeddables(); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Embeddable> getEmbeddables(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 69/120 2023. 06. 29. 22:08 Basic JPA embeddable type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no yes yes no yes yes no no no no no no no Relationship Update cascaded Persist cascaded no no no no no no no no no updatable @EntityView(Entity.class) interface View { Set<Embeddable> getEmbeddables(); void setEmbeddable(Set<Embeddable> set); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<Embeddable> getEmbeddables(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<Embeddable> getEmbeddables(); void setEmbeddable(Set<Embeddable> set); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Embeddable> getEmbeddables(); void setEmbeddable(Set<Embeddable> set); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Embeddable> getEmbeddables(); } Using a JPA entity type Entity2 Basic JPA entity type updatable @EntityView(Entity.class) interface View { Set<Entity2> getEntity2(); } @EntityView(Entity.class) interface View { Set<Entity2> getEntity2(); void setEntity2(Set<Entity2> entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Entity2> getEntity2(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 70/120 2023. 06. 29. 22:08 Basic JPA entity type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no yes no yes yes no no yes no no no no Relationship Update cascaded Persist cascaded no no no no no no no no no updatable @EntityView(Entity.class) @UpdatableEntityView interface View { Set<Entity2> getEntity2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<Entity2> getEntity2(); void setEntity2(Set<Entity2> entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Entity2> getEntity2(); void setEntity2(Set<Entity2> entity2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<Entity2> getEntity2(); } Using a read-only entity view type View2 that looks like @EntityView(Entity2.class) interface View2 { @IdMapping Integer getId(); String getName(); void setName(String name); } View type updatable @EntityView(Entity.class) interface View { Set<View2> getView2(); } @EntityView(Entity.class) interface View { Set<View2> getView2(); @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<View2> getView2(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 71/120 2023. 06. 29. 22:08 View type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no yes no [4] no [5] updatable void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<View2> getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<View2> getView2(); void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<View2> getView2(); void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<View2> getView2(); } 4. If a subtype of View2 that is an updatable view type exists and is added, updates will cascade 5. If a subtype of View2 that is a creatable view type exists and is added, the object will be persisted no no no no no no Update cascaded Persist cascaded no no no no no no A type that isn’t allowed for whatever reason will not be allowed to be added on the attribute. Using an updatable entity view type View2 that looks like @EntityView(Entity2.class) @UpdatableEntityView interface View2 { @IdMapping Integer getId(); String getName(); void setName(String name); } View type Relationship updatable @EntityView(Entity.class) interface View { Set<View2> getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(cascade = {}) Set<View2> getView2(); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 72/120 2023. 06. 29. 22:08 View type Blaze Persistence - Entity View Module Relationship Update cascaded Persist cascaded no no no no yes no yes yes no [6] updatable @EntityView(Entity.class) interface View { Set<View2> getView2(); void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<View2> getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { Set<View2> getView2(); void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<View2> getView2(); void setView2(Set<View2> view2); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(updatable = false) Set<View2> getView2(); } @EntityView(Entity.class) @UpdatableEntityView interface View { @UpdatableMapping(cascade = {}) Set<View2> getView2(); } 6. If a subtype of View2 that is a creatable view type exists and is added, the object will be persisted no yes no no yes no no no no 7.6. Locking support Blaze Persistence entity views by default automatically makes use of a version field mapped in the entity type for optimistic locking. This is controlled by the lockMode attribute on the @UpdatableEntityView annotation which by default is set to AUTO . LockMode.AUTO - The default. Uses the version field of the entity type the entity view is referring to for optimistic locking LockMode.OPTIMISTIC - Forces the use of optimistic locking based on the entity version field LockMode.PESSIMISTIC_READ - Acquires a JPA PESSIMISTIC_READ lock when reading the entity view LockMode.PESSIMISTIC_WRITE - Acquires a JPA PESSIMISTIC_WRITE lock when reading the entity view LockMode.NONE - Don’t use any locking even if a version attribute is available By default, all updatable attributes in an entity view are protected by optimistic locking. This means that if the value of an attribute was changed, the change will be flushed with the optimistic lock condition. Attribute changes that should be excluded from optimistic locking can be annotated with @OptimisticLock(exclude = true) to prevent the optimistic lock condition when only such attributes are changed. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 73/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module The entity type for which the optimistic lock condition is asserted is called the lock owner. If the entity type of an entity view does not have a version field and the LockMode.AUTO is used, the parent entity view type is considered being the lock owner. If the parent has no version field, it’s parent is considered and so forth. If no lock owner can be found, no optimistic locking is done. When specifying a lock mode other than LockMode.AUTO , the entity object for an entity view becomes the lock owner. By annotating @LockOwner on an updatable entity view type, a custom lock owner can be defined. This is still in development, so not all features might be available yet. Also see https://github.com/Blazebit/blazepersistence/issues/439 and https://github.com/Blazebit/blaze-persistence/issues/438 for more information. 7.7. Persist and Update cascading The cascade types defined in Blaze Persistence entity views have different semantics than what JPA offers and should not be mixed up. JPA defines cascade types for logical operations whereas Blaze Persistence entity views defines cascade types for state changes. In a JPA entity, one can define for which operations the changes done to an attribute should be flushed. For example the JPA CascadeType.PERSIST will cause a flush of an attribute’s affected values only if the owning entity is about to be persisted. Blaze Persistence entity views cascade types define whether a value of an attribute may do a specific state transition. If an attribute defines CascadeType.PERSIST , it means that new objects i.e. the ones created via EntityViewManager.create() , are allowed to be used as values and that these object should be persisted during flushing. Updates done to mutable values of an attribute are only flushed if the CascadeType.UPDATE is enabled. Normally, the update or persist cascading is enabled for all subtypes of the declared attribute type, but can be restricted by specifying specific subtypes for which to allow updates or persists. This can be done via the subtypes attribute of the @UpdatableMapping or the updateSubtypes or persistSubtypes attributes for the corresponding cascade types. 7.8. Cascading deletes and orphan removal Delete cascading and orphan removal have the same semantics as in JPA. If you delete an entity A that refers to entity B through an attribute that defines delete cascading, entity B is going to be deleted as well. When removing a reference from entity A to entity B through an attribute that defines orphan removal, entity B is going to be deleted. Orphan removal also implies delete cascading, so entity B is also deleted when deleting entity A. Most JPA implementations only support cascading deletes and orphan removal for managed entities whereas DML statements for the entity types do not consider this configuration. Blaze Persistence respects the settings all the way and strives to avoid data loading even for the removal by id action done via EntityViewManager.remove(EntityManager, Class, Object). When an entity graph for an entity view type has an arbitrary depth relationship, Blaze Persistence still has to do some entity data loading, but it tries to reduce the executed statements as much as possible. At some point, DML statements might be grouped together via Updatable CTEs for DBMS that support that. For more information about that, see https://github.com/Blazebit/blaze-persistence/issues/500 To enable delete cascading for an attribute, the CascadeType.DELETE has to be added to the cascade attribute of a @UpdatableMapping @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); @UpdatableMapping(cascade = { CascadeType.DELETE }) Person getOwner(); } When deleting a Cat like the following entityViewManager.remove(entityManager, CatUpdateView.class, catId); the owner is going to be deleted along with the Cat . The delete cascading even works for attributes that are only defined to do delete cascading in the entity. Assuming Cat does not have the arbitrary depth relationship kittens , the removal might trigger the following logical JPQL statements. DELETE Cat(nickNames) cat WHERE cat.id = :catId DELETE Cat cat WHERE cat.id = :catId RETURNING owner.id DELETE Person person WHERE person.id = :ownerId https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 74/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module First, the cascading delete enabled collections like e.g. the nickNames collection is deleted. Then the Cat is deleted and while doing that, the ids of the *ToOne relations with enabled cascading deletes like e.g. the owner’s id are returned. For DBMS not supporting the RETURNING clause for DML statements, a SELECT statement is issued before the DELETE to extract the ids of the *ToOne relations. Finally, the cascading deletes for the *ToOne relations are done e.g. the Person is deleted. A future strategy for deletion might facilitate temporary tables if the DBMS supports it rather than selecting. For more information see https://github.com/Blazebit/blaze-persistence/issues/220 If the entity type for an updatable entity view uses delete cascading or orphan removal for an attribute, an updatable mapping for that attribute must use these configurations as well. So if the entity type uses delete cascading for the owner of Cat , it would be an error to omit the delete cascading configuration. @UpdatableEntityView @EntityView(Cat.class) interface CatUpdateView { @IdMapping Long getId(); @UpdatableMapping(cascade = { }) Person getOwner(); 1 } 1 Can’t omit delete cascading if entity attribute uses delete cascading The same goes for orphan removal and the idea behind this is, that it makes delete cascading and orphan removal configurations visible in every updatable view, thus making it less surprising. It would make no sense to allow disabling delete cascading or orphan removal configurations because then the entity flush strategy would produce different results than the query flush strategy. Obviously the other way around i.e. enabling delete cascading or orphan removal if the entity attribute does not use these configurations, is very valid. Sometimes there are cases where delete cascading or orphan removal shouldn’t be done which means the cascading can’t be configured on the entity type attributes. This where Blaze Persistence entity views show their strength as they allow to control these configurations on a per-use case basis. 7.9. Conversion support As explained in the beginning, the vision for updatable entity views is to support the modelling of use case specific write models. Although most of the data that is generally updatable is mostly loaded already when starting a use case it is rarely necessary to make it updatable right away. Some use cases might require only a subset of the data to be updatable, while others require a different subset. To support modelling this appropriately it is possible to convert between entity views types. Imagine the following model for illustration purposes. @EntityView(Cat.class) interface KittenView { @IdMapping Long getId(); } @EntityView(Cat.class) interface CatBaseView extends KittenView { PersonView getOwner(); Set<KittenView> getKittens(); } @UpdatableEntityView @EntityView(Cat.class) interface CatOwnerUpdateView extends CatBaseView { @UpdatableMapping PersonView getOwner(); void setOwner(PersonView owner); } @UpdatableEntityView @EntityView(Cat.class) interface CatKittenUpdateView extends CatBaseView { @UpdatableMapping Set<KittenView> getKittens(); } When navigating to e.g. a detail UI for a Cat the CatBaseView would be loaded. If the UI had a special action to initiate a transfer to a different owner, doing that action would lead to the conversion of the CatBaseView to the CatOwnerUpdateView . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 75/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module CatBaseView catBaseView = //... CatOwnerUpdateView catOwnerUpdate = entityViewManager.convert(CatOwnerUpdateView.class, catBaseView); After setting the new owner and flushing the changes via EntityViewManager.save(EntityManager, Object) the view is converted back to the base view by invoking EntityViewManager.convert(Class, Object, ConvertOption…) again. CatOwnerUpdateView catBaseView = //... catBaseView = entityViewManager.convert(CatBaseView.class, catBaseView); When initiating the kitten update action the conversion would be done to CatKittenUpdateView . Keep in mind that most UIs do not necessarily work this way and that the added complexity might not be beneficial in all cases. Although this mechanism enables a clear separation for use cases, it might just as well be the case, that use cases are so small that it is better to have just a single write model. In some special cases like e.g. when simply changing a status of an object, it might not even be necessary to have an explicit write model. For such cases it is often more appropriate to have a specialized service method or event publishing of some sorts. Note that internally, the conversion feature is used for converting successfully persisted creatable entity views to their context specific declaration type. There are of course other possible use cases for this feature like e.g. conversion from a more detailed view to a view containing only a subset of the information. A very interesting use case is duplicating data. Such a use case requires to partially copy existing data such that it can be saved with a new identity. This is where the control over sub-attributes through EntityViewManager.convertWith(Class, Object, ConvertOption…) comes in handy. It allows to exclude attributes through ConvertOperationBuilder.excludeAttributes(String... attributes) and also convert specific attributes to a specific subtype with custom convert options through ConvertOperationBuilder.convertAttribute(String attributePath, Class<?> attributeViewClass, ConvertOption... convertOptions) . Imagine the following model for illustration purposes. @EntityView(Cat.class) interface KittenView { @IdMapping Long getId(); } @EntityView(Cat.class) interface CatView extends KittenView { PersonView getOwner(); Set<KittenView> getKittens(); } @CreatableEntityView @EntityView(Cat.class) interface CatCloneView extends CatView { void setOwner(PersonView owner); @UpdatableMapping(cascade = PERSIST) Set<KittenView> getKittens(); } A clone of a cat and kittens could be done by using CatView catView = //... catBaseView = entityViewManager.convertWith(CatCloneView.class, catView, ConvertOption.CREATE_NEW) .excludeAttribute("id") .excludeAttribute("kittens.id") .convertAttribute("kittens", CatCloneView.class, ConvertOption.CREATE_NEW) .convert(); The resulting object could then be persisted via EntityViewManager.save() which represents a duplicate of the original. It is also possible to convert an entity to a view Cat cat = //... catView = entityViewManager.convert(cat, CatView.class) https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 76/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 8. BasicUserType SPI Just like JPA providers offer an SPI to make use of custom types for basic values, Blaze Persistence also does. For read models, the type isn’t very important as the JPA provider handles the construction of the values and only provides entity views with object instance. Since write models need to handle change detection and mutability aspects of basic types i.e. non-subview type, the BasicUserType interface SPI is needed. 8.1. Supported types There are several well known types registered out of the box. boolean , java.lang.Boolean char , java.lang.Character byte , java.lang.Byte short , java.lang.Short int , java.lang.Integer long , java.lang.Long float , java.lang.Float double , java.lang.Double java.lang.String java.math.BigInteger , java.math.BigDecimal java.util.Date , java.sql.Time , java.sql.Date , java.sql.Timestamp java.util.Calendar , java.util.GregorianCalendar java.util.TimeZone , java.lang.Class java.util.UUID , java.net.URL java.util.Locale , java.util.Currency byte[] , java.lang.Byte[] char[] , java.lang.Character[] java.io.InputStream , java.sql.Blob java.sql.Clob , java.sql.NClob If found on the classpath, types for the following classes are registered java.time.LocalDate , java.time.LocalDateTime , java.time.LocalTime java.time.OffsetTime , java.time.OffsetDateTime , java.time.ZonedDateTime java.time.Duration , java.time.Instant java.time.MonthDay , java.time.Year , java.time.YearMonth , java.time.Period java.time.ZoneId , java.time.ZoneOffset If you miss a type you can register it via EntityViewConfiguration.registerBasicUserType(Class type, BasicUserType userType) . 8.2. Type support for MULTISET fetching One of the reasons why a custom basic user type might be desirable is the support for MULTISET fetching. When an entity view attribute defines MULTISET fetching, the basic user types of all types connected through that attribute must support MULTISET fetching. Normally this is not a problem, because well known user types are fully supported. Using embeddable types, custom composite types or JPA converted types in entity views is problematic though, because there is no way Blaze Persistence can figure out how to decompose the attribute to a string representation or construct the type from a string representation. This is where a custom basic user type implementation is desirable. Depending on whether the type is mutable or not, you can extend the com.blazebit.persistence.view.spi.type.AbstractMutableBasicUserType or com.blazebit.persistence.view.spi.type.ImmutableBasicUserType . An example implementation for an embeddable type composed of 2 attributes might look like the following: https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 77/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module public class QuantityBasicUserType extends com.blazebit.persistence.view.spi.type.AbstractMutableBasicUserType<Quantity> { @Override public Quantity deepClone(Quantity object) { // Clone the object if it is mutable. For immutable types, // you can extend com.blazebit.persistence.view.spi.type.ImmutableBasicUserType and don't need this method return object == null ? null : new Quantity(object); } @Override public String toStringExpression(String expression) { // A JPQL expression that produces a string format which is then parsed return "CONCAT(" + expression + ".value, '/', " + expression + ".unit)"; } @Override public Quantity fromString(CharSequence sequence) { // The CharSequence has the format as defined through toStringExpression // Now it must be de-serialized to a Quantity String s = sequence.toString(); String[] parts = s.split("\\/"); return new Quantity(new BigDecimal(parts[0]), parts[1]); } } Note that the deepClone method is only relevant for mutable types. Don’t forget to register the basic user type via EntityViewConfiguration.registerBasicUserType(Class type, BasicUserType userType) . EntityViewConfiguration configuration = ... configuration.registerBasicUserType(Quantity.class, new QuantityBasicUserType()); 8.3. Type support for write models When a basic type is used in a write model, it is very important that an appropriate BasicUserType is registered. If no basic user type is registered for a type, by default the com.blazebit.persistence.view.spi.type.MutableBasicUserType is used. This basic user type assume the type is mutable which will cause values of that type to always be assumed being dirty. Updatable entity views containing values of such a type are thus always considered being dirty which has the effect, that every call to EntityViewManager.save(EntityManager em, Object view) will cause a flush of attributes containing that value. The updatable-entity-view-change-model-api is also affected of this by always reporting such attributes as being dirty. Immutable types, like e.g. java.lang.String already does, can use the basic user type implementation com.blazebit.persistence.view.spi.type.ImmutableBasicUserType which assumes objects of the type are immutable. A proper basic user type implementation for mutable types, when based on the provided type com.blazebit.persistence.view.spi.type.AbstractMutableBasicUserType only needs an implementation for cloning a value. The cloned value is used to e.g. keep the initial state so that later changes can be detected by checking equality. 8.4. Type support for JPA managed types JPA managed types are also considered mutable by default, and since no dirty tracking information is available by default, objects of that such types are always considered dirty thus also always flushed. An integration with the native dirty tracking mechanism of the JPA provider might improve performance and will be considered in future versions. Entity types that handle change tracking manually, can implement a custom basic user type to improve the performance for usages of that entity type within updatable entity views, but are generally recommended to switch to subviews instead. For further information on the possible SPI methods consult the JavaDoc of the BasicUserType interface 8.5. Optimistic locking version type support To allow an attribute to be used as version for optimistic locking, the registered basic type also needs to implement the com.blazebit.persistence.view.spi.type.VersionBasicUserType interface. This type additionally requires to provide an implementation for returning the next version based on a given current version. 9. TypeConverter API The TypeConverter API is similar to the JPA AttributeConverter API as it allows to convert between an entity view model type and an underlying type. This is similar to the BasicUserType SPI but can also be used to convert view types to custom types. All this might sound very generic, but it is the foundation for the support of wrapping a type in a java.util.Optional . A TypeConverter is responsible for figuring out the actual underlying type of an entity view attribute type. In case of an attribute like e.g. Optional<Integer> getId() the TypeConverter for the java.util.Optional support determines the underlying type which is Integer . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 78/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Apart from this, the TypeConverter must also implement the conversion from the view type to the underlying type and the other way around. 9.1. Builtin TypeConverters There are several TypeConverters registered out of the box. Converters for java.sql.Blob , java.sql.Clob , java.sql.NClob to implement dirty tracking in coordination with a custom BasicUserType . If found on the classpath, TypeConverters for the following types are registered java.util.Optional for all Object types java.util.OptionalInt for java.lang.Integer java.util.OptionalLong for java.lang.Long java.util.OptionalDouble for java.lang.Double java.time.LocalDate for entity types java.util.Date java.sql.Date java.sql.Timestamp java.util.Calendar java.util.GregorianCalendar java.time.LocalDateTime for entity types java.util.Date java.sql.Timestamp java.util.Calendar java.util.GregorianCalendar java.time.Instant for entity types java.util.Date java.sql.Timestamp java.util.Calendar java.util.GregorianCalendar java.time.LocalTime for java.sql.Time java.util.GregorianCalendar for entity types java.util.Date java.sql.Timestamp java.util.Calendar for entity types java.util.Date java.sql.Timestamp java.util.Date for entity types java.util.Calendar java.util.GregorianCalendar If you miss a TypeConverter you can register it via EntityViewConfiguration.registerTypeConverter(Class entityModelType, Class viewModelType, TypeConverter typeConverter) . 10. Updatable Entity View Change Model Updatable entity views are not only better write model DTOs, but also allow to retrieving logical changes via the ChangeModel API. Using updatable entity views allows the persistence model to be efficiently updated, but the cost for doing that is hiding the persistent/initial state from the user. Oftentimes part of the persistent/initial state is compared with values that are about to be written to detect logical changes. Since updatable entity views handle the persistent state behind the scenes, such a manual comparison isn’t possible. Thanks to the ChangeModel API it is unnecessary. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 79/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module The ChangeModel API entry point is EntityViewManager.getChangeModel(Object view) and returns the change model for a given updatable entity view. A change model instance provides access to the initial and current state of an object and the ChangeKind of a change model. Singular change models also give access to the change models of the respective attributes of an entity view type. Plural change models additionally give access to the added, removed and mutated element change models. A map change model also allows to distinguish between element and key change models. 10.1. Change Model API overview To detect if a model or one of it’s child models is dirty, one can use the ChangeModel.isDirty() method. The actual change models of dirty elements within a SingularChangeModel can be retrieved via SingularChangeModel.getDirtyChanges() . Only attributes of the queried object are reported as change models i.e. only a single level. The singular change models allow access to the attributes change models either via attribute path or via the metamodel attribute objects by using one of the overloaded SingularChangeModel.get(String attributePath) methods. The term path implicates a nested attribute access is possible, which is the case, but beware that accessing attributes of collection elements will result in an exception unless the SingularChangeModel.getAll(String attributePath) variant is used which returns a list of change models instead of a single one. Another notable feature the singular change model provides is the checking for dirtyness of a specific attribute path. Instead of materializing every change model along the path, the SingularChangeModel.isDirty(String attributePath) method only reports the dirtyness of the object accessible through the given attribute path. A variant of this method SingularChangeModel.isChanged(String attributePath) will return early if one of the parent attributes was updated i.e. the identity was changed. The plural change model is similar in the respect that it provides analogous methods that simply return a list of change models instead of a single one. It also allows to access the change models of the added, removed or mutated elements separately. To access all dirty changes similar to what is possible with SingularChangeModel#getDirtyChanges() , plural change models provide the method PluralChangeModel.getElementChanges() for doing the same. The map change model additionally allows to differentiate between changes to key objects and element objects. It offers methods to access the key changes as well as the overall object changes with analogously named methods getAddedObjects() , getAddedKeys() etc. 10.2. Transaction support The change model implementation gains it’s insights by inspecting the dirty tracking information of the actual objects. Since a transaction commit will flush dirty changes i.e. the dirtyness is resetted, change model objects won’t report any dirty changes after a commit. If information about the change models should be retained after a transaction commit, it must be serialized with a custom mechanism. When a rollback occurs, the dirtyness is restored to be able to commit again after doing further changes which also means that change models will work as expected. 10.3. User type support The Change Model API builds on top of the BasicUserType foundation and it is thus essential to have a correct implementation for the type. Unknown types are considered mutable which has the effect, that objects of that type are always considered dirty. Provide a deepClone implementation or mark the type as immutable to avoid this. 11. Entity View Builder API The entity view builder API allows to build entity view objects through a builder API. You can assign attributes individually by using the various with methods on EntityViewBuilderBase and finally build a fully functional entity view object. The entity view builder API entry point is EntityViewManager.createBuilder(Class<X> entityViewClass) and returns a builder for the given entity view class. It is also possible to create a builder and copy the state from an existing view to a builder via EntityViewManager.createBuilder(X entityView) . 12. Spring Data integration Apart from a plain Spring integration which is handy for configuring and providing an EntityViewManager for injection, there is also a Spring Data integration module which tries to make using entity views with Spring Data as convenient as using entities. 12.1. Setup To setup the project for Spring Data you have to add dependencies as described in the Setup section and make beans available for CriteriaBuilderFactory and EntityViewManager instances as laid out in the Spring environment section. In short, the following Maven dependencies are required https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 80/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-2.7</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-hibernate-5.6</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> For Spring-Data version 2.6, 2.5, 2.4, 2.3, 2.2, 2.1, 2.0 or 1.x use the blaze-persistence-integration-spring-data artifact with the respective suffix 2.6 , 2.5 2.4 , 2.3 , 2.2 , 2.1 , 2.0 , 1.x . If you are using Jakarta APIs and Spring Framework 6+ / Spring Boot 3+, use this <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-3.1</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-hibernate-6.2</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> The dependencies for other JPA providers or other versions can be found in the core module setup section. A possible bean configuration for the required beans CriteriaBuilderFactory and EntityViewManager in short might look like this. @Configuration public class BlazePersistenceConfiguration { @PersistenceUnit private EntityManagerFactory entityManagerFactory; @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @Lazy(false) public CriteriaBuilderFactory createCriteriaBuilderFactory() { CriteriaBuilderConfiguration config = Criteria.getDefault(); // do some configuration return config.createCriteriaBuilderFactory(entityManagerFactory); } } @Configuration public class BlazePersistenceConfiguration { @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @Lazy(false) // inject the criteria builder factory which will be used along with the entity view manager public EntityViewManager createEntityViewManager(CriteriaBuilderFactory cbf, EntityViewConfiguration entityViewConfiguration) { return entityViewConfiguration.createEntityViewManager(cbf); } } To enabling Blaze JPA repositories, annotate your configuration or application class with @EnableBlazeRepositories . Optionally specify a custom basePackage for repository class scanning and a custom entityManagerFactoryRef. @EnableBlazeRepositories 12.1.1. Spring Boot Devtools It is important to note that Blaze Persistence currently does not integrate with Spring Boot Devtools ( org.springframework.boot:spring-bootdevtools ). Spring Boot Devtools uses a separate RestartClassloader to load classes that frequently change to allow for faster application https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 81/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module restarts. You may experience issues with active Spring Boot Devtools when using Blaze Persistence Entity-Views because the entity classes in the JPA metamodel are loaded by the RestartClassloader whereas the entity classes you annotate your entity views with are loaded by the base application classloader. This can lead to errors at startup time like: The entity class '<your-entity>' used for the entity view '<your-entity-view>' could not be found in the persistence unit! To work around this issue you either need to completely disable Spring Boot Devtools or alternatively, exclude your entity classes from the RestartClassloader by adding properties prefixed with restart.exclude to your META-INF/spring-devtools.properties . 12.2. Features The integration comes with a convenience base interface com.blazebit.persistence.spring.data.repository.EntityViewRepository that you can use for your repository definitions. Assume we have the following entity view: @EntityView(Cat.class) public interface SimpleCatView { @IdMapping public getId(); String getName(); @Mapping("LOWER(name)") String getLowerCaseName(); Integer getAge(); } A very simple repository might look like this: @Transactional(readOnly = true) public interface SimpleCatViewRepository extends EntityViewRepository<SimpleCatView, Long> { List<SimpleCatView> findByLowerCaseName(String lowerCaseName); } Since we use EntityViewRepository as a base interface we inherit the most commonly used repository methods. You can now use this repository as any other Spring Data repository: @Controller public class MyCatController { @Autowired private SimpleCatViewRepository simpleCatViewRepository; public Iterable<SimpleCatView> getCatDataForDisplay() { return simpleCatViewRepository.findAll(); } public SimpleCatView findCatByName(String name) { return simpleCatViewRepository.findByLowerCaseName(name.toLowerCase()); } } Spring Data Specifications can be used without restrictions. There is also the convenience base interface com.blazebit.persistence.spring.data.repository.EntityViewSpecificationExecutor that can be extended from. @Transactional(readOnly = true) public interface SimpleCatViewRepository extends EntityViewRepository<SimpleCatView, Long>, EntityViewSpecificationExecutor<SimpleCatView, Cat> { } @Controller public class MyCatController { @Autowired private SimpleCatViewRepository simpleCatViewRepository; public Iterable<SimpleCatView> getCatDataForDisplay(final int minAge) { return simpleCatViewRepository.findAll(new Specification<Cat>() { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 82/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @Override public Predicate toPredicate(Root<Cat> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { return criteriaBuilder.ge(root.<Integer>get("age"), minAge); } }); } Because Spring Data Specifications work on a JPA criteria builder we provide BlazeSpecification as an alternative that accepts a Blaze Persistence criteria builder but works analogously apart from that. @Transactional(readOnly = true) public interface SimpleCatViewRepository extends Repository<Cat, Long> { Iterable<SimpleCatView> findAll(BlazeSpecification specification); } @Controller public class MyCatController { @Autowired private SimpleCatViewRepository simpleCatViewRepository; public Iterable<SimpleCatView> getCatDataForDisplay(final int minAge) { return simpleCatViewRepository.findAll(new BlazeSpecification() { @Override public void applySpecification(String rootAlias, CriteriaBuilder<?> builder) { builder.where("age").ge(minAge); } }); } The integration handles ad-hoc uses of @EntityGraph by adapting the query generation through call of CriteriaBuilder.fetch() rather than passing the entity graphs as hints. Another notable feature the integration provides is the support for the return type KeysetAwarePage as a replacement for Page . By using KeysetAwarePage the keyset pagination feature is enabled for the repository method. @Transactional(readOnly = true) public interface KeysetAwareCatViewRepository extends Repository<Cat, Long> { KeysetAwarePage<SimpleCatView> findAll(Pageable pageable); } Note that the Pageable should be an instance of KeysetPageable if keyset pagination should be used. A KeysetPageable can be retrieved through the KeysetAwarePage or manually by constructing a KeysetPageRequest . Note that constructing a KeysetPageRequest or actually the contained KeysetPage manually is not recommended. When working with Spring WebMvc, the Spring Data WebMvc or WebFlux integrations might come in handy. For stateful server side frameworks, it’s best to put the KeysetAwarePage into a session like storage to be able to use the previousOrFirst() and next() methods for retrieving KeysetPageable objects. When using parameters in an entity view, these parameters are usually passed in as optional parameters to an EntityViewSetting rather than normal query parameters. You can customize the EntityViewSetting object that is used by providing a EntityViewSettingProcessor like so. @Transactional(readOnly = true) public interface SimpleCatViewRepository extends Repository<Cat, Long> { List<SimpleCatView> findAll(EntityViewSettingProcessor<SimpleCatView> processor); } simpleCatViewRepository.findAll(setting -> setting.withOptionalParameter("language", Locale.US)); To just pass optional parameters, one can also annotate a parameter with @OptionalParam to designate it as being an optional parameter and to be included in the generated EntityViewSetting . @Transactional(readOnly = true) public interface SimpleCatViewRepository extends Repository<Cat, Long> { List<SimpleCatView> findAll(@OptionalParam("language") Locale language); } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 83/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module All other Spring Data repository features like restrictions, pagination, slices and ordering are supported as usual. Please consult the Spring Data documentation for further information. 12.3. Spring Data WebMvc integration The Spring Data WebMvc integration offers similar pagination features for keyset pagination to what Spring Data WebMvc integration already offers for normal offset pagination. 12.3.1. Setup To setup the project for Spring Data WebMvc you have to add the following additional dependency. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-webmvc</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta APIs and Spring 6+ <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-webmvc-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> 12.3.2. Usage First, a keyset pagination enabled repository is needed which can be done by using KeysetAwarePage as return type. @Transactional(readOnly = true) public interface KeysetAwareCatViewRepository extends Repository<Cat, Long> { KeysetAwarePage<SimpleCatView> findAll(Pageable pageable); } A controller can then use this repository like the following: @RestController public class MyCatController { @Autowired private KeysetAwareCatViewRepository simpleCatViewRepository; @RequestMapping(path = "/cats", method = RequestMethod.GET) public Page<SimpleCatView> getCats(@KeysetConfig(Cat.class) KeysetPageable pageable) { return simpleCatViewRepository.findAll(pageable); } } Note that Blaze Persistence imposes some very important requirements that have to be fulfilled There must always be a sort specification The last sort specification must be a unique identifier For the keyset pagination to kick in, the client has to remember the values by which the sorting is done of the first and the last element of the result. The values then need to be passed to the next request as JSON encoded query parameters. The values of the first element should use the parameter lowest and the last element the parameter highest . The following will illustrate how this works. First, the client makes an initial request. GET /cats?page=0&size=3&sort=id,desc { content: [ { id: 10, name: 'Felix', age: 10 }, { id: 9, name: 'Robin', age: 4 }, https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 84/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module { id: 8, name: 'Billy', age: 7 } ] } It’s the responsibility of the client to remember the attributes by which it sorts of the first and last element. In this case, {id: 10} will be remembered as lowest and {id: 8} as highest . The client also has to remember the page/offset and size which was used to request this data. When the client then wants to switch to the next page/offset, it has to pass lowest and highest as parameters as well as prevPage / prevOffset representing the page/offset that was used before. Note that the following is just an example for illustration. Stringified JSON objects in JavaScript should be encoded view encodeURI() before being used as query parameter. GET /cats?page=1&size=3&sort=id,desc&prevPage=0&lowest={id:10}&highest={id:8} { content: [ { id: 7, name: 'Kitty', age: 1 }, { id: 6, name: 'Bob', age: 8 }, { id: 5, name: 'Frank', age: 14 } ] } This will make use of keyset pagination as can be seen by looking at the generated JPQL or SQL query. Note that the client should drop or forget the lowest , highest and prevPage / prevOffset values when the page size changes and it is expected to show data not connected to the last page the sorting changes the filtering changes For a full AngularJS example see the following example project. 12.3.3. Entity view deserialization The Spring Data WebMvc integration depends on the Jackson integration and automatically provides support for deserializing entity views. Currently, there is no support for constructor injection into entity views, so entity view attributes that should be deserializable should have a setter. @EntityView(Cat.class) @UpdatableEntityView public interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); } public interface CatViewRepository extends Repository<Cat, Long> { public CatUpdateView save(CatUpdateView catCreateView); } A controller can then deserialize entity views of request bodies by simply using it as @RequestBody annotated parameter like this: @RestController public class MyCatController { @Autowired private CatViewRepository catViewRepository; @RequestMapping(path = "/cats", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<String> updateCat(@RequestBody CatUpdateView catView) { catViewRepository.save(catView); return ResponseEntity.ok(catView.getId().toString()); } } In the example above, the entity view id will be sourced from the request body. Alternatively, it is also possible to retrieve the id from a path variable like this: https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 85/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @RestController public class MyCatController { @Autowired private CatViewRepository catViewRepository; @RequestMapping(path = "/cats/{id}", method = RequestMethod.PUT, consumes = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<String> updateCat(@EntityViewId("id") @RequestBody CatUpdateView catView) { catViewRepository.save(catView); return ResponseEntity.ok(catView.getId().toString()); } } 12.4. Spring Data WebFlux integration The Spring Data WebFlux integration provides the same features as the Spring Data WebMvc integration. In addition it also supports using Mono and Flux types. 12.4.1. Setup To setup the project for Spring Data WebFlux you have to add the following additional dependency. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-spring-data-webflux</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> 12.4.2. Usage A controller can be written like for Spring Data WebMvc: @RestController public class MyCatController { @Autowired private KeysetAwareCatViewRepository simpleCatViewRepository; @RequestMapping(path = "/cats", method = RequestMethod.GET) public Page<SimpleCatView> getCats(@KeysetConfig(Cat.class) KeysetPageable pageable) { return simpleCatViewRepository.findAll(pageable); } } It can also use Mono or Flux types, but note that Spring Data JPA repositories don’t support reactive access. @Controller public class MyCatController { @Autowired private KeysetAwareCatViewRepository simpleCatViewRepository; @Bean public RouterFunction<ServerResponse> createRouterFunctions(CatRestController controller) { return RouterFunctions.route(RequestPredicates.GET("/cats"), this::getCats); } public Flux<SimpleCatView> getCats() { return Flux.fromIterable(simpleCatViewRepository.findAll().getContent()); } } 13. Spring HATEOAS integration In addition to the Spring Data and Spring WebMvc or WebFlux integration, we also provide an integration for Spring HATEOAS 1.0+ that allows to create keyset aware pagination links. 13.1. Setup To setup the project for Spring HATEOAS you first have to setup the spring data integration as described in the Setup section. In short, the following Maven dependencies are required https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 86/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module <dependency> <groupId>${project.groupId}</groupId> <artifactId>blaze-persistence-integration-spring-hateoas-webmvc</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-hibernate-5.6</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> or if you are using Jakarta APIs and Spring 6+ <dependency> <groupId>${project.groupId}</groupId> <artifactId>blaze-persistence-integration-spring-hateoas-webmvc-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-hibernate-6.2</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> The dependencies for other JPA providers or other versions can be found in the core module setup section. Note that Spring HATEOAS 1.0 requires Spring Data 2.2 and Spring HATEOAS 1.1 requires Spring Data 2.3. 13.2. Features The integration provides a custom PagedResourcesAssembler that will generate proper keyset aware pagination links when provided with a KeysetAwarePage object. Assume we have the following entity view: @EntityView(Cat.class) public interface SimpleCatView { @IdMapping public getId(); String getName(); @Mapping("LOWER(name)") String getLowerCaseName(); Integer getAge(); } A very simple repository might look like this: @Transactional(readOnly = true) public interface KeysetAwareCatViewRepository extends Repository<Cat, Long> { KeysetAwarePage<SimpleCatView> findAll(Pageable pageable); } A controller can then inject the KeysetPageable object along with a KeysetAwarePagedResourcesAssembler and use it like in the following example: @RestController public class MyCatController { @Autowired private KeysetAwareCatViewRepository simpleCatViewRepository; @RequestMapping(path = "/cats", method = RequestMethod.GET, produces = { "application/hal+json" }) public PagedModel<EntityModel<SimpleCatView>> getCats( @KeysetConfig(Cat.class) KeysetPageable pageable, https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 87/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module KeysetAwarePagedResourcesAssembler<SimpleCatView> assembler) { return assembler.toModel(simpleCatViewRepository.findAll(pageable)); } } The PagedModel object could also be used to generate a rel HTTP header like this: @RestController public class MyCatController { @Autowired private KeysetAwareCatViewRepository simpleCatViewRepository; @RequestMapping(path = "/cats", method = RequestMethod.GET) public PagedModel<EntityModel<SimpleCatView>> getCats( @KeysetConfig(Cat.class) KeysetPageable pageable, KeysetAwarePagedResourcesAssembler<SimpleCatView> assembler) { Page<DocumentView> resultPage = simpleCatViewRepository.findAll(pageable); MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); for (Link link : assembler.toModel(resultPage).getLinks()) { headers.add(HttpHeaders.LINK, link.toString()); } return new HttpEntity<>(resultPage, headers); } } For more information about the Spring-Data or Spring WebMvc integration, on which the Spring HATEOAS support is based on, take a look into the Spring-Data chapter. For a full example see the following example project. 14. DeltaSpike Data integration Blaze Persistence provides an integration with DeltaSpike Data to create entity view based repositories. 14.1. Setup To setup the project for DeltaSpike Data you have to add the entity view and CDI integration dependencies as described in the getting started section along with the integration dependencies for your JPA provider as described in the core module setup section. In addition, the following Maven dependencies are required: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-deltaspike-data-api</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-deltaspike-data-impl-1.8</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> This will also work for DeltaSpike Data 1.9. If you still work with DeltaSpike Data 1.7 you will have to use a different integration as DeltaSpike Data 1.9 and 1.8 changed quite a bit. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-deltaspike-data-impl-1.7</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> You also need to make beans available for CriteriaBuilderFactory and EntityViewManager as laid out in the CDI environment section. 14.2. Features To mark a class or an interface as repository, use the DeltaSpike org.apache.deltaspike.data.api.Repository annotation. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 88/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @Repository(forEntity = Cat.class) public interface CatViewRepository { List<SimpleCatView> findByLowerCaseName(String lowerCaseName); } The integration provides the following base interfaces that you may optionally extend to define entity view repositories: com.blazebit.persistence.deltaspike.data.EntityViewRepository provides simple base methods. com.blazebit.persistence.deltaspike.data.FullEntityViewRepository adds JPA criteria support to the com.blazebit.persistence.deltaspike.data.EntityViewRepository interface. @Repository public abstract class CatViewRepository extends FullEntityViewRepository<Cat, SimpleCatView, Long> { public List<SimpleCatView> findByAge(final int minAge) { return criteria().gt(Cat_.age, minAge) .select(SimpleCatView.class).orderAsc(Cat_.id).getResultList(); } } Similar to what Spring Data offers, it is also possible to make use of a Specification which essentially is a callback that allows to refine a query. @Repository(forEntity = Cat.class) public interface SimpleCatViewRepository { List<SimpleCatView> findAll(Specification spec); } @Path("cats") public class MyCatController { @Inject private SimpleCatViewRepository simpleCatViewRepository; @GET public List<SimpleCatView> getCatDataForDisplay(@QueryParam("minage") final int minAge) { return simpleCatViewRepository.findAll(new Specification<Cat>() { @Override public Predicate toPredicate(Root<Cat> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { return criteriaBuilder.ge(root.<Integer>get("age"), minAge); } }); } The integration handles ad-hoc uses of @EntityGraph by adapting the query generation through call of CriteriaBuilder.fetch() rather than passing the entity graphs as hints. Another notable feature the integration provides is the support for a Pageable object with Page return type similar to what Spring Data offers. The integration also supports the return type KeysetAwarePage . By using KeysetAwarePage the keyset pagination feature is enabled for the repository method. @Repository(forEntity = Cat.class) public interface KeysetAwareCatViewRepository { KeysetAwarePage<SimpleCatView> findAll(Pageable pageable); } Note that the Pageable should be an instance of KeysetPageable if keyset pagination should be used. A KeysetPageable can be retrieved through the KeysetAwarePage or manually by constructing a KeysetPageRequest . Note that constructing a KeysetPageRequest or actually the contained KeysetPage manually is not recommended. When working with JAX-RS, the DeltaSpike Data Rest integration might come in handy. For stateful server side frameworks, it’s best to put the KeysetAwarePage into a session like storage to be able to use the previousOrFirst() and next() methods for retrieving KeysetPageable objects. All other DeltaSpike Data repository features like restrictions, explicit offset pagination, returning QueryResult and others are supported as usual. Please consult the DeltaSpike Data documentation for further information. 14.3. DeltaSpike Data Rest integration The DeltaSpike Data Rest integration offers similar pagination features for normal and keyset pagination to what Spring Data offers for normal offset based pagination. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 89/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 14.3.1. Setup To setup the project for using DeltaSpike along with JAX-RS you have to add the following additional dependency. <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-deltaspike-data-rest-api</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-deltaspike-data-rest-impl</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> This will also pull in the JAX-RS integration as well as the Jackson integration that allows for deserializing entity views in JAX-RS controllers. 14.3.2. Usage First, a keyset pagination enabled repository is needed. @Repository(forEntity = Cat.class) public interface KeysetAwareCatViewRepository { KeysetAwarePage<SimpleCatView> findAll(Pageable pageable); } A controller can then use this repository like the following: @Path("cats") public class MyCatController { @Inject private KeysetAwareCatViewRepository simpleCatViewRepository; @GET public Page<SimpleCatView> getCats(@KeysetConfig(Cat.class) KeysetPageable pageable) { return simpleCatViewRepository.findAll(pageable); } } Note that Blaze Persistence imposes some very important requirements that have to be fulfilled There must always be a sort specification The last sort specification must be a unique identifier For the keyset pagination to kick in, the client has to remember the values by which the sorting is done of the first and the last element of the result. The values then need to be passed to the next request as JSON encoded query parameters. The values of the first element should use the parameter lowest and the last element the parameter highest . The following will illustrate how this works. First, the client makes an initial request. GET /cats?page=0&size=3&sort=id,desc { content: [ { id: 10, name: 'Felix', age: 10 }, { id: 9, name: 'Robin', age: 4 }, { id: 8, name: 'Billy', age: 7 } ] } It’s the responsibility of the client to remember the attributes by which it sorts of the first and last element. In this case, {id: 10} will be remembered as lowest and {id: 8} as highest . The client also has to remember the page/offset and size which was used to request this data. When the client then wants to switch to the next page/offset, it has to pass lowest and highest as parameters as well as prevPage / prevOffset representing the page/offset that was used before. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 90/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Note that the following is just an example for illustration. Stringified JSON objects in JavaScript should be encoded view encodeURI() before being used as query parameter. GET /cats?page=1&size=3&sort=id,desc&prevPage=0&lowest={id:10}&highest={id:8} { content: [ { id: 7, name: 'Kitty', age: 1 }, { id: 6, name: 'Bob', age: 8 }, { id: 5, name: 'Frank', age: 14 } ] } This will make use of keyset pagination as can be seen by looking at the generated JPQL or SQL query. Note that the client should drop or forget the lowest , highest and prevPage / prevOffset values when the page size changes and it is expected to show data not connected to the last page the sorting changes the filtering changes For a full AngularJS example see the following example project. 14.3.3. Entity view deserialization The DeltaSpike Data Rest integration depends on the JAX-RS integration and thus also on the Jackson integration through which it automatically provides support for deserializing entity views. Currently, there is no support for constructor injection into entity views, so entity view attributes that should be deserializable should have a setter. @EntityView(Cat.class) @UpdatableEntityView public interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); } @Repository(forEntity = Cat.class) public interface CatViewRepository { public CatUpdateView save(CatUpdateView catCreateView); } The JAX-RS integration can automatically deserialize entity views of request bodies by simply using the entity view type as parameter like this: @Path("") public class MyCatController { @Inject private CatViewRepository catViewRepository; @POST @Path("/cats") @Consumes(MediaType.APPLICATION_JSON) public Response updateCat(CatUpdateView catUpdateView) { catViewRepository.save(catUpdateView); return Response.ok(catUpdateView.getId().toString()).build(); } } 15. JAX-RS integration The JAX-RS integration module serves as the API which contains the @EntityViewId annotation. The MessageBodyReader and ParamConverter implementations to integrate serialization frameworks with JAX-RS are available for Jackson integration JSONB integration https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 91/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module At some point there will also be support for JAXB The integration is discovered automatically through the javax.ws.rs.ext.Providers ServiceLoader contract. Simply putting the artifact on the classpath is enough for the integration. 15.1. Setup To use the Jackson integration directly you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jaxrs-jackson</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jaxrs-jackson-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> To use the JSONB integration directly you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jaxrs-jsonb</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jaxrs-jsonb-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>runtime</scope> </dependency> 15.2. Features The main feature is the possibility to use entity views just like normal POJOs that can be deserialized automatically. @EntityView(Cat.class) @UpdatableEntityView public interface CatUpdateView { @IdMapping Long getId(); String getName(); void setName(String name); } The JAX-RS integration can automatically deserialize entity views of request bodies by simply using the entity view type as parameter like this: @Path("") public class MyCatController { @Inject private EntityManager em; @Inject private EntityViewManager evm; @POST @Path("/cats") @Consumes(MediaType.APPLICATION_JSON) @Transactional public Response updateCat(CatUpdateView catUpdateView) { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 92/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module evm.save(em, catUpdateView); return Response.ok(catUpdateView.getId().toString()).build(); } } In the example above, the entity view id will be sourced from the request body. Alternatively, it is also possible to retrieve the id from a path variable like this: @Path("") public class MyCatController { @Inject private EntityManager em; @Inject private EntityViewManager evm; @PUT @Path("/cats/{id}") @Consumes(MediaType.APPLICATION_JSON) @Transactional public Response updateCat(@EntityViewId("id") CatUpdateView catUpdateView) { evm.save(em, catUpdateView); return Response.ok(catUpdateView.getId().toString()).build(); } } 16. GraphQL integration GraphQL is a language for data communication that requires a schema. Defining that schema and keeping it in sync with the model can become a very painful task. This is where the GraphQL integration comes to rescue as it is capable of contributing entity view types to the GraphQL schema and also create a EntityViewSetting object from a GraphQL DataFetchingEnvironment with full support for partial loading as defined by selection lists. In addition, it also has support for the Relay pagination specification to allow easy keyset pagination. 16.1. Setup To use the GraphQL integration you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> Note that the integration works with the de facto standard runtime for GraphQL which is graphql-java . At least version 17.3 is required. 16.2. Usage The integration works by contributing entity view type as GraphQL types to a GraphQL TypeDefinitionRegistry or GraphQLSchema.Builder . Let’s consider the following entity views @EntityView(Person.class) public interface PersonIdView { @IdMapping Long getId(); } @EntityView(Person.class) public interface PersonSimpleView extends PersonIdView { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 93/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module String getName(); } @EntityView(Cat.class) public interface CatSimpleView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) public interface CatWithOwnerView extends CatSimpleView { PersonSimpleView getOwner(); } This will generate the following GraphQL types type CatSimpleView { id: ID! name: String } type CatWithOwnerView { id: ID! name: String owner: PersonSimpleView } type PersonIdView { id: ID! } type PersonSimpleView { id: ID! name: String } The integration happens through the class GraphQLEntityViewSupportFactory , which produces a GraphQLEntityViewSupport which you can then use. The created GraphQLEntityViewSupport object is a singleton that should only be created during boot and can be used for creating EntityViewSetting objects in GraphQL DataFetcher implementations. Usually, the object is exposed as @Bean in Spring or @ApplicationScoped bean in CDI. The GraphQLEntityViewSupport.createSetting() and GraphQLEntityViewSupport.createPaginatedSetting() methods inspect the data fetching environment and know which entity view type is needed, but you can also provide a custom EntityViewSetting with a custom entity view type or some prepared filters/sorters. In addition, these methods will determine what to fetch according to the DataFetchingEnvironment.getSelectionList() . This will lead to the optimal query to be generated for the fields that are requested. This is not only about skipping select items, but also about avoiding unnecessary joins! 16.2.1. Plain graphql-java setup With just graphql-java, you have to provide a schema and do the runtime-wiring. This could look like the following with a sample schema: type Query { catById(id: ID!): CatWithOwnerView } and the setup logic: EntityViewManager evm = ... // Read in the GraphQL schema URL url = Resources.getResource("schema.graphqls"); String sdl = Resources.toString(url, StandardCharsets.UTF_8); TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(sdl); // Configure how to integrate entity views boolean defineNormalTypes = true; boolean defineRelayTypes = true; GraphQLEntityViewSupportFactory graphQLEntityViewSupportFactory = new GraphQLEntityViewSupportFactory(defineNormalTypes, defineRelayTypes); graphQLEntityViewSupportFactory.setImplementRelayNode(false); graphQLEntityViewSupportFactory.setDefineRelayNodeIfNotExist(true); // Integrate and create the support class for extraction of EntityViewSetting objects GraphQLEntityViewSupport graphQLEntityViewSupport = graphQLEntityViewSupportFactory.create(typeRegistry, evm); Next, one needs to define a DataFetcher for the defined query catById like so https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 94/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module CatViewRepository repository; RuntimeWiring.newRuntimeWiring() .type(TypeRuntimeWiring.newTypeWiring("Query") .dataFetcher("catById", new DataFetcher() { @Override public Object get(DataFetchingEnvironment dataFetchingEnvironment) { return repository.findById( graphQLEntityViewSupport.createSetting(dataFetchingEnvironment), Long.valueOf(dataFetchingEnvironment.getArgument("id")) ); } }) ) .build(); Finally, the RuntimeWiring and TypeDefinitionRegistry are joined together to a GraphQL schema which is required for the GraphQL runtime. SchemaGenerator schemaGenerator = new SchemaGenerator(); return schemaGenerator.makeExecutableSchema(typeRegistry, runtimeWiring); Naming types or additional fields Types can be explicitly named by putting the @GraphQLName annotation on a type. @GraphQLName("TheEntity") @EntityView(MyEntity.class) public interface MyEntityView { //... } Additional fields can be declared as getter methods that follow the Java beans convention: @EntityView(MyEntity.class) public interface MyEntityView { //... default String getAdditionalField() { return "some data"; } @GraphQLName("additionalData") default String getData() { return "more data"; } } In this case the schema for MyEntityView will contain two additional fields additionalField and additionalData . Note that when the GraphQL field name does not match the property name of a getter method like in the previous example, an additional data fetcher must be declared for the field: RuntimeWiring.newRuntimeWiring() .type(TypeRuntimeWiring.newTypeWiring("MyEntityView") .dataFetcher("additionalData", new DataFetcher() { @Override public Object get(DataFetchingEnvironment dataFetchingEnvironment) { Object source = dataFetchingEnvironment.getSource(); if (source instanceof MyEntityView) { return ((MyEntityView) source).getData(); } return null; } }) ) .build(); Ignoring types or fields Types can be explicitly ignored by putting the @GraphQLIgnore annotation on a type. It’s also possible to prevent getters in entity views to appear as fields in the GraphQL type schema, by annotating the getter method with the @GraphQLIgnore annotation. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 95/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @EntityView(MyEntity.class) public interface MyEntityView { //... @GraphQLIgnore default String getAdditionalField() { return "some data"; } } Forcing non-null types on fields The type of a GraphQL field can be forced to be non-null by putting the @GraphQLNonNull annotation on a getter method. Usually, the integration is able to figure out non-null types through its nullability analysis of mapping expressions, but for custom methods or cases when the analysis fails, the explicit annotation can be used. @EntityView(MyEntity.class) public interface MyEntityView { //... @GraphQLNonNull default String getAdditionalField() { return "some data"; } } For a full example see the following example project. 16.2.2. Netflix DGS setup To use the Netflix DGS integration you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql-dgs</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta APIs <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql-dgs-7.0</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> The Netflix DGS setup is similar to the plain graphql-java one, as you have to provide a schema as well, although you have to follow a convention. A schema must be located in a schema folder and have a suffix of *.graphls according to the documentation. The runtime-wiring looks different though as it supports an annotation based model. This could look like the following with a sample schema: type Query { catById(id: ID!): CatWithOwnerView } Next, one needs to define a DataFetcher for the defined query catById like so @DgsComponent public class CatFetcher { @Autowired CatViewRepository repository; @Autowired GraphQLEntityViewSupport graphQLEntityViewSupport; @DgsQuery public CatWithOwnerView catById(@InputArgument("id") Long id, DataFetchingEnvironment dataFetchingEnvironment) { return repository.findById(graphQLEntityViewSupport.createSetting(dataFetchingEnvironment), Long.valueOf(dataFetchingEnvironment.getArgument("id"))); https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 96/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module } } Naming types or additional fields Types can be explicitly named by putting the @GraphQLName annotation on a type. @GraphQLName("TheEntity") @EntityView(MyEntity.class) public interface MyEntityView { //... } Additional fields can be declared as getter methods that follow the Java beans convention: @EntityView(MyEntity.class) public interface MyEntityView { //... default String getAdditionalField() { return "some data"; } @GraphQLName("additionalData") default String getData() { return "more data"; } } In this case the schema for MyEntityView will contain two additional fields additionalField and additionalData . Note that when the GraphQL field name does not match the property name of a getter method like in the previous example, an additional data fetcher must be declared for the field: @DgsComponent public class GraphQLExtensionApi { @DgsData(parentType = "MyEntityView", field = "theData") public String getNodeData(DataFetchingEnvironment dataFetchingEnvironment) { Object source = dataFetchingEnvironment.getSource(); if (source instanceof MyEntityView) { return ((MyEntityView) source).getData(); } return null; } } Ignoring types or fields Types can be explicitly ignored by putting the @GraphQLIgnore annotation on a type. It’s also possible to prevent getters in entity views to appear as fields in the GraphQL type schema, by annotating the getter method with the @GraphQLIgnore annotation. @EntityView(MyEntity.class) public interface MyEntityView { //... @GraphQLIgnore default String getAdditionalField() { return "some data"; } } Forcing non-null types on fields The type of a GraphQL field can be forced to be non-null by putting the @GraphQLNonNull annotation on a getter method. Usually, the integration is able to figure out non-null types through its nullability analysis of mapping expressions, but for custom methods or cases when the analysis fails, the explicit annotation can be used. @EntityView(MyEntity.class) public interface MyEntityView { //... https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 97/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module @GraphQLNonNull default String getAdditionalField() { return "some data"; } } For a full example see the following example project. 16.2.3. SPQR setup To use the SPQR GraphQL integration you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql-spqr</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-graphql-spqr-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> The SPQR configuration is very simple and since the framework is fully declarative, you don’t need a dedicated GraphQL schema definition. @Configuration public class GraphQLProvider { @Autowired EntityViewManager evm; @Autowired GraphQLSchema graphQLSchema; private GraphQLEntityViewSupport graphQLEntityViewSupport; @PostConstruct public void init() { GraphQLEntityViewSupportFactory graphQLEntityViewSupportFactory = new GraphQLEntityViewSupportFactory(false, false); graphQLEntityViewSupportFactory.setImplementRelayNode(false); graphQLEntityViewSupportFactory.setDefineRelayNodeIfNotExist(false); this.graphQLEntityViewSupport = graphQLEntityViewSupportFactory.create(graphQLSchema, evm); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) @Lazy(false) public GraphQLEntityViewSupport graphQLEntityViewSupport() { return graphQLEntityViewSupport; } } Next, one needs to define a DataFetcher for the defined query catById like so @Component @GraphQLApi public class CatFetcher { @Autowired CatViewRepository repository; @Autowired GraphQLEntityViewSupport graphQLEntityViewSupport; @GraphQLQuery public CatWithOwnerView catById(@GraphQLArgument(name = "id") Long id, @GraphQLEnvironment ResolutionEnvironment env) { return repository.findById(graphQLEntityViewSupport.createSetting(env.dataFetchingEnvironment), id); } } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 98/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Naming types or additional fields Types can be explicitly named by putting the @GraphQLType or @GraphQLName annotation on a type. @GraphQLType("TheEntity") @EntityView(MyEntity.class) public interface MyEntityView { //... } Additional fields can be declared as getter methods that follow the Java beans convention, or named explicitly by annotating the methods with @GraphQLQuery : @EntityView(MyEntity.class) public interface MyEntityView { //... default String getAdditionalField() { return "some data"; } @GraphQLQuery(name = "additionalData") default String getData() { return "more data"; } } In this case the schema for MyEntityView will contain two additional fields additionalField and additionalData . Note that when the GraphQL field name does not match the property name of a getter method like in the previous example, the @GraphQLName annotation will not work, and the SPQR annotation @GraphQLQuery is preferred. Ignoring types or fields Types can be explicitly ignored by putting the @GraphQLIgnore annotation on a type. It’s also possible to prevent getters in entity views to appear as fields in the GraphQL type schema, by annotating the getter method with the @GraphQLIgnore annotation. @EntityView(MyEntity.class) public interface MyEntityView { //... @GraphQLIgnore default String getAdditionalField() { return "some data"; } } Forcing non-null types on fields The type of a GraphQL field can be forced to be non-null by putting the @GraphQLNonNull annotation on a getter method. Usually, the integration is able to figure out non-null types through its nullability analysis of mapping expressions, but for custom methods or cases when the analysis fails, the explicit annotation can be used. @EntityView(MyEntity.class) public interface MyEntityView { //... @GraphQLNonNull default String getAdditionalField() { return "some data"; } } For a full example see the following example project. 16.2.4. MicroProfile GraphQL - SmallRye MicroProfile GraphQL (version 1.1 at the time of writing) has a completely different approach, as it is completely annotation based. At the moment, only the SmallRye implementation is supported and unfortunately, not yet within Quarkus. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 99/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Let’s consider the following sample schema type Query { catById(id: ID!): CatWithOwnerView } and the setup logic: @ApplicationScoped public class GraphQLProducer { @Inject EntityViewManager evm; GraphQLEntityViewSupport graphQLEntityViewSupport; void configure(@Observes GraphQLSchema.Builder schemaBuilder) { // Option 1: As of SmallRye GraphQL 1.3.1 you can disable the generation of GraphQL types and annotate all entity views with @Type instead // boolean defineNormalTypes = false; // boolean defineRelayTypes = false; // Option 2: Let the integration replace the entity view GraphQL types boolean defineNormalTypes = true; boolean defineRelayTypes = true; // Configure how to integrate entity views GraphQLEntityViewSupportFactory graphQLEntityViewSupportFactory = new GraphQLEntityViewSupportFactory(defineNormalTypes, defineRelayTypes); graphQLEntityViewSupportFactory.setImplementRelayNode(false); graphQLEntityViewSupportFactory.setDefineRelayNodeIfNotExist(true); graphQLEntityViewSupportFactory.setScalarTypeMap(GraphQLScalarTypes.getScalarMap()); // Integrate and create the support class for extraction of EntityViewSetting objects this.graphQLEntityViewSupport = graphQLEntityViewSupportFactory.create(schemaBuilder, evm); } @Produces @ApplicationScoped GraphQLEntityViewSupport graphQLEntityViewSupport() { return graphQLEntityViewSupport; } } Note that you need a microprofile-config.properties file in META-INF with the config option smallrye.graphql.events.enabled=true to enable the events. Next, one needs to define a DataFetcher for the defined query catById like so @GraphQLApi public class CatFetcher { @Inject CatViewRepository repository; @Inject Context context; @Inject GraphQLEntityViewSupport graphQLEntityViewSupport; @Query public CatWithOwnerView catById(@Input("id") Long id) { return repository.findById(graphQLEntityViewSupport.createSetting(context.unwrap(DataFetchingEnvironment.class)), id); } } Naming types or additional fields Types can be explicitly named by putting the @Name or @GraphQLName annotation on a type. @Name("TheEntity") @EntityView(MyEntity.class) public interface MyEntityView { //... } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 100/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Additional fields can be declared as getter methods that follow the Java beans convention, or named explicitly by annotating the methods with @Query : @EntityView(MyEntity.class) public interface MyEntityView { //... default String getAdditionalField() { return "some data"; } @Query("additionalData") default String getData() { return "more data"; } } In this case the schema for MyEntityView will contain two additional fields additionalField and additionalData . Note that when the GraphQL field name does not match the property name of a getter method like in the previous example, the @GraphQLName annotation will not work, and the MicroProfile GraphQL annotation @Query is preferred. Ignoring types or fields Types can be explicitly ignored by putting the @Ignore or @GraphQLIgnore annotation on a type. It’s also possible to prevent getters in entity views to appear as fields in the GraphQL type schema, by annotating the getter method with the @GraphQLIgnore annotation. @EntityView(MyEntity.class) public interface MyEntityView { //... @Ignore default String getAdditionalField() { return "some data"; } } Forcing non-null types on fields The type of a GraphQL field can be forced to be non-null by putting the @NonNull annotation on a getter method. Usually, the integration is able to figure out non-null types through its nullability analysis of mapping expressions, but for custom methods or cases when the analysis fails, the explicit annotation can be used. @EntityView(MyEntity.class) public interface MyEntityView { //... @NonNull default String getAdditionalField() { return "some data"; } } For a full example see the following example project. 16.2.5. Sample query The repository for the previously presented setups could look like this: public class CatViewRepository { private final EntityManager em; private final CriteriaBuilderFactory cbf; private final EntityViewManager evm; public CatViewRepository(EntityManager em, CriteriaBuilderFactory cbf, EntityViewManager evm) { this.em = em; this.cbf = cbf; this.evm = evm; } public <T> T findById(EntityViewSetting<T, CriteriaBuilder<T>> setting, Long id) { https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 101/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module return evm.find(em, setting, id); } } A sample GraphQL query query { findCatById(id: 1) { id name } } will cause a JPQL query similar to the following SELECT c.id, c.name FROM Cat c WHERE c.id = :param It does not select or join the owner information, although it is specified in the entity view! This optimization works through applying the selection list of the DataFetchingEnvironment via EntityViewSetting.fetch() . 16.3. Pagination support GraphQL itself does not really define a standard pagination mechanism, so the integration implements part of the Relay pagination specification in order to provide support for keyset pagination in a more or less common format. To generate the types that are necessary for using a Relay compatible client, the GraphQLEntityViewSupportFactory can be further configured. boolean defineNormalTypes = true; // This time, also define the relay types i.e. Connection, Edge and Node boolean defineRelayTypes = true; GraphQLEntityViewSupportFactory graphQLEntityViewSupportFactory = new GraphQLEntityViewSupportFactory(defineNormalTypes, defineRelayTypes); // Implementing the Node interface requires a custom type resolver which is out of scope here, so configure to not doing that graphQLEntityViewSupportFactory.setImplementRelayNode(false); // If the type registry does not yet define the Node interface, we specify that it should be generated graphQLEntityViewSupportFactory.setDefineRelayNodeIfNotExist(true); With the entity views defined before, this will generate the following GraphQL types type PageInfo { startCursor: String endCursor: String } type CatWithOwnerViewConnection { edges: [CatWithOwnerViewEdge] pageInfo: PageInfo } type CatWithOwnerViewEdge { node: CatWithOwnerViewNode! cursor: String! } type CatWithOwnerViewNode { id: ID! name: String owner: PersonSimpleView } type PersonSimpleView { id: ID! name: String } To use these type, the static GraphQL Schema needs to be extended. Note that you can skip this for MicroProfile GraphQL. type Query { findAll(first: Int, last:Int, offset: Int, before: String, after: String): CatWithOwnerViewConnection! } https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 102/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module The Relay specification defines the first and last arguments to represent the amount of element to fetch. Using first will fetch the next X elements after the given reference point or the start, according to a specific ordering. Using last will fetch the last X elements before the given reference point or the end, according to a specific ordering. If you can’t use keyset pagination, the GraphQL integration also allows to use an offset argument, but it is not recommended as offset based pagination has scalability problems. A data fetcher for using this, could look like the following CatViewRepository repository = ... DataFetchingEnvironment dataFetchingEnvironment = ... EntityViewSetting<Object, ?> setting = graphQLEntityViewSupport.createPaginatedSetting(dataFetchingEnvironment); // The last order by item must be a unique expression for deterministic ordering setting.addAttributeSorter("id", Sorters.ascending()); if (setting.getMaxResults() == 0) { return new GraphQLRelayConnection<>(Collections.emptyList()); } return new GraphQLRelayConnection<>(repository.findAll(setting)); Note that in case of MicroProfile GraphQL, you will have to define the various input arguments in the method signature of the data fetcher: @Query public GraphQLRelayConnection<CatWithOwnerView> findAll( @Name("first") Integer first, @Name("last") Integer last, @Name("offset") Integer offset, @Name("before") String before, @Name("after") String after) { // ... } The GraphQLEntityViewSupport.createPaginatedSetting() method is capable of reading all necessary information from the DataFetchingEnvironment and the schema. It knows how to process first , last , offset , before and after arguments as well as integrates with the selection list feature to Avoid count queries to determine the overall count Avoid fetching non-requested node attributes If the query does not specify first or last , the EntityViewSetting.getMaxResults() will be 0 which will cause an exception if used for querying. Finally, the DataFetcher must return a GraphQLRelayConnection object that wraps a List or PagedList such that the correct result structure is produced. A sample GraphQL query query { findAll(first: 1){ edges { node { id name } } pageInfo { startCursor endCursor } } } will cause a JPQL query similar to the following SELECT c.id, c.name FROM Cat c LIMIT 1 https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 103/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module and provide a result object like the following query: { findAll: { edges: [{ node: { id: 1, name: "Cat 1" } }], pageInfo: { startCursor: "...", endCursor: "..." } } } You can the use the endCursor on the client side as value for the after argument to get the next page: query { findAll(first: 1, after: "..."){ edges { node { id name } } pageInfo { startCursor endCursor } } } which will cause a JPQL query similar to the following SELECT c.id, c.name FROM Cat c WHERE c.id > :previousId LIMIT 1 and provide a result object like the following query: { findAll: { edges: [{ node: { id: 2, name: "Cat 2" } }], pageInfo: { startCursor: "...", endCursor: "..." } } } For a full example see one of the following example projects: Plain graphql-java Netflix DGS MicroProfile GraphQL SPQR 17. Quarkus integration https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 104/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module We provide a basic Quarkus extension that allows to use Blaze Persistence core and entity views in a Quarkus application. As outlined in the setup section you need the following dependency for the integration: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-quarkus</artifactId> <version>${blaze-persistence.version}</version> </dependency> The use in native images also requires a dependency on the entity view annotation processor that may be extracted into a separate native profile: <profiles> <profile> <id>native</id> <dependencies> <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-entity-view-processor</artifactId> <version>${blaze-persistence.version}</version> <scope>provided</scope> </dependency> </dependencies> </profile> </profiles> 17.1. Entity view and entity view listener discovery The extension performs entity view and entity view listener scanning at deployment time while the remainder of the bootstrapping is performed at runtime. 17.2. CDI support CriteriaBuilderFactory and EntityViewManager are injectable out of the box. 17.3. Multiple Blaze Persistence instances In order to allow users to utilize the Hibernate ORM extension’s support for multiple persistence units the extension supports multiple Blaze Persistence instances (i.e. CriteriaBuilderFactory and EntityViewManager ) that can use different persistence units using the Quarkus configuration properties approach. The properties at the root of the quarkus.blaze-persistence. namespace refer to the default Blaze Persistence instance that is automatically created as long as no other named instances have been defined. 17.3.1. Assigning persistence units to Blaze Persistence instances If not specified otherwise, the default Blaze Persistence uses the default persistence unit. Using a map based approach, it is possible to define named Blaze Persistence instances. The used persistence units can be assigned using the persistence-unit property. quarkus.blaze-persistence.persistence-unit=UserPU quarkus.blaze-persistence."order".persistence-unit=OrderPU The above snippet assigns the persistence unit UserPU to the default Blaze Persistence instance and the OrderPU to the instance named order . 17.3.2. Attaching entity view and entity view listener classes to Blaze Persistence instances When multiple Blaze Persistence instances have been defined, it is required to specify packages for each instance that determine the attachment of discovered entity views and entity view listeners to the respective instances. There are two ways to do this which cannot be mixed: Via the packages configuration property; Via the @com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance package-level annotation. If mixed use is detected, the annotations are ignored and only the packages configuration properties are taken into account. Using the packages configuration property: https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 105/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module quarkus.blaze-persistence.packages=com.example.view.shared,com.example.view.user quarkus.blaze-persistence."order".packages=com.example.view.shared,com.example.view.order The above snippet assigns all enity views under the com.example.view.user package to the default Blaze Persistence instance and all entity views under the com.example.view.order package to the named "order" instance. Views under the com.example.view.shared package will be known to both instances. An alternative approach to attach entity view and entity view listener classes to Blaze Persistence instances is to use package-level @com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance annotations. The two approaches cannot be mixed. To obtain a configuration similar to the one above with the packages configuration property, create package-info.java files with the following contents: @BlazePersistenceInstance("order") package com.example.view.order; import com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance; @BlazePersistenceInstance(BlazePersistenceInstance.DEFAULT) package com.example.view.user; import com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance; @BlazePersistenceInstance(BlazePersistenceInstance.DEFAULT) @BlazePersistenceInstance("order") package com.example.view.shared; import com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance; Both approaches take subpackages into account. 17.3.3. CDI integration The CDI integration is straightforward and uses @com.blazebit.persistence.integration.quarkus.runtime.BlazePersistenceInstance annotation to specify the Blaze Persistence instance for injection. @Inject CriteriaBuilderFactory cbf; @Inject EntityViewManage evm; This will inject the CriteriaBuilderFactory and EntityViewManager of the default Blaze Persistence instance. @Inject @BlazePersistenceInstance("order") CriteriaBuilderFactory cbf; @Inject @BlazePersistenceInstance("order") EntityViewManage evm; This will inject the CriteriaBuilderFactory and EntityViewManager of the named order instance. Be careful to not mix up the entity managers you pass to CriteriaBuilderFactory when performing operations. In the context of the above example, always pass the entity manager for the default persistence unit to the default CriteriaBuilderFactory and the entity manager for the orderPU to the CriteriaBuilderFactory belonging to the order Blaze Persistence instance. 17.4. Hot reload The extension supports hot reload. 17.5. Configuration properties There are various optional properties useful to refine your EntityViewManager and CriteriaBuilderFactory or guide guesses of Quarkus. There are no required properties, as long as the Hibernate ORM extension is configured properly. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 106/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module When no property is set, the Blaze Persistence defaults apply. The configuration properties listed here allow you to override such defaults, and customize and tune various aspects. Key quarkus.blaze-persistence.template-eager-loading Type boolean Default false Description A boolean flag to make it possible to prepare all view template caches on startup. By default the eager loading of the view templates is disabled to have a better startup performance. Valid values for this property are true or false . Key quarkus.blaze-persistence.managed-type-validation-disabled Type boolean Default false Description A boolean flag to make it possible to disable the managed type validation. By default the managed type validation is enabled, but since the validation is not bullet proof, it can be disabled. Valid values for this property are true or false . Key quarkus.blaze-persistence.default-batch-size Type int Default 1 Description An integer value that defines the default batch size for entity view attributes. By default the value is 1 and can be overridden either via com.blazebit.persistence.view.BatchFetch#size() or by setting this property via com.blazebit.persistence.view.EntityViewSetting#setProperty . Key quarkus.blaze-persistence.expect-batch-mode Type String Default "values" Description A mode specifying if correlation value, view root or embedded view batching is expected. By default the value is values and can be overridden by setting this property via com.blazebit.persistence.view.EntityViewSetting#setProperty . Valid values are - values - view_roots embedding_views Key quarkus.blaze-persistence.updater.eager-loading Type boolean Default false Description A boolean flag to make it possible to prepare the entity view updater cache on startup. By default the eager loading of entity view updates is disabled to have a better startup performance. Valid values for this property are true or false . Key quarkus.blaze-persistence.updater.disallow-owned-updatable-subview Type boolean Default true Description A boolean flag to make it possible to disable the strict validation that disallows the use of an updatable entity view type for owned relationships. By default the use is disallowed i.e. the default value is true , but since there might be strange models out there, it possible to allow this. Valid values for this property are true or false . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 107/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Key quarkus.blaze-persistence.updater.strict-cascading-check Type boolean Default true Description A boolean flag to make it possible to disable the strict cascading check that disallows setting updatable or creatable entity views on non-cascading attributes before being associated with a cascading attribute. When disabled, it is possible, like in JPA, that the changes done to an updatable entity view are not flushed when it is not associated with an attribute that cascades updates. By default the use is enabled i.e. the default value is true . Valid values for this property are true or false . Key quarkus.blaze-persistence.updater.error-on-invalid-plural-setter Type boolean Default false Description A boolean flag that allows to switch from warnings to boot time validation errors when invalid plural attribute setters are encountered while the strict cascading check is enabled. When true , a boot time validation error is thrown when encountering an invalid setter, otherwise just a warning. This configuration has no effect when the strict cascading check is disabled. By default the use is disabled i.e. the default value is false . Valid values for this property are true or false . Key quarkus.blaze-persistence.create-empty-flat-views Type boolean Default true Description A boolean flag that allows to specify if empty flat views should be created by default if not specified via EmptyFlatViewCreation . By default the creation of empty flat views is enabled i.e. the default value is true . Valid values for this property are true or false . Key quarkus.blaze-persistence.expression-cache-class Type String Default "com.blazebit.persistence.parser.expression.ConcurrentHashMapExpressionCache" Description The fully qualified expression cache implementation class name. Key quarkus.blaze-persistence.inline-ctes Type boolean Default true Description If set to true, the CTE queries are inlined by default. Valid values for this property are true , false or auto . Default is true which will always inline non-recursive CTEs. The auto configuration will only make use of inlining if the JPA provider and DBMS dialect support/require it. The property can be changed for a criteria builder before constructing a query. Key quarkus.blaze-persistence.query-plan-cache-enabled Type boolean Default true Description If set to true, the query plans are cached and reused. Valid values for this property are true and false . Default is true . This configuration option currently only takes effect when Hibernate is used as JPA provider. The property can be changed for a criteria builder before constructing a query. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 108/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 17.6. Customization As of version 1.6.4, a CDI event of the type EntityViewConfiguration is fired with an optional @BlazePersistenceInstance qualifier at boot time. This allows to further customize the configuration which is often necessary for Custom type test values Providing this is necessary if you make use of some Hibernate UserType or custom BasicType to allow Blaze Persistence to figure out if equals / hashCode is properly implemented. Register custom type converter If you want to automatically convert between a domain type, and a persistence entity model type, a type converter is needed which can be registered on EntityViewConfiguration . Register custom basic user type In order to make proper use of a custom type in entity views, it is necessary to register a BasicUserType on EntityViewConfiguration . Configure default values for optional parameters Sometimes it is useful to provide access to services into entity views through optional parameters, for which a global default value can be registered on EntityViewConfiguration . As of version 1.6.5, also a CDI event of the type CriteriaBuilderConfiguration is fired with an optional @BlazePersistenceInstance qualifier at boot time. This allows to further customize the configuration which is often necessary if the context-less variant CriteriaBuilderConfigurationContributor isn’t enough Register named type for VALUES If you want to use a type that isn’t supported out of the box, it needs to be registered under a name. Register custom JpqlFunctionGroup If you want to register a CDI context aware JpqlFunctionGroup . Register JpqlMacro If you want to register a CDI context aware JpqlMacro . Register custom dialect When a dialect has a bug, needs a customization, or a new kind of dialect should be registered. Configure properties Sometimes it is simply necessary to override a configuration property through setProperty 18. Serialization integration In general, serialization of entity views should be no problem for most serialization frameworks as they rely on getter based access and entity views naturally define their state through getters. The deserialization support is a different thing though for which special integrations are required. By default, the deserialization will invoke EntityViewManager.getReference(viewType, id) to construct objects. If the view type that should be deserialized is creatable i.e. annotated with @CreatableEntityView and no value for the @IdMapping attribute is provided in the payload, it will be created via EntityViewManager.create() . The rest of the payload values are then deserialized onto the constructed object via the setters the entity view types provide. Read-only types only support deserialization when a value for the @IdMapping attribute is given and obviously need setters for the deserialization to work properly. Adding setters makes the models mutable which might not be desirable, so consider this when wanting to deserialize payload to entity views. A possible solution is to create an entity view subtype that has the sole purpose of providing setters so that it can be deserialized. Since entity view objects are created via EntityViewManager.getReference() or EntityViewManager.create() , the objects can then be directly saved via EntityViewManager.save() . For updatable entity views it is important to understand that EntityViewManager.save() will only flush non-null attributes i.e. do a partial flush. This is due to dirty tracking thinking that the initial state is all null attributes. Through deserialization, some attributes are set to values which are then considered dirty. That behavior is exactly what you would expect from an HTTP endpoint using the PATCH method. If you need to flush the full state regardless of the dirty tracking information, you can use EntityViewManager.saveFull() , but be aware that orphan removals will currently not work when using the QUERY flush strategy because the initial state is unknown. If you need orphan removal in such https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 109/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module a case, you are advised to switch to the ENTITY flush strategy for now. You could also make use of the EntityViewManager.saveTo() and EntityViewManager.saveFullTo() variants to flush data to an entity that was loaded via e.g. EntityManager.find() . An alternative to this would be to deserialize the state onto an existing updatable entity view that was loaded via e.g. EntityViewManager.find() . With the initial state being known due to loading from the database, orphan removal will work correctly, but be aware that providing null values for attributes in the JSON payload will obviously cause null to be set on the entity view attributes. 18.1. Jackson integration 18.1.1. Setup To use the Jackson integration directly you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jackson</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jackson-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> If you are using JAX-RS, Spring WebMvc or Spring WebFlux, consider using the already existing integrations instead to avoid unnecessary work. 18.1.2. Usage The integration happens by adding a module and a custom visibility checker to an existing ObjectMapper . ObjectMapper existingMapper = ... EntityViewManager evm = ... EntityViewAwareObjectMapper mapper = new EntityViewAwareObjectMapper(evm, existingMapper); The EntityViewAwareObjectMapper class provides utility methods for integrating with JAX-RS, Spring WebMvc and Spring WebFlux, but you can use your ObjectMapper directly as before as the module and visibility checker is registered in the existing mapper. 18.2. JSONB integration 18.2.1. Setup To use the JSONB integration directly you need the following Maven dependencies: <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jsonb</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> or if you are using Jakarta JPA <dependency> <groupId>com.blazebit</groupId> <artifactId>blaze-persistence-integration-jsonb-jakarta</artifactId> <version>${blaze-persistence.version}</version> <scope>compile</scope> </dependency> If you are using JAX-RS, Spring WebMvc or Spring WebFlux, consider using the already existing integrations instead to avoid unnecessary work. 18.2.2. Usage The integration happens by adding custom JsonDeserializer instances per entity view and a PropertyVisibilityStrategy to a JsonbConfig . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 110/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module JsonbConfig jsonbConfig = new JsonbConfig(); EntityViewJsonbDeserializer.integrate(jsonbConfig, evm, idValueAccessor); Jsonb jsonb = JsonbBuilder.create(jsonbConfig); The resulting Jsonb instance will make use of the special deserializers for entity views and you can simply use it as usual e.g. jsonb.fromString("...", MyEntityView.class) . 19. Metamodel The metamodel for entity views is very similar to the JPA metamodel and the entry point is ViewMetamodel which can be acquired through EntityViewManager.getMetamodel() It allows access to views( ViewType ) and flat views( FlatViewType ) which both are subtypes of managed views( ManagedViewType ). The only difference between the two is that a flat view has no id mapping, so it’s identity is composed of all attributes which results in some limitations as described in the flat view mapping section. A view can have multiple named constructors that have parameter attributes. Additionally, a view can also have multiple named view filters. Every managed view has attributes which are structured based on the arity(singular or plural), the mapping type(parameter or method) and correlation type(normal, subquery or correlated). An attribute is always either an instance of ParameterAttribute or MethodAttribute depending on whether it is defined on a constructor as parameter or as getter method. A parameter attribute is defined by it’s index and it’s declaring MappingConstructor . Method attributes have a name, may have multiple named attribute filters and might possibly be updatable. A singular attribute is always an instance of SingularAttribute and is given if isCollection() returns false . If it is a subquery i.e. isSubquery() returns true , it is also an instance of SubqueryAttribute . If it is correlated i.e. isCorrelated() returns true , it is also an instance of CorrelatedAttribute . If it is neither a subquery nor correlated, it is going to be an instance of MappingAttribute . A plural attribute is always an instance of PluralAttribute and is given if isCollection() return true . Since plural attributes can’t be defined via a subquery mapping, it is never an instance of SubqueryAttribute . If it is correlated i.e. isCorrelated() returns true , it is also an instance of CorrelatedAttribute , otherwise it is going to be an instance of MappingAttribute . Depending on the collection type returned by getCollectionType a plural attribute is also an instance of https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 111/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module CollectionAttribute if CollectionType.COLLECTION ListAttribute if CollectionType.LIST SetAttribute if CollectionType.SET MapAttribute if CollectionType.MAP 20. Annotation processor Blaze Persistence provides an annotation processor that can be used to generate static metamodels, implementations and builders for entity views. The setup is described in getting started chapter. The annotation processor supports the following annotation processor options: debug to print debug information. Default false addGenerationDate to add the generation date to the generated java files. Default false addGeneratedAnnotation to add the @Generated annotation to the generated java files. Default true addSuppressWarningsAnnotation to add the @SuppressWarnings annotation to the generated java files. Default false strictCascadingCheck whether to generate strict cascading checks for entity view implementations. Default true defaultVersionAttributeName the name of an entity view attribute that should be considered the optimistic lock version. No default defaultVersionAttributeType the type the entity view version attribute should have to be considered the version attribute. No default generateImplementations whether to generate entity view implementations. If false , the generateBuilders option is meaningless. Default true generateBuilders whether to generate entity view builders. Default true createEmptyFlatViews whether to create empty flat views by default unless specified via @EmptyFlatViewCreation . Default is true generateDeepConstants whether to create nested classes for subview attributes that allow deep static referencing into a model. Default is true optionalParameters a semicolon separated list of names with optional types in the format NAME=java.lang.String , for globally registered optional parameters 20.1. Static metamodel Static metamodel classes will reflect the structure of the entity view and provide access to the typed metamodel elements for an entity view. The static metamodels for entity views like the following: @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { Set<SimpleCatView> getKittens(); } look like this: @StaticMetamodel(SimpleCatView.class) public class SimpleCatView_ { public static volatile SingularAttribute<SimpleCatView, Long> id; public static volatile SingularAttribute<SimpleCatView, String> name; public static final String ID = "id"; public static final String NAME = "name"; } @StaticMetamodel(CatView.class) public class CatView_ { public static volatile SingularAttribute<CatView, Long> id; public static volatile SingularAttribute<CatView, String> name; public static volatile SetAttribute<CatView, SimpleCatView> kittens; public static final String ID = "id"; public static final String NAME = "name"; https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 112/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module public static final String KITTENS = "kittens"; } The attributes can be used for doing type safe operations like e.g. setting attributes through entity view builders in a type safe yet generic manner. Generated metamodels are annotated with @StaticMetamodel and are scanned for during boot which can be turned off via the configuration property STATIC_METAMODEL_SCANNING_DISABLED. 20.2. Static implementation The static implementation of an entity view is a class that implements the entity view contract as well as some entity view SPI contracts like e.g. com.blazebit.persistence.view.spi.type.MutableStateTrackable , com.blazebit.persistence.view.spi.type.DirtyStateTrackable , com.blazebit.persistence.view.spi.type.EntityViewProxy . The implementations for the state tracking contracts contain code for efficient dirty tracking. The static implementations for entity views like the following: @EntityView(Cat.class) interface SimpleCatView { @IdMapping Long getId(); String getName(); } @EntityView(Cat.class) interface CatView extends SimpleCatView { Set<SimpleCatView> getKittens(); } look like this: @StaticImplementation(SimpleCatView.class) public class SimpleCatViewImpl implements SimpleCatView, EntityViewProxy { private final Long id; private final String name; public SimpleCatViewImpl(SimpleCatViewImpl noop, Map<String, Object> optionalParameters) { this.id = null; this.name = null; } public SimpleCatViewImpl(Long id) { this.id = id; this.name = null; } public SimpleCatViewImpl(Long id, String name) { this.id = id; this.name = name; } public SimpleCatViewImpl(SimpleCatViewImpl noop, int offset, Object[] tuple) { this.id = (Long) tuple[offset + 0]; this.name = (String) tuple[offset + 1]; } public SimpleCatViewImpl(SimpleCatViewImpl noop, int offset, int[] assignment, Object[] tuple) { this.id = (Long) tuple[offset + assignment[0]]; this.name = (String) tuple[offset + assignment[1]]; } @Override public Long getId() { return id; } @Override public String getName() { return name; } @Override public Class<?> $$_getJpaManagedClass() { return Cat.class; } @Override https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 113/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module public Class<?> $$_getJpaManagedBaseClass() { return Cat.class; } @Override public Class<?> $$_getEntityViewClass() { return SimpleCatView.class; } @Override public boolean $$_isNew() { return false; } @Override public Object $$_getId() { return id; } @Override public Object $$_getVersion() { return null; } @Override public int hashCode() { return Objects.hashCode(id); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || this.$$_getId() == null) { return false; } if (obj instanceof EntityViewProxy) { EntityViewProxy other = (EntityViewProxy) obj; if (this.$$_getJpaManagedBaseClass() == other.$$_getJpaManagedBaseClass() && this.$$_getId().equals(other.$$_getId())) { return true; } else { return false; } } if (obj instanceof SimpleCatView) { SimpleCatView other = (SimpleCatView) obj; if (!Objects.equals(this.getId(), other.getId())) { return false; } return true; } return false; } } @StaticImplementation(CatView.class) public class CatViewImpl implements CatView, EntityViewProxy { // Similar to SimpleCatViewImpl with some additions for kittens private final Set<SimpleCatView> kittens; @Override public Set<SimpleCatView> getKittens() { return kittens; } } The first constructor public SimpleCatViewImpl(SimpleCatViewImpl noop, Map<String, Object> optionalParameters) is the so called "create"-constructor i.e. the one used for EntityViewManager.create() . The next constructor public SimpleCatViewImpl(Long id) is the idreference constructor i.e. the one used for EntityViewManager.getReference() . The third constructor public SimpleCatViewImpl(Long id, String name) is the full state constructor which can be used by end-users. The other two constructors public SimpleCatViewImpl(SimpleCatViewImpl noop, int offset, Object[] tuple) and public SimpleCatViewImpl(SimpleCatViewImpl noop, int offset, int[] assignment, Object[] tuple) are used internally by the runtime to construct entity view objects. The variant with int[] assignment is usually only relevant when entity view inheritance is enabled. Generated implementations are annotated with @StaticImplementation and are scanned for during boot which can be turned off via the configuration property STATIC_IMPLEMENTATION_SCANNING_DISABLED. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 114/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module The generation of static implementations can be turned off by setting the generateImplementations option to false in the annotation processor option map. 20.3. Static builder The static builder of an entity view is a class that implements the com.blazebit.persistence.view.EntityViewBuilder contract to build a static implementation instance. The generated class is a straightforward implementation of the builder interface tailored for the entity view state i.e. every attribute is a separate field in the builder. A call to EntityViewManager.createBuilder() will return an instance of a registered static builder type, or if none is registered, a generic builder. Generated builders are annotated with @StaticBuilder and are scanned for during boot which can be turned off via the configuration property STATIC_BUILDER_SCANNING_DISABLED. The generation of static builders can be turned off by setting the generateBuilders option to false in the annotation processor option map. 21. Configuration Blaze Persistence can be configured by setting properties on a com.blazebit.persistence.view.spi.EntityViewConfiguration object and creating a EntityViewManager from it. 21.1. Configuration properties 21.1.1. PROXY_EAGER_LOADING Defines whether proxy classes for entity views should be created eagerly when creating the EntityViewManager or on demand. To improve startup performance this is deactivated by default. When using entity views in a clustered environment you might want to enable this! Key com.blazebit.persistence.view.proxy.eager_loading Type boolean Default false Applicable Configuration only 21.1.2. TEMPLATE_EAGER_LOADING Defines whether entity view template objects should be created eagerly when creating the EntityViewManager or on demand. To improve startup performance this is deactivated by default. In a production environment you might want to enable this so that templates don’t have to be built ondemand but are retrieved from a cache. Key com.blazebit.persistence.view.eager_loading Type boolean Default false Applicable Configuration only 21.1.3. PROXY_UNSAFE_ALLOWED Defines whether proxy classes that support using the getter methods in a constructor should be allowed. These proxy classes have to be defined via sun.misc.Unsafe to avoid class verification errors. Disabling this property makes the use of the getter in the constructor return the default value for the property instead of the actual value. Key com.blazebit.persistence.view.proxy.unsafe_allowed Type boolean Default true Applicable Configuration only 21.1.4. EXPRESSION_VALIDATION_DISABLED Defines whether the expressions of entity view mappings should be validated. https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 115/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Key com.blazebit.persistence.view.expression_validation_disabled Type boolean Default true Applicable Configuration only 21.1.5. DEFAULT_BATCH_SIZE Defines the default batch size to be used for attributes that are fetched via the SELECT fetch strategy. To specify the batch size of a specific attribute, append the attribute name after the "batch_size" like e.g. com.blazebit.persistence.view.batch_size.subProperty Key com.blazebit.persistence.view.batch_size Type int Default 1 Applicable Always 21.1.6. EXPECT_BATCH_CORRELATION_VALUES This was deprecated in favor of EXPECT_BATCH_MODE . Defines whether by default batching of correlation values or view root ids is expected for attributes that are fetched via the SELECT fetch strategy. To specify the batch expectation of a specific attribute, append the attribute name after the "batch_correlation_values" like e.g. com.blazebit.persistence.view.batch_correlation_values.subProperty Key com.blazebit.persistence.view.batch_correlation_values Type boolean Default true Applicable Always 21.1.7. EXPECT_BATCH_MODE Defines the expected batch mode i.e. whether correlation values, view root ids or embedding view ids are expected to be batched for attributes that are fetched via the SELECT fetch strategy. To specify the batch expectation of a specific attribute, append the attribute name after the "batch_mode" like e.g. com.blazebit.persistence.view.batch_mode.subProperty Key com.blazebit.persistence.view.batch_mode Type String Default values, view_roots, embedding_views Applicable Always 21.1.8. UPDATER_EAGER_LOADING Defines whether entity view updater objects should be created eagerly when creating the EntityViewManager or on demand. To improve startup performance this is deactivated by default. In a production environment you might want to enable this so that updaters don’t have to be built ondemand but are retrieved from a cache. Key com.blazebit.persistence.view.updater.eager_loading Type boolean Default false Applicable Configuration only https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 116/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module 21.1.9. UPDATER_FLUSH_MODE Defines the flush mode the entity view updater objects should be using which is normally defined via @UpdatableEntityView(mode = ...) . This is a global override. To override the flush mode of a specific class, append the fully qualified class name after the "flush_mode" part like e.g. com.blazebit.persistence.view.updater.flush_mode.com.test.MyUpdatableCatView . Key com.blazebit.persistence.view.updater.flush_mode Type String Values partial, lazy or full Default none Applicable Configuration only 21.1.10. UPDATER_FLUSH_STRATEGY Defines the flush strategy the entity view updater objects should be using which is normally defined via @UpdatableEntityView(strategy = ...) . This is a global override. To override the flush strategy of a specific class, append the fully qualified class name after the "flush_strategy" part like e.g. com.blazebit.persistence.view.updater.flush_strategy.com.test.MyUpdatableCatView . Key com.blazebit.persistence.view.updater.flush_strategy Type String Values entity or query Default none Applicable Configuration only 21.1.11. UPDATER_DISALLOW_OWNED_UPDATABLE_SUBVIEW Defines whether the use of an updatable entity view type for owned relationships is disallowed. By default the use is disallowed i.e. the default value is true , but since there might be strange models out there, it is possible to allow this. The main reason to disallow this, is that this kind of usage would break the idea of a separate model per use case, but there is also technical reason. Updatable entity views are only allowed to have a single parent object due to the way dirty tracking is implemented. This is not necessarily a limitation, but was simply done this way because the developers believe in the model per use case approach and want to encourage this way of working. During loading of entity views, tuples are transformed into entity views. Updatable entity views are de-duplicated i.e. if another tuple would be transformed, it uses the existing object instead. During construction of an entity view all it’s child views are registered for dirty tracking. Since an updatable view may only have one parent, and owned *ToOne relationships do not guarantee that the relationship object will only have one parent, this will result in a runtime exception depending on the data. Beware that allowing updatable entity view types for *ToOne relationships might lead to these exceptions at runtime if the relationship isn’t logically a OneToOne. Key com.blazebit.persistence.view.updater.disallow_owned_updatable_subview Type boolean Default true Applicable Configuration only 21.1.12. UPDATER_STRICT_CASCADING_CHECK Defines whether the strict cascading check that disallows setting updatable or creatable entity views on non-cascading attributes before being associated with a cascading attribute is enabled. When disabled, it is possible, like in JPA, that the changes done to an updatable entity view are not flushed when it is not associated with an attribute that cascades updates. By default the use is enabled i.e. the default value is true . https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 117/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module Key com.blazebit.persistence.view.updater.strict_cascading_check Type boolean Default true Applicable Configuration only 21.1.13. UPDATER_ERROR_ON_INVALID_PLURAL_SETTER Defines whether warnings or boot time validation errors should be emitted when invalid plural attribute setters are encountered while the strict cascading check is enabled. When true , a boot time validation error is thrown when encountering an invalid setter, otherwise just a warning. This configuration has no effect when the strict cascading check is disabled. By default the use is disabled i.e. the default value is false . Key com.blazebit.persistence.view.updater.error_on_invalid_plural_setter Type boolean Default false Applicable Configuration only 21.1.14. PAGINATION_DISABLE_COUNT_QUERY Defines whether the pagination count query should be disabled when applying a EntityViewSetting to a CriteriaBuilder . When true , the pagination count query is disabled via PaginatedCriteriaBuilder.withCountQuery(false) . By default the pagination count query is enabled i.e. the default value is false . Key com.blazebit.persistence.view.pagination.disable_count_query Type boolean Default false Applicable EntityViewSetting only 21.1.15. PAGINATION_EXTRACT_ALL_KEYSETS Defines whether the pagination query should extract all keysets rather than just the first and last ones. When true , the keyset extraction is enabled via PaginatedCriteriaBuilder.withExtractAllKeysets(true) . By default only the first and last keysets are extracted i.e. the default value is false . Key com.blazebit.persistence.view.pagination.extract_all_keysets Type boolean Default false Applicable EntityViewSetting only 21.1.16. PAGINATION_FORCE_USE_KEYSET Defines whether the pagination query should force the usage of a keyset when available even if page or page size changes. By default only the first and last keysets are extracted i.e. the default value is false . Key com.blazebit.persistence.view.pagination.force_use_keyset Type boolean Default false Applicable EntityViewSetting only 21.1.17. PAGINATION_HIGHEST_KEYSET_OFFSET Defines the offset from the maxResults at which to find the highest keyset i.e. the highest keyset will be at position Math.min(size, maxResults - offset) . Setting 1 along with a maxResults + 1 allows to look ahead one element to check if there are more elements which is useful for pagination https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 118/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module with lazy page count or endless scrolling. By default the offset is disabled i.e. the default value is null . Key com.blazebit.persistence.view.pagination.highest_keyset_offset Type integer Default null Applicable EntityViewSetting only 21.1.18. PAGINATION_BOUNDED_COUNT Defines the maximum value up to which the count query should count. By default the bounded count is disabled i.e. all rows are counted. Key com.blazebit.persistence.view.pagination.bounded_count Type integer Default null Applicable EntityViewSetting only 21.1.19. STATIC_BUILDER_SCANNING_DISABLED Defines whether the scanning for @StaticBuilder classes for the registered entity views should be disabled. When true , the scanning is disabled which improves startup performance but causes that entity view builders returned via EntityViewManager.createBuilder() will use a generic implementation. By default the scanning is enabled i.e. the default value is false . Key com.blazebit.persistence.view.static_builder_scanning_disabled Type boolean Default false Applicable Configuration only 21.1.20. STATIC_IMPLEMENTATION_SCANNING_DISABLED Defines whether the scanning for @StaticImplementation classes for the registered entity views should be disabled. When true , the scanning is disabled which improves startup performance but causes that entity view implementations will be generated at runtime. By default the scanning is enabled i.e. the default value is false . Key com.blazebit.persistence.view.static_implementation_scanning_disabled Type boolean Default false Applicable Configuration only 21.1.21. STATIC_METAMODEL_SCANNING_DISABLED Defines whether the scanning for @StaticMetamodel classes for the registered entity views should be disabled. When true , the scanning is disabled which improves startup performance but causes that the static metamodels are not initialized. By default the scanning is enabled i.e. the default value is false . Key com.blazebit.persistence.view.static_metamodel_scanning_disabled Type boolean Default false Applicable Configuration only 21.1.22. CREATE_EMPTY_FLAT_VIEWS Defines whether empty flat views should be created by default if not specified via @EmptyFlatViewCreation . When false , null will be set for an attribute if the flat view would be empty, otherwise an empty flat view is set. By default the creation of empty flat views is enabled i.e. the default https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 119/120 2023. 06. 29. 22:08 Blaze Persistence - Entity View Module value is true . Key com.blazebit.persistence.view.create_empty_flat_views Type boolean Default true Applicable Configuration only 22. FAQ This section tries to cover some standard questions that often come up when introducing entity views or updatable entity views into a project as well as some common problems with explanations and possible solutions. 22.1. Why do I get an optimistic lock exception when updating an updatable entity view? The com.blazebit.persistence.view.OptimisticLockException is very similar to the javax.persistence.OptimisticLockException and when thrown, it signals that an update isn’t possible because of a change of an object that happened in the meantime. This can also happen when you do not use optimistic locking explicitly. 22.1.1. If you try to update a non-existent entity When trying to update an entity that does not exist, the EntityViewManager.update operation will throw the com.blazebit.persistence.view.OptimisticLockException . The entity could have been deleted in the meantime i.e. between loading the view and the update operation The entity view causing the exception is the result of a wrong usage of EntityViewManager.convert as it is missing the ConvertOption.CREATE_NEW 22.1.2. If you try to update a concurrently updated entity Either the entity was updated within the current transaction or within another transaction through a different mechanism or a different entity view object. If an update in a different transaction caused the exception, it is necessary to load the new version of the entity view and let the end-user enter the values to update again. By inspecting the change model of the old instance one can assist the user by copying over non-conflicting value changes and just highlight conflicting changes. If a previous update in the same transaction causes the exception, the code should be adapted to prevent this from happening or updating the version on the entity view accordingly. 22.2. Why do I get a "could not invoke proxy constructor" exception when fetching entity views? Entity views are type checked for most parts, but there are some dynamic non-declarative parts that can’t be type checked that might cause this runtime exception when using a wrong result. Usually, this happens when a SubqueryProvider or CorrelationProvider is in use. The implementations of these classes define the result type in a manner that is not type checkable. If a SubqueryProvider returns an integer via e.g. select("1") or select("someIntAttribute") , but the entity view attribute using the subquery provider uses a different type like e.g. boolean , constructing an instance of that entity view might fail when trying to interpret the integer as boolean with an IllegalArgumentException saying that types are incompatible. The obvious fix is to correct either the select item to return the correct type or the entity view attribute to declare the appropriate attribute type. In case of a subquery provider it is also possible to wrap the subquery into a more complex expression by using e.g. @MappingSubquery(value = MyProvider.class, subqueryAlias = "subquery", expression = "CASE WHEN EXISTS subquery THEN true ELSE false END") . A CorrelationProvider can fail in a similar manner as it defines the entity type it correlates via correlate(SomeEntity.class) . If the entity view attribute expects a different type that is not compatible, it will fail at runtime with an IllegalArgumentException saying that types are incompatible. If a correlation result is defined via @MappingCorrelated(correlationResult = "someAttributeOfCorrelatedEntity") the type of that expression must be compatible which can be another cause for an error. This problem can be fixed by adapting the correlation result expression, by changing the correlated entity in the correlation provider or by changing the declared attribute type. Version 1.6.10-SNAPSHOT Last updated 2022-11-21 11:27:04 MEZ https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/ 120/120