Uploaded by hex

Blaze Persistence - Entity View Module

advertisement
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
Download