View source | Discuss this page | Page history | Printable version   
Toolbox
Main Page
Upload file
What links here
Recent changes
Help

PDF Books
Add page
Show collection (0 pages)
Collections help

Search

Hibernate 5.3 Migration Guide

Contents

Introduction

The following guide is intended to provide information and examples of changes that should be applied to the code in order to continue working properly after the upgrade of the Hibernate library, one of the main components of the Openbravo architecture.

Bulbgraph.png   In 3.0PR18Q4 release the Hibernate library used in Openbravo has been upgraded to version 5.3.2. It is important to read this guide before moving to that version.
Bulbgraph.png   In 3.0PR19Q3 release the Hibernate library used in Openbravo has been upgraded to version 5.4.2. This upgrade does not introduce any big change but just bug fixes. Therefore, note that the topics of this guide still apply when updating Openbravo from a version prior to 3.0PR18Q4.

To understand this document it requires to have experience with the DAL. See the Related Links section with some of the documents that makes sense to study before read this document.

About this Guide

Bulbgraph.png   The following guide contains the most important changes detected so far, but in any case it is strongly recommended to test all the custom code and modules in deep before proceeding with the update to version 3.0PR18Q4

Every item listed in this guide has an icon which describes its type. Their meaning are:

Runtime.png Runtime Errors / Flow Changes: this kind of items are those that in case they are not properly fixed then they can lead to runtime errors or unexpected changes in the execution flow.

Error.png Compilation Errors / Behavior Changes: these are those that can cause compilation errors or which behavior has changed with respect to the previous Hibernate version.

WarningIcon.png Clean code: this type holds those changes that are not required to make the code work but it is recommended to apply in order to have a cleaner code such as the usage of deprecated classes/methods.

NewFeature.png New Features: features not present in previous releases.

WarningIcon.png Deprecations

In this section, we discuss the more relevant classes and methods which are deprecated after the Hibernate upgrade to version 5.3.2.

org.hibernate.Query

The org.hibernate.Query class has been deprecated in favor of new org.hibernate.query.Query.

 
    // Hibernate 3.6
    ...
    import org.hibernate.Query;
    ...
 
    // Hibernate 5.3
    ...
    import org.hibernate.query.Query;
    ...

See the Typed Queries section for additional information.

org.hibernate.SQLQuery

The org.hibernate.SQLQuery can be used to execute native SQL queries:

 
    // Hibernate 3.6
    SQLQuery qry = OBDal.getInstance().getSession().createSQLQuery("select stragg(1) from dual");

This class has been deprecated and org.hibernate.query.NativeQuery class should be used instead:

 
    // Hibernate 5.3
    @SuppressWarnings("rawtypes")
    NativeQuery qry = OBDal.getInstance().getSession().createNativeQuery("select stragg(1) from dual");

org.hibernate.query.Query.setResultTransformer()

With a ResultTransformer it is possible to define how the results of a query should be handled, i.e., it can be used to change the "shape" of the query results.

In Hibernate 6.0, the ResultTransformer will be replaced by a @FunctionalInterface and for this reason, the setResultTransformer() method in org.hibernate.query.Query is deprecated.

There is no replacement for ResultTransformer in Hibernate 5.3, therefore as recommended here, for the moment it can be used as-is.

 
    @SuppressWarnings("deprecation")
    query.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);

org.hibernate.query.Query.getNamedParameters()

The org.hibernate.query.Query.getNamedParameters() method which returns an array containing the names of the named parameters used in a query is also deprecated:

 
    // Hibernate 3.6
    String[] namedParameters = query.getNamedParameters();

Now the query parameter meta-data should be accessed in order to retrieve a set with the names of the named parameters:

 
    // Hibernate 5.3
    Set<String> namedParameters = query.getParameterMetadata().getNamedParameterNames();

NewFeature.png Typed Queries

It is important to note that the new org.hibernate.query.Query is a raw type that allows to specify the type of the objects returned by the query. This means that the following code will generate compilation warnings starting from 3.0PR18Q4:

 
    String hql = "select r.startTime from ProcessRun r";
    Query query = OBDal.getInstance().getSession().createQuery(hql);
    @SuppressWarnings("unchecked")
    List<Date> startTimes = query.list();

This code should be modified as follows:

 
    String hql = "select r.startTime from ProcessRun r";
    Query<Date> query = OBDal.getInstance().getSession().createQuery(hql, Date.class);
    List<Date> startTimes = query.list();
Bulbgraph.png   Update/Delete queries can not be typed.

If a query used to update or delete rows is typed, then it will fail when executing it. To avoid warnings when not typing this kind of queries, the executeUpdate() method can be directly invoked:

 
    String hql= "delete from ProcessRun"; 
    int rowCount = OBDal.getInstance().getSession().createQuery(hql).executeUpdate(); // update/delete queries cannot be typed

Or if the reference for the query is needed, then the @SuppressWarnings("rawtypes") should be included:

 
    String hql= "delete from ProcessRun where client.id = :clientId"; 
    int rowCount;
    @SuppressWarnings("rawtypes")
    Query deleteQry = OBDal.getInstance().getSession().createQuery(hql); // update/delete queries cannot be typed
    deleteQry.setParameter("clientId", client.getId());
    rowCount = deleteQry.executeUpdate();

In addition, the OBQuery class now makes use of the org.hibernate.query.Query internally. For this reason, it is important to note that when using the setSelectClause() method the type of the object returned by the underlying query may change with respect to the type initially defined. So the methods of the OBQuery API that expects the initial type can not be invoked under these circumstances:

 
    // Hibernate 5.3
    OBQuery<Product> query = OBDal.getInstance().createQuery(Product.class,
        "where searchKey = 'ES/0001'");
    query.setSelectClause("name"); // changed query returning type
    // query.uniqueResult(); will fail due to the type change 
    String name = (String) query.uniqueResultObject();

Runtime.png Positional Parameters are not Supported

Support for legacy-style query parameter ('?') declarations in HQL/JPQL queries has been removed. This feature has been deprecated since Hibernate 4.1 and finally removed in 5.3 version.

Therefore, the following query declaration is not valid:

 
    Query<Product> query = OBDal.getInstance().getSession()
        .createQuery("from Product as p where p.name = ? and p.stocked = ?", Product.class);
    query.setParameter(0, "Ale Beer");
    query.setParameter(1, true);
Bulbgraph.png   Please note that although the previous code compiles successfully, it will be failing at runtime.

To make the previous query work fine it must use named parameters:

 
    Query<Product> query = OBDal.getInstance().getSession()
        .createQuery("from Product as p where p.name = :name and p.stocked = :isStocked", Product.class);
    query.setParameter("name", "Ale Beer");
    query.setParameter("isStocked", true);

In case of using OBQuery it is recommended to neither use positional parameters:

 
    OBQuery<Product> obQuery = OBDal.getInstance().createQuery(Product.class,
        "as p where p.name = ? and p.stocked = ?");
    List<Object> parameters = new ArrayList<>(2);
    parameters.add("Ale Beer");
    parameters.add(true);
    obQuery.setParameters(parameters);

Note that the previous query will not fail due to an internal mechanism that converts the positional parameters into named parameters. In any case, it is recommended to use named parameters instead and for this reason the OBQuery.setParameters() method is deprecated since 3.0PR18Q3 release.

OBQuery provides the setNamedParameters method to provide a map containing the named parameters with their respective values:

 
    OBQuery<Product> obQuery = OBDal.getInstance().createQuery(Product.class,
        "as p where p.name = :name and p.stocked = :isStocked");
    Map<String, Object> parameters = new HashMap<>(2);
    parameters.put("name", "Ale Beer");
    parameters.put("isStocked", true);
    obQuery.setNamedParameters(parameters);

Or alternatively:

 
    OBQuery<Product> obQuery = OBDal.getInstance().createQuery(Product.class,
        "as p where p.name = :name and p.stocked = :isStocked");
    obQuery.setNamedParameter("name", "Ale Beer");
    obQuery.setNamedParameter("isStocked", true);

Runtime.png Comparing Business Objects and IDs with Query Parameters

It is no longer possible to set a business object instead of its corresponding ID as a parameter value when comparing IDs, i.e, although semantically the following query is not correct, this was working fine before PR18Q4 release:

 
    // Hibernate 3.6
    Client client;
    ...
    String hql = "as p where p.client.id = :client";
    OBQuery<Product> query = OBDal.getInstance().createQuery(Product.class, hql);
    query.setNamedParameter("client", client);
    query.list();

After the upgrade, the previous query is throwing a java.lang.ClassCastException during the parameter processing. To make it work, the correct parameter value should be provided (the business object ID):

 
    // Hibernate 5.3
    Client client;
    ...
    String hql = "as p where p.client.id = :clientId";
    OBQuery<Product> query = OBDal.getInstance().createQuery(Product.class, hql);
    query.setNamedParameter("clientId", client.getId());
    query.list();

Runtime.png Exception Handling

Session Flush

Before the upgrade, if there were errors when flushing the DAL session, different kind of exceptions could be thrown depending on the type of error. Now the following exceptions are not being directly thrown but wrapped in a javax.persistence.PersistenceException:

This should be taken into account in that code which is directly catching any of the exceptions listed above for a try block where the session is flushed, like in the following example:

 
   // Hibernate 3.6
   ...
   try {
      OBDal.getInstance().flush();
      OBDal.getInstance().remove(bob);
      OBDal.getInstance().commitAndClose();
    } catch (org.hibernate.exception.GenericJDBCException e) {
      OBDal.getInstance().rollbackAndClose();
    }

Under this scenario, now a javax.persistence.PersistenceException should be caught in order to handle the error properly:

 
   // Hibernate 5.3
   ...
   try {
      OBDal.getInstance().flush();
      OBDal.getInstance().remove(bob);
      OBDal.getInstance().commitAndClose();
    } catch (org.hibernate.PersistenceException e) {
      OBDal.getInstance().rollbackAndClose();
    }


Bulbgraph.png   Note that to handle errors during the flush operation, there could be cases which were not directly catching those exceptions, but their parent exception org.hibernate.JDBCException or its parent org.hibernate.HibernateException. In those cases the same change applies.

beginTransaction() method

The OBDal.getInstance().getSession().beginTransaction() method now throws a java.lang.IllegalStateException if it is invoked when the transaction is already active.

org.hibernate.query.Query()

The org.hibernate.query.Query class wraps org.hibernate.QueryException inside a java.lang.IllegalArgumentException.

 
   // Hibernate 3.6
   try {
      ...
      Query query = OBDal.getInstance().getSession().createQuery("select a.id from UOM u");
      //                                                                 ^                                                                             
      //                                                                 |                                                                               
      //                                                          Note wrong alias                                                                    
      query.list();
    } catch (org.hibernate.QueryException e) {
      OBDal.getInstance().rollbackAndClose();
    }
 
   // Hibernate 5.3
   try {
      ...
      Query<String> query = OBDal.getInstance().getSession().createQuery("select a.id from UOM u", String.class);
      //                                                                         ^                                                                      
      //                                                                         |                                                                                                                                               
      //                                                                  Note wrong alias                                                                    
      query.list();
    } catch (java.lang.IllegalArgumentException e) {
      OBDal.getInstance().rollbackAndClose();
    }
Bulbgraph.png   Note that this change also affects the OBQuery class: now methods like count() will not directly thrown an org.hibernate.QueryException.

Oracle and Trigger Errors

Before the upgrade, if an error happened during the execution of a trigger having Oracle as the underlying database, the error was being thrown with a org.hibernate.QueryTimeoutException. Now this kind of errors are thrown using a org.hibernate.exception.GenericJDBCException.

 
   // Hibernate 3.6
   ...
   try {
      ...
      Query queryInsert = OBDal.getInstance().getSession().createQuery(insert.toString());
      queryInsert.executeUpdate();
    } catch (org.hibernate.QueryTimeoutException | org.hibernate.exception.GenericJDBCException e) {
      // Handle database trigger error on insert:
      // QueryTimeoutException -> Oracle
      // GenericJDBCException -> PostgreSQL
      OBDal.getInstance().rollbackAndClose();
    }
 
   // Hibernate 5.3
   ...
   try {
      ...
      Query queryInsert = OBDal.getInstance().getSession().createQuery(insert.toString());
      queryInsert.executeUpdate();
    } catch (org.hibernate.exception.GenericJDBCException e) {
      OBDal.getInstance().rollbackAndClose();
    }

Thus, we have now a more consistent behavior because a GenericJDBCException will be thrown when an error is raised from a trigger, both in PostgreSQL and Oracle.

Runtime.png Unbalanced Parentheses

After the upgrade, it is not possible to create queries with unbalanced parentheses because an org.hibernate.hql.internal.ast.QuerySyntaxException is thrown in that case:

 
    // Hibernate 5.3: an exception will be thrown after executing this
    Query<String> query = OBDal.getInstance().getSession()
        .createQuery("select c.symbol from Currency c where c.iSOCode IN ('EUR', 'USD'))", String.class); 
    //                                                                                 ^
    //                                                                                 |
    //                                                                         Note extra parentheses

Anyway queries must not have unbalanced parentheses, before 3.0PR18Q4 release this could lead to unexpected results.

Runtime.png Return Aliases

The org.hibernate.Query.getReturnAliases() method has been deprecated and its behavior has changed also. Let's say that we want to execute an HQL query like the one shown below:

 
   SELECT c.id, c.name AS name, c.organization FROM Country c

Before the upgrade, the getReturnAliases method was returning an array containing the following values: ["0", "name", "2"].

Now we are retrieving the following result: [null, "name", null]. This means that now null is returned for those columns in the select which does not have an alias.

Runtime.png Evicting Null Value

The behavior of Session.evict() method has changed in regard of null value. In previous versions, although it had no effect, it was possible to invoke this method with a null value:

 
    // Hibernate 3.6 
    // This will not fail even if bob is null
    OBDal.getInstance().getSession().evict(bob);

Now a NullPointerException is thrown in this very same case. For this reason we should add a null check or catch the exception in order to manage it properly:

 
    // Hibernate 5.3 
    if (bob != null) {
      OBDal.getInstance().getSession().evict(bob);
    }

Error.png Transaction Status

The org.hibernate.Transaction.wasRolledBack() and org.hibernate.Transaction.wasCommitted() methods are no longer available in the Transaction interface.

 
    // Hibernate 3.6
    public void onTransactionCompleted(@Observes TransactionCompletedEvent event) {
      ...
      if (event.getTransaction().wasRolledBack()) {
        ...
      } else if (event.getTransaction().wasCommitted()) {
        ...
      }
      ...
    }

Starting from 3.0PR18Q4, we have to make use of the getStatus() method which returns a value of the TransactionStatus enum:

 
    // Hibernate 5.3
    public void onTransactionCompleted(@Observes TransactionCompletedEvent event) {
      ...
      if (event.getTransaction().getStatus() == TransactionStatus.ROLLED_BACK) {
        ...
      } else if (event.getTransaction().getStatus() == TransactionStatus.COMMITTED) {
        ...
      }
      ...
    }

NewFeature.png Tuple Queries

A common way to create queries that return multiple columns is as follows:

 
    Query<Object[]> query = OBDal.getInstance().getSession()
        .createQuery("select c.id as id, c.name as name from Country c", Object[].class);
    for (Object[] entry : query.list()) {
      String id = (String) entry[0];
      String name = (String) entry[1];
      ...
    }

But it is also possible to achieve the same by using a Tuple Query which makes it possible to retrieve values from Tuple based on their alias instead of position:

 
    Query<Tuple> query = OBDal.getInstance().getSession()
        .createQuery("select c.id as id, c.name as name from Country c", Tuple.class);
    for (Tuple tuple : query.list()) {
      String id = (String) tuple.get("id");
      String name = (String) tuple.get("name");
    }
Bulbgraph.png   Note aliases are only available if they were explicitly declared in the Query by as keyword.

Although it is still possible to use positions:

 
    Query<Tuple> query = OBDal.getInstance().getSession()
        .createQuery("select c.id as id, c.name as name from Country c", Tuple.class);
    for (Tuple tuple : query.list()) {
      String id = (String) tuple.get(0);
      String name = (String) tuple.get(1);
      ...
    }

Besides, the javax.persistence.Tuple allows to retrieve the query aliases:

 
        Query<Tuple> query = OBDal.getInstance().getSession()
        .createQuery("select c.id as id, c.name as name from Country c", Tuple.class);
    for (Tuple tuple : query.list()) {
      for (TupleElement<?> tupleElement : tuple.getElements()) {
        String alias = tupleElement.getAlias();
        String value = (String) tuple.get(alias);
        ...
      }
    }

NewFeature.png CriteriaQuery

Criteria queries are essentially an object graph, where each part of the graph represents an increasing (as we navigate down this graph) more atomic part of query.

The first step in performing a javax.persistence.criteria.CriteriaQuery is building the graph, by using the javax.persistence.criteria.CriteriaBuilder. This class acts as a factory that helps us to build the individual parts of the query:

 
    CriteriaBuilder builder = OBDal.getInstance().getSession().getCriteriaBuilder();
    CriteriaQuery<Currency> criteria = builder.createQuery(Currency.class);
    Root<Currency> root = criteria.from(Currency.class);
    criteria.select(root);
    criteria.where(builder.like(root.get("symbol"), "%r"));
    criteria.orderBy(builder.desc(root.get("iSOCode")));

With the previous code we are creating a criteria query to retrieve all the currencies whose symbol ends with an 'r', in a descending order by the ISO code.

Finally, we have just to execute the query using that criteria:

 
    Query<Currency> query = OBDal.getInstance().getSession().createQuery(criteria);
    List<Currency> currencies = query.list();

To know more about criteria queries, refer here.

NewFeature.png Streams

The org.hibernate.query.Query class has a new method called stream() which can be used to handle the query results as a Stream.

The OBQuery API has been extended to make use of this new feature and it also provides a new stream() method that allows to make use of all the operations provided by the Stream API with the results obtained by the execution of the query:

 
    ...
    private static final Logger log = LoggerFactory.getLogger(MyClass.class);
    ...
    OBQuery<Currency> query = OBDal.getInstance().createQuery(Currency.class,
        "as c where c.standardPrecision = 2");
    query.stream().forEach(currency -> log.info("ISO Code: {}", currency.getISOCode()));

It is important to note that while streaming the query data it is being read and kept in memory, for this reason the session cache should be cleared once in a while in the same way it is explained here.

NewFeature.png Join unrelated entities

Find here an article describing this feature.

NewFeature.png ScrollableResults is now AutoCloseable

This allows to use them in try-with-resources:

 
    // Hibernate 3.6 (it continues working in 5.3)
    ScrollableResults results = queryService.scroll();
    try {
      ...
    } finally {
      results.close();
    }

is equivalent to:

 
   // Hibernate 5.3
   try (ScrollableResults results = queryService.scroll()) {
      ...      
   }

Error.png Registering SQL Functions

The deprecated OBDal.getInstance().registerSQLFunction method has been removed because it is no longer supported to register SQL functions in that way.

The standard way of registering SQL functions is by using an org.openbravo.dal.core.SQLFunctionRegister as explained here.

Error.png Openbravo API Changes

See here to check the full list of API changes introduced in Openbravo with the upgrade.

Related Links

Data Access Layer

Data Access Layer Performance

How to do a complex query using the DAL-1

How to do a complex query using the DAL-2

Retrieved from "http://wiki.openbravo.com/wiki/Hibernate_5.3_Migration_Guide"

This page has been accessed 33,827 times. This page was last modified on 3 May 2019, at 07:31. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.