Use of DTOs in Blaze Persistence

hi,

in order to avoid hydrating full entities and fetch only the required field(s), is it actually possible with Blaze Persistence, without the entity views module (to keep it simple) ?

I have not found the DTOs in the Blaze-Persistence documentation, except for entity views module.

So we define a DTO and then simply use it. For example :

import java.time.LocalDateTime;

public record UserDTO(
String username,
String email,
LocalDateTime createdAt
) {}

and

EntityManager em = emHolder.getEntityManager();
CriteriaBuilderFactory cbf = CriteriaBuilderFactoryProducer.get(em);

List users = cbf.create(em, UserDTO.class)
.from(UserPO.class, ā€œuā€)
.selectNew(UserDTO.class,
ā€œu.usernameā€,
ā€œu.emailā€,
ā€œu.createdAtā€)
.where(ā€œu.activeā€).eqLiteral(true)
.orderByDesc(ā€œu.createdAtā€)
.getResultList();

Does it work ?

If not, how to do it and remain simple ? If it must be done with entity views module, what would be the approach, or please give a URL.

I emphasize that Blaze Persistence should be used for implementation, and not, for example, Hibernate’s DTO projections (Hibernate is the JPA provider in the project).

The goal is to avoid hibernate entity hydration whenever possible, for performance purposes.

Thanks

If you use the constructor syntax in HQL, no entities will be created, unless you pass entity references to the constructor of your DTO. In fact, the Blaze-Persistence query you wrote there is fine and somewhat equivalent to the following HQL:

select new my.organisation.UserDto(
  u.username,
  u.email,
  u.createdAt
)
from UserPo u
where u.active = true
order by u.createdAt desc

A couple of big benefits that you get when using Blaze-Persistence Entity-Views is that you don’t have to write code/strings that are only validated during runtime and that you avoid lazy loading issues.

With an Entity View like this:

@EntityView(UserPO.class)
public record UserDTO(
  String username,
  String email,
  LocalDateTime createdAt
) {}

you can have all your projection specific mappings in your Entity Views and concentrate on the business logic in your querying code:

CriteriaBuilder<UserPO> cb = cbf.create(em, UserPO.class, "u")
.where(ā€œu.activeā€).eqLiteral(true)
.orderByDesc(ā€œu.createdAtā€)
List<UserDTO> users = evm.applySetting(EntityViewSetting.create(UserDTO.class), cb).getResultList();

By parameterizing your business code with the EntityViewSetting, you can even let the caller of your business logic decide about the data representation. This is very useful for the GraphQL integration, since it will make sure to produce a minimal query for the request.

At boot time, the Entity View is type checked against the mapping expressions (which can be more complicated) to ensure it can be constructed safely. So if you mess up a refactoring somewhere, you will notice during deployment/app-startup time.

You can find more information on in the Entity View documentation.

Thanks a lot for your explanation. Just one more question. According to you, is there any hibernate entity hydration in the following Blaze-Persistence query :

private static boolean isIssuerOrUnitAdmin(long personId, long documentId) {
    try (var emHolder = PersistenceContext.getEntityManagerHolder()) {
        EntityManager em = emHolder.getEntityManager();
        CriteriaBuilderFactory cbf = CriteriaBuilderFactoryProducer.get(em);

        
        CriteriaBuilder<Long> issuerCheck = cbf.create(em, Long.class)
                .from(DocumentIssuerPO.class, "dg")  // discriminator-based subtype
                .innerJoin("dg.document", "d")
                .select("1")
                .where("dg.group.id").eq(personId)
                .where("d.id").eq(documentId)
                .setMaxResults(1);

        List<Long> issuerResult = issuerCheck.getResultList();
        if (!issuerResult.isEmpty()) {
            return true;
        }
   // 2ļøāƒ£ Check if the person is a Unit Admin of the document’s unit
            CriteriaBuilder<Long> adminCheck = cbf.create(em, Long.class)
                    .from(AbstractDocumentPO.class, "d")
                    .innerJoin("d.typeUnit", "tu")
                    .innerJoin("tu.unit", "u")
                    .innerJoin("TREAT(u.groups AS UnitAdminPO)", "ua")
                    .innerJoin("ua.members", "m")
                    .where("m.id").eq(personId)
                    .where("u.archived").eqLiteral(false)
                    .where("d.id").eq(documentId)
                    .select("1L")
                    .setMaxResults(1);

            return !adminCheck.getResultList().isEmpty();
        } catch (Exception e) {
            logger.error("An error occurred when checking issuer or unit admin role", e);
            return false;
        }
    }

I think there should not be hibernate hydration in such a query, but I am not sure.

There is none. As long as you don’t select anything that is of an entity type, no entities are going to be created.

1 Like