tdranv
tdranv

Reputation: 1330

Build a dynamic query with Hibernate (no Criteria API)

I have the following method:

public List<Parcel> filter(ParcelFilterParameters pfp) {
    try (var session = sessionFactory.getCurrentSession()) {
        var baseQuery = "from Parcel ";
        var filters = new ArrayList<String>();

        pfp.getCustomerId().ifPresent(e -> filters.add(" owner.id = :ownerId "));
        pfp.getCategoryId().ifPresent(e -> filters.add(" category.id = :categoryId "));


        if (!filters.isEmpty()) {
            baseQuery = baseQuery + " where " + String.join(" and ", filters);
        }

        Query<Parcel> query = session.createQuery(baseQuery, Parcel.class);

        pfp.getCustomerId().ifPresent(e -> query.setParameter("ownerId", e));
        pfp.getCategoryId().ifPresent(e -> query.setParameter("categoryId", e));
        return query.list();
    }
}

This is the ParcelFilterParameters class:

public class ParcelFilterParameters {
    private Optional<Integer> customerId;
    private Optional<Integer> categoryId;

    // getters and setters...
}

Although weird looking, it does work, but this repeats for a few other entities so I was wondering if I can make it more generic in some way (without using the Criteria API)? Or at least skip the second batch of ifPresent(e -> ...s?

Upvotes: 1

Views: 278

Answers (2)

Madlemon
Madlemon

Reputation: 331

Although you asked for no Criteria API usage consider using org.hibernate.criterion.Example. It scales well with a growing number of entities. The example introspects every field that is not null and will build your where-block from that.

First in the ParcelFilterParameters we should remove the Optioals from the fields since Example works fine with null-values. Then we build a Parcel-object from the ParcelFilterParameters and create an Example from that. The Example can be added as an Criterion to the query.

public List<Parcel> filter(ParcelFilterParameters pfp) {
    try (var session = sessionFactory.getCurrentSession()) {
        // Build example Parcel from ParcelFilterParameters
        Parcel parcel = new Parcel();
        // initialize customer/owner and category ...
        parcel.getOwner().setId(pfp.getCustomerId());
        parcel.getCategory().setId(pfp.getCategoryId());

        return session.createCriteria(Parcel.class)
                      .add(Example.create(parcel))
                      .list();
    }
}

Upvotes: 3

Nikolai  Shevchenko
Nikolai Shevchenko

Reputation: 7521

To avoid the second batch of ifPresent you can use map to hold all query parameters:

List<String> filters = new ArrayList<>();
Map<String, Object> params = new HashMap<>();

pfp.getCustomerId().ifPresent(e -> {
    filters.add(" owner.id = :ownerId ");
    params.put("ownerId", e);
});
pfp.getCategoryId().ifPresent(e -> {
    filters.add(" category.id = :categoryId ");
    params.put("categoryId", e);
});

and apply them all in single method call:

Query<Parcel> query = session.createQuery(baseQuery, Parcel.class);
query.setProperties(params);
return query.list();

Upvotes: 2

Related Questions