Red Hat

In Relation To

The Hibernate team blog on everything data.

Hibernate Validator 6 is going to be the Reference Implementation of Bean Validation 2.0. This Beta1 release is coordinated with the 2.0.0.Beta1 release (Public Review Draft) of the Bean Validation specification.

It is also a playground used to validate future enhancements of the Bean Validation specification so feedback on the subjects presented here is very welcome!

Note that Hibernate Validator 6 requires JDK 8 or above.

What’s new since Alpha2

Bean Validation 2.0.0.Beta1 support

The main goal of this version is to upgrade to Bean Validation 2.0.0.Beta1.

This was mainly small adjustments to be on par with the latest changes in the specification but we also added a couple of features related to the validation of container elements (think List<@NotBlank String>).

Metadata API support for container element constraints

Until now, the container element constraints were not exposed via the metadata API. You can now obtain them via the API:

// Given a class User with a property declared as Map<@Valid AddressType, @NotNull Address> addresses

PropertyDescriptor propertyDescriptor = validator
        .getConstraintsForClass( User.class )
        .getConstraintsForProperty( "addresses" );

List<ContainerElementTypeDescriptor> containerElementTypeDescriptors =
        propertyDescriptor.getContainerElementTypes();

ContainerElementTypeDescriptor mapKeyDescriptor = containerElementTypeDescriptors.get( 0 );
ContainerElementTypeDescriptor mapValueDescriptor = containerElementTypeDescriptors.get( 1 );

// get the type argument index corresponding to this container element
assert mapKeyDescriptor.getTypeArgumentIndex() == 0;
assert mapValueDescriptor.getTypeArgumentIndex() == 1;

// get the constraint descriptors for this container element
assert mapKeyDescriptor.getConstraintDescriptors().size() == 0;
assert mapValueDescriptor.getConstraintDescriptors().size() == 1;

// indicates if the validation is cascaded
assert mapKeyDescriptor.isCascaded() == true;
assert mapValueDescriptor.isCascaded() == false;

// get the potential nested container elements
assert mapKeyDescriptor.getContainerElementTypes().size() == 0;
assert mapValueDescriptor.getContainerElementTypes().size() == 0;

Support for group conversions in container elements

We added support for cascaded validation of container elements a while ago but we did not support group conversions. They are now supported.

public class User {

    private Map<@Valid AddressType, @Valid @ConvertGroup(from = Default.class, to = BasicChecks.class) Address> addresses;
}

Polished support for the new built-in annotations

The error messages were missing for the new built-in annotations, we fixed it. Note that several translations need an update so if you speak a language other than English, German, French or Ukrainian, don’t hesitate to step up to the plate and send a pull request.

We also added support for @Positive and @Negative to the JavaMoney types.

And, finally, we added support for these new constraints to the annotation processor.

And a few other things

  • @SafeHtml now supports relative URLs thanks to the new baseURI attribute.

  • We fixed a few things to support the latest JDK 9 early access releases.

The complete list of 23 fixed issues can be found in the release notes.

Getting 6.0.0.Beta1

To get the release with Maven, Gradle etc. use the GAV coordinates org.hibernate.validator:{hibernate-validator|hibernate-validator-cdi|hibernate-validator-annotation-processor}:6.0.0.Beta1. Note that the group id has changed from org.hibernate (Hibernate Validator 5 and earlier) to org.hibernate.validator (from Hibernate Validator 6 onwards).

Alternatively, a distribution bundle containing all the bits is provided on SourceForge (TAR.GZ, ZIP).

Feedback, issues, ideas?

To get in touch, use the usual channels:

What’s next?

Bean Validation 2.0 is currently in the Public Review phase, the Public Review Ballot will take place at the beginning of June. The Proposed Final Draft is planned to be released shortly thereafter, so if you spot any remaining issues or shortcomings in the spec draft, please let us know as soon as possible.

Hibernate Validator 6 is still under active development. We’ll keep you posted with our progress.

We just published Hibernate Search version 5.8.0.Beta2, with bugfixes and improvements over 5.8.0.Beta1.

Hibernate Search 5.8.0.Beta2, just as 5.7.0.Final, is only compatible with Hibernate ORM 5.2.3 and later.

If you need to use Hibernate ORM 5.0.x or 5.1.x and cannot upgrade, please use Hibernate Search 5.6.1.Final.

About 5.8

Hibernate Search 5.8 is about:

  • making the Elasticsearch integration compatible with Elasticsearch 5.x (done);

  • improving performance of the Elasticsearch integration (in progress);

  • introducing a new DSL for defining analyzers (in progress);

  • ensuring that Hibernate Search will work well with Java 9 (done, though Java 9 may still change);

  • improving and documenting Wildfly Swarm integration (in discussion);

  • removing the need for class definition on master nodes in JMS/JGroups integration (in discussion);

  • and of course, fixing reported bugs.

You can have a look at the roadmap for more details.

What’s new since the first Beta?

For a full list of changes since 5.8.0.Beta1, please see the release notes.

Below is a summary of the main changes:

  • HSEARCH-2606: duplicate parameters in analyzer definitions are now detected automatically and trigger an error on startup.

  • HSEARCH-2014 (local Lucene indexes only): index size is now reported as part of Hibernate Search statistics, available over JMX in particular if enabled.

  • HSEARCH-2208: use of org.apache.lucene.search.Filter throughout the APIs has been deprecated. You should use org.apache.lucene.search.Query instead, because org.apache.lucene.search.Filter will be removed when we next upgrade the Lucene dependency (in Hibernate Search 6). See the updated documentation for examples on how to achieve filters using queries (you might need to refresh your browser cache to see the latest version of the documentation).

  • HSEARCH-2675: master election when the current master fails now works correctly with the JGroups backend.

  • HSEARCH-1886: we made preliminary changes to make dynamic sharding work correctly with replicated (JGroups/JMS) backends. You can follow progress on HSEARCH-2676.

  • HSEARCH-2502: the worker can now be set to execute works asynchronously even when using Elasticsearch, by setting the same *.worker.execution configuration option as when using local Lucene indexes.

We’d also like to thank Andrej Golovnin for his work on HSEARCH-2691, fixing a bug related to query caching.

How to get this release

All versions are available on Hibernate Search’s web site.

Ideally use a tool to fetch it from Maven central; these are the coordinates:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-orm</artifactId>
   <version>5.8.0.Beta2</version>
</dependency>

To use the experimental Elasticsearch integration you’ll also need:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-elasticsearch</artifactId>
   <version>5.8.0.Beta2</version>
</dependency>

Downloads from Sourceforge are available as well.

Feedback, issues, ideas?

To get in touch, use the following channels:

Hibernate ORM 5.1.6.Final released

Posted by    |       |    Tagged as Hibernate ORM Releases

We decided to do another release of the 5.1 series to fix some bugs to be included in an upcoming version of WildFly. This may be the last release of the 5.1 series, so we recommend that you migrate to 5.2 for future bugfixes.

Hibernate ORM 5.1.6.Final:

For information on consuming the release via your favorite dependency-management-capable build tool, see http://hibernate.org/orm/downloads/

Hibernate Community Newsletter 9/2017

Posted by    |       |    Tagged as Discussions Hibernate ORM

Welcome to the Hibernate community newsletter in which we share blog posts, forum, and StackOverflow questions that are especially relevant to our users.

Articles

Hibernate uses Proxy objects for lazy associations. If you want to understand how Porxy objects work and how you can unproxy them to the actual entities they represent, then you should definitely read this article.

Romain Manni-Bucau wrote a very interesting article which points out how you should be merging incoming and existing association collections when using Hibernate.

Getting access to the underlying database metadata is very easy with Hibernate 5. You just have to know how to make use of the Integrator SPI mechanism, as explained in this article.

Knowing how to implement equals and hashCode is of paramount importance when using JPA and Hibernate. Read this article by Steven Schwenke to find out business keys are very suitable for this task.

Bean Validation is a very convenient mechanism for validating entity state. Check out this article to find out how you can make sure that an integer value is within a bounded range.

Simple Query String, what about it?

Posted by    |       |    Tagged as Hibernate Search

In the last Hibernate Search release announcement, you might have noticed something about Simple Query Strings.

The documentation is still a little sparse and we wanted to give this feature some more light and have some feedback before going Final with it.

This feature is part of the already released 5.8.0.Beta1 so you can already play with it (either with the Lucene backend or with the new Elasticsearch backend).

What’s a Simple Query String?

Let’s begin with some history.

Lucene 4.7.0 introduced a new query parser, the SimpleQueryParser, presented as a "parser for human-entered queries". The point of this parser is to be a very simple lenient state machine to parse queries entered by your end users.

The parser is capable to transform keyword "some phrase" -keywordidontwant fuzzy~ prefix* into a Lucene query, giving your users a little more power (phrase queries, fuzzy queries, boolean operators…​). The lenient part is important as it will try to build the best possible query without throwing a parse exception, even if the query is not what we would consider syntactically correct.

Another nice feature is that it allows to search on multiple fields. You basically end up establishing the following contract with Lucene:

  • users will enter a search query (more or less syntactically correct)

  • it will search on the fields you have specified (and you can also specify a specific boost for each field)

  • you can enable each of the features that you want to expose to the users (i.e. you can enable the phrase queries but not the boolean operators)

  • building the query won’t throw an exception

So, what is a Simple Query String for us? Just a string following the SimpleQueryParser syntax or, in user terms, what the user entered in the search box.

Let’s take an example

In the following, we will base our discussion on the following Book entity:

@Entity
@Indexed
public class Book {

    // [...]

    @Field
    private String title;

    @Field
    private String summary;

    @Field
    private String author;

    // [...]
}

Our goal is to be able to search war peace on the title field with a boost of 5 and on the summary field with a boost of 2 and have only the results containing both words. A Book containing war in title and peace in summary should be considered a valid result.

Until now, to fulfill these requirements with Hibernate Search, you had the following possibilities:

Use the DSL
  • Manually split the search query into keywords

  • Manually build a (rather complicated) query using the keyword() entry point of the DSL (keyword() only supports OR so you would have to build several different keyword() queries for each keyword and for each field)

  • Downsides:

    • it is not possible to allow the users to optionally enter phrase queries, fuzzy queries…​ without implementing a parser (or having checkbox options to enable them)

    • it might lead to a lot of boilerplate code if you want to search in more than 2 fields

Use the Lucene MultiFieldQueryParser
  • Quite efficient

  • Downsides:

    • Not lenient: can throw a ParseException

    • You expose the full power of the Lucene default QueryParser which might not be what you want

    • You can define the default fields but the user can search on other fields with the field:keyword syntax: it might not be what you want

How would we do it with the new simpleQueryString() DSL entry point?

It is as simple as:

String simpleQueryString = "war peace"; // what the end user is looking for

QueryBuilder qb = fullTextSession.getSearchFactory()
            .buildQueryBuilder()
            .forEntity( Book.class)
            .get(); // instantiate the QueryBuilder providing the DSL

Query query = qb.simpleQueryString()
            .onField( "title" ).boostedTo( 5.0f )
            .andField( "summary" ).boostedTo( 2.0f )
            .withAndAsDefaultOperator() // we want AND to be the default operator
            .matching( simpleQueryString )
            .createQuery();
List<Book> results = fullTextSession.createFullTextQuery( query, Book.class ).getResultList();

Important precision: the default operator used if you don’t define the operator explicitly is OR. In the general case, it might not be what you’re looking for, thus the call to withAndAsDefaultOperator() in the example above.

If you also want to search on the author field, just add another andField() clause and you’re done.

The thing I particularly like about it is the fact that you’re able to define a simple contract with your Lucene index and that the users have some flexibility to define more advanced queries inside the contract you defined:

  • -war peace: documents contain peace but not war

  • war | peace: documents contain war or peace (you can omit the | operator if it is defined as the default)

  • war + pea*: documents contain war and at least a word starting with pea (you can omit the + operator if it is defined as the default)

  • "war and peace": documents contain the phrase war and peace

  • pease~: documents contain pease with a fuzzy search so documents containing peace will be returned too

  • war + (peace | harmony): documents contain war and either peace or harmony

  • and any combinations of the above…​

  • or none: simple searches are obviously also supported!

What do you think about it?

Still there? We have a few questions for you.

First, after a lot of discussions, we have chosen to name the DSL entry point simpleQueryString(). It has the advantage to be consistent with Elasticsearch. If you can think of a better (and maybe more explicit) name, it’s not too late to join the party! Once we go final, we will stick to this name foreeeever.

Your feedback is very welcome in the comments below or on the hibernate-dev mailing list.

Secondly, for now, we haven’t exposed the ability to disable particular features. By default, the following features are all enabled:

  • boolean operators (+, - and |)

  • precedence operators (parenthesis)

  • phrase search ("some phrase")

  • prefix operator (prefix*)

  • fuzzy operator (fuzzy~)

  • slop for phrase search ("some phrase"~2)

Think it might be useful to be able to disable features? Feel free to open an issue on our JIRA or even better propose a pull request! You can find the original patch here: https://github.com/hibernate/hibernate-search/pull/1318, it might help you to start.

Just a quick heads-up to French-speaking developers: I will be presenting the Elasticsearch integration in Hibernate Search at the Strasbourg Java User Group (ElsassJUG) meetup, at 7 PM on Wednesday 26th of April.

I will briefly introduce full-text search (why and how it’s done), then present how to use Hibernate Search to keep Lucene indexes in sync with your Hibernate ORM entities, and I will show you how easy it is to target Elasticsearch instead of local Lucene indexes since Hibernate Search 5.6.0.

For more information about the location or to register, please refer to the Meetup page.

Hibernate Community Newsletter 8/2017

Posted by    |       |    Tagged as Discussions Hibernate ORM

Welcome to the Hibernate community newsletter in which we share blog posts, forum, and StackOverflow questions that are especially relevant to our users.

Articles

The Hibernate ResultTransformer is extremely useful to customize the way you fetch data from the database. Check out this article to learn more about this topic.

JPA and Hibernate use the first-level cache as a staging buffer for every read/write operation, and understanding its inner workings is very important if you want to use JPA effectively. For more details, you should definitely read this article.

Marco Behler offers 6 very practical video tutorials for Hibernate beginners.

Dealing with difficult software problems is easier than you might think. Check out this article for more details on how you can solve any software issue with the help of our wonderful software community.

If you wonder why you should choose Hibernate over plain JDBC, this article gives you 15 reasons why Hibernate is worth using.

This very short article offers a quick introduction to mapping a bidirectional one-to-many association. If you want to know what is the best way to map a one-to-many database relationship, then you should definitely read this article as well.

Database concurrency control is a very complex topic. PostgreSQL advisory locks are a very useful concurrency control API which you can use to implement multi-node coordination mechanisms. Check out this article for more details on this topic.

Time to upgrade

Hibernate ORM 5.2.10 has been released, as well as Hibernate Search 5.8.0.Beta1 which is now compatible with ElasticSearch 5.

We just published Hibernate Search version 5.8.0.Beta1, which is now compatible with Elasticsearch versions 5.x.

New improved Elasticsearch client

This release now uses the new Elasticsearch REST client, which is expected to be a safe choice in terms of long term maintenance as it’s sponsored and recommended by the Elasticsearch team.

Compared to the driver we used previously, this one uses a state of the art reactive architecture, so we can take advantage of more efficient resource utilization.

Elasticsearch 2.x is still supported

The new driver is backwards compatible, so we’ll still be able to connect to clusters running Elasticsearch 2.x.

This doesn’t need any configuration flag as Hibernate Search can automatically detect the version of Elasticsearch it’s being pointed to.

The features supported by Elasticsearch 5.x and 2.x are however slightly different and you’ll find that some low level mapping features are documented as compatible with only one specific version.

The migration guide will be updated when this minor release will be feature complete.

New Simple Query String supported by the Query builder DSL

The useful capabilities from Lucene’s SimpleQueryParser are now conveniently exposed by our higher level DSL.

We’ll publish a detailed blog about this new feature soon: stay tuned!

If you can’t wait, we won’t prevent you from peeking into the Simple Query String documentation.

Simplified JNDI configuration

If you integrated any external component into Hibernate Search using JNDI, for example a JMS queue or an Infinispan cache, this configuration was simplified.

You will no longer need to set Hibernate Search specific configuration properties such as how to set the InitialContext for JNDI lookups: only configure Hibernate ORM, and Hibernate Search will inherit the same settings.

How to get these releases

All versions are available on Hibernate Search’s web site.

Ideally use a tool to fetch it from Maven central; these are the coordinates:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-orm</artifactId>
   <version>5.8.0.Beta1</version>
</dependency>

To use the experimental Elasticsearch integration you’ll also need:

<dependency>
   <groupId>org.hibernate</groupId>
   <artifactId>hibernate-search-elasticsearch</artifactId>
   <version>5.8.0.Beta1</version>
</dependency>

Downloads from Sourceforge are available as well.

Feedback

Feedback always welcome!

Please let us know of any problem or suggestion by creating an issue on JIRA, or by sending an email to the developer’s developer’s mailing lists, or posting on the forums.

We also monitor Stack Overflow; when posting on SO please use the tag hibernate-search.

Tenth bug-fix release for ORM 5.2

Posted by    |       |    Tagged as Hibernate ORM Releases

The Tenth bug-fix release for Hibernate ORM 5.2 has just been published. It is tagged at https://github.com/hibernate/hibernate-orm/releases/tag/5.2.10

The complete list of changes can be found here (or here for people without a Hibernate Jira account).

For information on consuming the release via your favorite dependency-management-capable build tool, see http://hibernate.org/orm/downloads/

The release bundles can be obtained from SourceForge or BinTray.

Accessing private state of Java 9 modules

Posted by    |       |    Tagged as Discussions

Data-centric libraries often need to access private state of classes provided by the library user.

An example is Hibernate ORM. When the @Id annotation is given on a field of an entity, Hibernate will by default directly access fields - as opposed to calling property getters and setters - to read and write the entity’s state.

Usually, such fields are private. Accessing them from outside code has never been a problem, though. The Java reflection API allows to make private members accessible and access them subsequently from other classes. With the advent of the module system in Java 9, rules for this will change a bit, though.

In the following we’ll explore the options authors of a library provided as a Java 9 module have to access private state of classes defined in other modules.

An example

As an example, let’s consider a simple method which takes an object - e.g. an instance of an entity type defined by the user - and a field name and returns the value of the object’s field of that name. Using reflection, this method could be implemented like this (for the sake of simplicity, we are ignoring the fact that a security manager could be present):

package com.example.library;

public class FieldValueAccessor {

    public Object getFieldValue(Object object, String fieldName) {
        try {
            Class<?> clazz = object.getClass();
            Field field = clazz.getDeclaredField( fieldName );
            field.setAccessible( true );
            return field.get( object );
        }
        catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            throw new RuntimeException( e );
        }
    }
}

By calling Field#setAccessible() we can obtain the field’s value in the following, even if it had been declared private. The module descriptor for the library module is trivial, it just exports the package of the accessor class:

module com.example.library {
    exports com.example.library;
}

In a second module, representing our application, let’s define a simple "entity":

package com.example.entities;

public class MyEntity {

    private String name;

    public MyEntity(String name) {
        this.name = name;
    }

    // ...
}

And also a simple main method which makes use of the accessor to read a field from the entity:

package com.example.entities;

public class Main {
    public static void main(String... args) {
        FieldValueAccessor accessor = new FieldValueAccessor();
        Object fieldValue = accessor.getFieldValue( new MyEntity( "hey there" ), "name" );
        assert "hey there".equals( fieldValue );
    }
}

As this module uses the library module, we need to declare that dependency in the entity module’s descriptor:

module com.example.myapp {
    requires com.example.library;
}

With the example classes in place, let’s run the code and see what happens. It would have been fine on Java 8, but as of Java 9 we’ll see this exception instead:

java.lang.reflect.InaccessibleObjectException:
Unable to make field private final java.lang.String com.example.entities.MyEntity.name accessible:
module com.example.myapp does not "opens com.example.entities" to module com.example.library

The call to setAccessible() fails, as by default code in one module isn’t allowed to perform so-called "deep reflection" on code in another (named) module.

Open this module!

Now what can be done to overcome this issue? The error message is giving us the right hint already: the package with the type to reflect on must be opened to the module containing the code invoking setAccessible().

If a package has been opened to another module, that module can access the package’s types reflectively at runtime. Note that opening a package will not make it accessible to other modules at compile time; this would require the package to be exported instead (as in the case of the library module above).

There are several options for opening a package. The first is to make the module an open module:

open module com.example.myapp {
    requires com.example.library;
}

This opens up all packages in this module for reflection by all other modules (i.e. this would be the behavior as known from other module systems such as OSGi). In case you’d like some more fine-grained control, you can opt to open specific packages only:

module com.example.myapp {
    opens com.example.entities;
    requires com.example.library;
}

This will allow for deep reflection on the entities package, but not on other packages within the application module. Finally, there is the possibility to limit an opens clause to one or more specific target modules:

module com.example.myapp {
    opens com.example.entities to com.example.library;
    requires com.example.library;
}

That way the library module is allowed to perform deep reflection, but not any other module.

No matter which of these options we use, the library module can now make private fields of types in the entities package of the entities module accessible and subsequently read or write their value.

Opening up packages in one way or another lets library code written prior to Java 9 continue to function as before. It requires some implicit knowledge, though. I.e. application developers need to know which libraries need reflective access to which types so they can open up the right packages. This can become tough to manage in more complex applications with multiple libraries performing reflection.

Luckily, there’s a more explicit approach in the form of variable handles.

Can you handle the var?

Variable handles - defined by JEP 193 - are a very powerful addition to the Java 9 API, providing "read and write access to [variables] under a variety of access modes". Describing them in detail would go far beyond the scope of this post (refer to the JEP and this article if you would like to learn more). For our purposes let’s focus on their capability for accessing fields, representing an alternative to the traditional reflection-based approach.

So how could our FieldValueAccessor class be implemented using variable handles?

Var handles are obtained via the MethodHandles.Lookup class. If such lookup has "private access" to the entities module, it will let us access private fields of that module’s types. To get hold of such lookup, we let the client code pass in a lookup when bootstrapping the library code:

Lookup lookup = MethodHandles.lookup();
FieldValueAccessor accessor = new FieldValueAccessor( lookup );

In FieldValueAccessor#getFieldValue() we can now use the method MethodHandles#privateLookupIn() which will return a new lookup granting private access to the given entity instance. From that lookup we can eventually obtain a VarHandle which allows us to get the object’s field value:

public class FieldValueAccessor {

    private final Lookup lookup;

    public FieldValueAccessor(Lookup lookup) {
        this.lookup = lookup;
    }

    public Object getFieldValue(Object object, String fieldName) {
        try {
            Class<?> clazz = object.getClass();
            Field field = clazz.getDeclaredField( fieldName );

            MethodHandles.Lookup privateLookup = MethodHandles.privateLookupIn( clazz, lookup );
            VarHandle handle = privateLookup.unreflectVarHandle( field );

            return handle.get( object );
        }
        catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException( e );
        }
    }
}

Note that this only works if the original lookup has been created by code in the entities module.

This is because MethodHandles#lookup() is a caller sensitive method, i.e. the returned value will depend on the direct caller of that method. privateLookupIn() checks whether the given lookup is allowed to perform deep reflection on the given class. Thus a lookup obtained in the entities module will do the trick, whereas a lookup retrieved in the library module wouldn’t be of any use.

Which route to take?

Both discussed approaches let libraries access private state from Java 9 modules.

The var handle approach makes the requirements of the library module more explicit, which I like. Expecting a lookup instance during bootstrap should be less error-prone than the rather implicit requirement for opening up packages or modules.

Mails by the OpenJDK team also seem to suggest that - together with their siblings, method handles - var handles are the way to go in the long term. Of course it requires the application module to be cooperative and pass the required lookup. It remains to be seen how this could look like in container / app server scenarios, where libraries typically aren’t bootstrapped by the application code but by the server runtime. Injecting some helper code for obtaining the lookup object upon deployment may be one possible solution.

As var handles only are introduced in Java 9 you might want to refrain from using them if your library is supposed to run with older Java versions, too (actually, you can do both by building multi-release JARs). A very similar approach can be implemented in earlier Java versions using method handles (see MethodHandles.Lookup#findGetter()). Unfortunately, though, there’s no official way to obtain method handles with private access prior to Java 9 and the introduction of privateLookupIn​(). Ironically, the only way to get such handles is employing some reflection.

The final thing to note is that there may be a performance advantage to using var and method handles, as access checking is done only once when getting them (as opposed to every invocation). Some proper benchmarking would be in order, though, to see what’s the difference in a given use case.

As always, your feedback is very welcome. Which approach do you prefer? Or perhaps you’ve found yet other solution we’ve missed so far? Let us know in the comments below.

back to top