Blaze-Persistence

repository·main·Indexed 21 days ago

https://github.com/blazebit/blaze-persistence

A high-performance, feature-rich Criteria API for JPA providers. It extends standard JPA capabilities with advanced query features including CTEs, set operations, and keyset pagination, as well as an Entity-View module for efficient data projection and optimized SQL selection.

Tokens
93.1K
Snippets
229
Records
349
Agent score
62%

What's inside Blaze-Persistence

  1. What is Blaze-Persistence?

    main

    Blaze-Persistence is a rich Criteria API for JPA providers designed to overcome common restrictions found in standard JPA. It provides a fluent API for building complex queries and includes several specialized modules:

    • Core API: The foundation for building queries.
    • Entity-View Module: Allows creating views for JPA entities, similar to how RDBMS views work for tables.
    • JPA-Criteria Module: Implements the standard JPA Criteria API but is backed by the Blaze-Persistence Core API, enabling advanced query building from CriteriaQuery objects.
    • Integrations: Provides seamless support for Spring Data, DeltaSpike Data, and other frameworks.
  2. Integrate Blaze-Persistence with JAX-RS

    main

    The JAX-RS integration module provides the @EntityViewId annotation and includes MessageBodyReader and ParamConverter implementations to allow serialization frameworks (like Jackson or JSONB) to work seamlessly with JAX-RS.

    Integration is discovered automatically via the javax.ws.rs.ext.Providers ServiceLoader contract; simply adding the appropriate artifact to your classpath is sufficient.

  3. What is Blaze-Persistence Core?

    main

    Blaze-Persistence is a library that sits on top of a JPA provider to simplify the construction of complex, dynamic queries. It addresses common JPA pain points such as unreadable Criteria API code, the risks of manual query string concatenation, and the difficulty of implementing efficient pagination when fetching collections.

    The core module provides a fluent builder API designed for readability and integrates deeply with JPA providers to enable advanced SQL features (like CTEs) that are not natively supported by standard JPA. It works by generating JPQL or provider-native query strings that represent the logical query structure.

  4. What is a TypeConverter in Entity Views

    main

    A TypeConverter is an abstraction used to convert between an entity view model type and its underlying type. It is conceptually similar to the JPA AttributeConverter API.

    Its primary responsibilities are:

    1. Type Resolution: Determining the actual underlying type of an attribute. For example, if a view method returns Optional<Integer>, the TypeConverter identifies Integer as the underlying type.
    2. Bidirectional Conversion: Implementing the logic to convert from the view type to the underlying type and vice versa.

    This mechanism is the foundation for supporting wrapper types like java.util.Optional in entity views.

  5. Use Entity Array Expressions

    main

    An entity array expression uses an entity name as the base expression to perform a join with a predicate.

    Example: Cat[age > 18] results in LEFT JOIN Cat alias ON alias.age > 18.

    To refer to the joined entity directly within the predicate (for example, to restrict by concrete type), use the special identifier _.

    Example: Animal[TYPE(_) = Cat] results in LEFT JOIN Animal alias ON TYPE(alias) = Cat.

    // Entity array expression example
    .where("Cat[age > 18]")
  6. How Updatable Entity Views work

    main

    Updatable entity views use an EntityViewUpdater to synchronize changes from the view back to the persistence context.

    • Attribute Flushers: An EntityViewUpdater is composed of nested attribute flushers. The updater is responsible for flushing dirty attributes to the persistence context.
    • Dirty Tracking: The system tracks changes either by comparing the current state against the initial state or by not tracking state at all.
    • Collection Updates: Collections are managed using custom collection implementations that perform action recording. These recorded actions are then replayed onto the collection of an entity reference to apply changes.
  7. Implement Entity View Inheritance

    main

    Entity view inheritance allows you to materialize different subtypes based on a selection predicate.

    Basic Setup

    1. Annotate the base view with @EntityViewInheritance.
    2. Annotate subtypes with @EntityViewInheritanceMapping("predicate").

    Restricting Subtypes

    You can explicitly list allowed subtypes in the @EntityViewInheritance annotation on the supertype: @EntityViewInheritance({ Subtype.class }).

    Inheritance at the Use Site

    Use @MappingInheritance on a subview attribute to override or delimit which subtypes are considered for that specific relationship. Use @MappingInheritanceSubtype to define the specific mappings and subtypes. Setting onlySubtypes = true prevents the base type from being materialized (it returns null instead).

    JPA Inheritance

    If your JPA entities already use inheritance, Blaze-Persistence automatically handles it. If a subtype uses an entity subtype in its @EntityView annotation, Blaze-Persistence generates TYPE(this) = Subtype constraints automatically.

    @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();
    }
  8. Use Keyset Pagination with Entity Views

    main

    Keyset pagination can be enabled by enriching an offset-based EntityViewSetting with a KeysetPage using the withKeysetPage(KeysetPage keysetPage) method.

    To use this:

    1. Create a paginated setting using EntityViewSetting.create(Class, firstResult, maxResults).
    2. Call .withKeysetPage(previousKeysetPage) on the setting.
    3. After retrieving the PagedList, save the new keyset via list.getKeysetPage() for the next request.

    In stateless environments (like REST), you must serialize the KeysetPage to the client and deserialize it when the client requests the next page. Note that custom implementations of KeysetPage and Keyset may be required for custom serialization.

    EntityViewSetting<CatView, PaginatedCriteriaBuilder<CatView>> setting = 
        EntityViewSetting.create(CatView.class, firstResult, maxResults);
    
    // Apply the previous keyset to enable keyset pagination
    setting.withKeysetPage(previousKeysetPage);
    
    PagedList<CatView> list = catDataAccess.findAll(setting);
    
    // Store the new keyset for the next page request
    previousKeysetPage = list.getKeysetPage();
  9. How CTE (Common Table Expression) Builders Work

    main

    CTE builders are categorized by whether they are used for SELECT queries or DML queries.

    Select CTEs:

    • Recursive CTEs: These require an explicit base part and a recursive part. You start with com.blazebit.persistence.SelectRecursiveCTECriteriaBuilder to define the base part, then use .union() to incorporate the recursive part via com.blazebit.persistence.SelectCTECriteriaBuilder.
    • Non-recursive CTEs: Use com.blazebit.persistence.SelectCTECriteriaBuilder. While they support set operations, it is recommended to use the recursive builder for recursive logic to ensure portability and readability.
  10. Customize Blaze-Persistence via CDI Events in Quarkus

    main

    For advanced customization at boot time, the Quarkus extension fires CDI events that you can observe. You can use the @BlazePersistenceInstance qualifier to target specific instances.

    EntityViewConfiguration Event

    Fired at boot time to customize EntityViewManager. Use this to:

    • Provide custom type test values (for Hibernate UserType or BasicType).
    • Register custom type converters.
    • Register custom BasicUserType instances.
    • Configure default values for optional parameters.

    CriteriaBuilderConfiguration Event

    Fired at boot time to customize CriteriaBuilderFactory. Use this to:

    • Register named types for VALUES.
    • Register custom JpqlFunctionGroup (CDI context aware).
    • Register JpqlMacro (CDI context aware).
    • Register a custom dialect.
  11. Define and Query an Entity View

    main

    An Entity View is a projection of an entity (similar to a database view) that allows querying a subset of fields.

    1. Define the View: Use @EntityView(TargetEntity.class) on an interface or class. Use @IdMapping for the identifier and @Mapping("fieldName") to map methods to specific entity attributes.
    2. Query the View: Use EntityViewManager.applySetting() to transform a standard CriteriaBuilder<Entity> into a CriteriaBuilder<EntityView>.
    // 1. Define the view
    @EntityView(Cat.class)
    public interface CatNameView {
        @IdMapping
        public Long getId();
    
        @Mapping("name")
        public String getCatName();
    }
    
    // 2. Query the view
    CriteriaBuilder<Cat> cb = cbf.create(em, Cat.class);
    CriteriaBuilder<CatNameView> catNameBuilder = evm.applySetting(EntityViewSetting.create(CatNameView.class), cb);
    List<CatNameView> catNameViews = catNameBuilder.getResultList();
  12. Use Static Builders for Entity Views

    main

    The annotation processor generates static builder classes annotated with @StaticBuilder. These classes implement the com.blazebit.persistence.view.EntityViewBuilder contract.

    Each attribute in the entity view is represented as a separate field in the generated builder, making it a straightforward implementation of the builder pattern. When you call EntityViewManager.createBuilder(), the manager returns an instance of a registered static builder (or a generic builder if none is found).

    Generation can be disabled by setting generateBuilders to false. Scanning can be disabled via STATIC_BUILDER_SCANNING_DISABLED.