Introduction
Starting in WildFly 14.0.0.Final, (JPA 2.2) Hibernate ORM version 5.3.x is included, instead of Hibernate 5.1.x, which is great unless your application depends on certain Hibernate ORM 5.1 (native) classes that have changed in ORM 5.2 + 5.3. Refer to the Hibernate migration guides [1][2] to see what changed in ORM 5.2 + 5.3.
Migration help
To help you with your migration to WildFly 14+, we introduced the Hibernate bytecode transformer [3], which will update your application classes at the time that your application is deployed by the WildFly application server (only if you enable the bytecode transformer). The on-disk copy of your application is not modified, instead only the loaded (into computer memory) copy of your application is changed by the Hibernate bytecode transformer.
If your application depends on any of the Hibernate (native) interfaces mentioned in [3] documentation, you have two options, when migrating to WildFly 14 (or later), either compile your application against Hibernate ORM 5.3 and fix any compile errors that you get, or enable the Hibernate bytecode transformer for your application. The Hibernate bytecode transformer is disabled by default, it can be enabled via the -DHibernate51CompatibilityTransformer=true system property or via the jboss-deployment-structure (see [3] for example).
You definitely should try to recompile your application against Hibernate 5.3.x, to see if you get any compile errors (due to your application depending on Hibernate interfaces changed in 5.3). If you get zero compile errors, then you shouldn't need to use the Hibernate bytecode transformer. For those that get compile errors, keep reading below to learn more.
Note that the Hibernate bytecode transformer cannot (correctly) transform applications that have already been compiled against Hibernate ORM 5.3, please only use the transformer against your applications that have been compiled/built with ORM 5.1.
How are application classfiles transformed
If you want to take a quick look at the transformer source code, you can open it via [4] (for those that like to jump directly into the code). Also take a quick look at the ASM bytecode manipulation project [5], which does all of the work of actually modifying your application class definitions (again, only in memory).
Basically, the Hibernate transformer [4] is called with a copy of each application class in byte[] format, after the class is loaded into memory but before its used by the application. Using the ASM [5] project, we check if the loaded class, has a Hibernate user type as a super class, as well as a few other Hibernate classes. We then make adjustments in your application class, to account for parameter type changes, references to o.h.FlushMode are changed, as well as change types passed to Hibernate calls from your application. There are a few other changes that we make as well.
List of bytecode transformations that are performed
- In user type classes, change methods with parameter type org.hibernate.engine.spi.SessionImplementor to instead use type org.hibernate.engine.spi.SharedSessionContractImplementor, this is done for implementations of:
- org.hibernate.usertype.UserType.
- org.hibernate.usertype.CompositeUserType.
- org.hibernate.usertype.UserCollectionType.
- org.hibernate.usertype.UserVersionType.
- org.hibernate.type.Type.
- org.hibernate.type.SingleColumnType.
- org.hibernate.type.AbstractStandardBasicType.
- org.hibernate.type.ProcedureParameterExtractionAware.
- org.hibernate.type.ProcedureParameterNamedBinder.
- org.hibernate.type.VersionType.
- In user type classes, when calling org.hibernate.* class methods, cast SessionImplementor to SharedSessionContractImplementor.
- Change calls to org.hibernate.BasicQueryContract.getFlushMode(), to instead call BasicQueryContract.getHibernateFlushMode().
- Change calls to org.hibernate.Session.getFlushMode(), to instead call Session.getHibernateFlushMode().
- Change calls to org.hibernate.Query.getFirstResult(), to instead call Query.getHibernateFirstResult(), so null can be returned when the value is uninitialized (note that 0 is returned instead of negative values).
- Change calls to org.hibernate.Query.getMaxResults(), to instead call Query.getHibernateMaxResults(), so that null will be returned when the value is uninitialized or org.hibernate.Query#setMaxResults() was called with a value <= 0.
- Change calls to org.hibernate.Query.setFirstResult(), to instead call Query.setHibernateFirstResult(), so that calls to set a value < 0 results in pagination starting with the 0th row as was done in Hibernate ORM 5.1.
- Change calls to org.hibernate.Query.setMaxResults(), to instead call Query.setHibernateMaxResults(), so that passed values <= 0 are treated the same as uninitialized.
- Change references to NEVER field in enum org.hibernate.FlushMode, to instead reference FlushMode.MANUAL.
Please give us feedback on problems
Let us know (in WildFly forums) how the Hibernate bytecode transformer works for you. We recently made some additional changes in the transformer for WildFly 17.0.0.Final, so you could try upgrading to WF17 if you hit problems.
Additional tips for troubleshooting with the Hibernate bytecode transformer
There are a few reasons why you should try enabling the -DHibernate51CompatibilityTransformer.showTransformedClassFolder=LocalFolderPath (replace "LocalFolderPath" with a valid local filesystem path) system property at least once when using the transformer. For any class that the transformer rewrites, the specified output folder (it must already exist), will contain a (low level) description of your modified application classes. For example, I enabled this option while transforming the [6] BitSetType test class (as part of a WildFly testsuite run) and got:
// class version 52.0 (52)
// access flags 0x21
// signature Lorg/hibernate/type/AbstractSingleColumnStandardBasicType<Ljava/util/BitSet;>;Lorg/hibernate/type/DiscriminatorType<Ljava/util/BitSet;>;
// declaration: org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType extends org.hibernate.type.AbstractSingleColumnStandardBasicType<java.util.BitSet> implements org.hibernate.type.DiscriminatorType<java.util.BitSet>
public class org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType extends org/hibernate/type/AbstractSingleColumnStandardBasicType implements org/hibernate/type/DiscriminatorType {
// compiled from: BitSetType.java
// access flags 0x19
public final static Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; INSTANCE
// access flags 0x9
public static Z $_org_jboss_as_hibernate_Hibernate51CompatibilityTransformer_transformed_$
// access flags 0x1
public <init>()V
L0
LINENUMBER 45 L0
ALOAD 0
GETSTATIC org/hibernate/type/descriptor/sql/VarcharTypeDescriptor.INSTANCE : Lorg/hibernate/type/descriptor/sql/VarcharTypeDescriptor;
GETSTATIC org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetTypeDescriptor.INSTANCE : Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetTypeDescriptor;
INVOKESPECIAL org/hibernate/type/AbstractSingleColumnStandardBasicType.<init> (Lorg/hibernate/type/descriptor/sql/SqlTypeDescriptor;Lorg/hibernate/type/descriptor/java/JavaTypeDescriptor;)V
L1
LINENUMBER 46 L1
RETURN
L2
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
// access flags 0x1
public stringToObject(Ljava/lang/String;)Ljava/util/BitSet; throws java/lang/Exception
L0
LINENUMBER 50 L0
ALOAD 0
ALOAD 1
INVOKEVIRTUAL org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.fromString (Ljava/lang/String;)Ljava/lang/Object;
CHECKCAST java/util/BitSet
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
LOCALVARIABLE xml Ljava/lang/String; L0 L1 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
public objectToSQLString(Ljava/util/BitSet;Lorg/hibernate/dialect/Dialect;)Ljava/lang/String; throws java/lang/Exception
L0
LINENUMBER 55 L0
ALOAD 0
ALOAD 1
INVOKEVIRTUAL org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.toString (Ljava/lang/Object;)Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
LOCALVARIABLE value Ljava/util/BitSet; L0 L1 1
LOCALVARIABLE dialect Lorg/hibernate/dialect/Dialect; L0 L1 2
MAXSTACK = 2
MAXLOCALS = 3
// access flags 0x1
public getName()Ljava/lang/String;
L0
LINENUMBER 60 L0
LDC "bitset"
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1
public replace(Ljava/lang/Object;Ljava/lang/Object;Lorg/hibernate/engine/spi/SharedSessionContractImplementor;Ljava/lang/Object;Ljava/util/Map;Lorg/hibernate/type/ForeignKeyDirection;)Ljava/lang/Object;
L0
LINENUMBER 71 L0
ALOAD 0
ALOAD 1
ALOAD 2
ALOAD 3
ALOAD 4
ALOAD 5
ALOAD 6
INVOKESPECIAL org/hibernate/type/AbstractSingleColumnStandardBasicType.replace (Ljava/lang/Object;Ljava/lang/Object;Lorg/hibernate/engine/spi/SharedSessionContractImplementor;Ljava/lang/Object;Ljava/util/Map;Lorg/hibernate/type/ForeignKeyDirection;)Ljava/lang/Object;
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
LOCALVARIABLE original Ljava/lang/Object; L0 L1 1
LOCALVARIABLE target Ljava/lang/Object; L0 L1 2
LOCALVARIABLE session Lorg/hibernate/engine/spi/SessionImplementor; L0 L1 3
LOCALVARIABLE owner Ljava/lang/Object; L0 L1 4
LOCALVARIABLE copyCache Ljava/util/Map; L0 L1 5
LOCALVARIABLE foreignKeyDirection Lorg/hibernate/type/ForeignKeyDirection; L0 L1 6
MAXSTACK = 7
MAXLOCALS = 7
// access flags 0x1041
public synthetic bridge stringToObject(Ljava/lang/String;)Ljava/lang/Object; throws java/lang/Exception
L0
LINENUMBER 38 L0
ALOAD 0
ALOAD 1
INVOKEVIRTUAL org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.stringToObject (Ljava/lang/String;)Ljava/util/BitSet;
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1041
public synthetic bridge objectToSQLString(Ljava/lang/Object;Lorg/hibernate/dialect/Dialect;)Ljava/lang/String; throws java/lang/Exception
L0
LINENUMBER 38 L0
ALOAD 0
ALOAD 1
CHECKCAST java/util/BitSet
ALOAD 2
INVOKEVIRTUAL org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.objectToSQLString (Ljava/util/BitSet;Lorg/hibernate/dialect/Dialect;)Ljava/lang/String;
ARETURN
L1
LOCALVARIABLE this Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType; L0 L1 0
MAXSTACK = 3
MAXLOCALS = 3
// access flags 0x8
static <clinit>()V
L0
LINENUMBER 42 L0
NEW org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType
DUP
INVOKESPECIAL org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.<init> ()V
PUTSTATIC org/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType.INSTANCE : Lorg/jboss/as/test/compat/jpa/hibernate/transformer/BitSetType;
RETURN
MAXSTACK = 2
MAXLOCALS = 0
}
Note the "$_org_jboss_as_hibernate_Hibernate51CompatibilityTransformer_transformed_$" variable is automatically added by the Hibernate bytecode transformer, to mark a class as being transformed. This is an internal detail that may change over time, if needed (so please don't try to reference it, as your code will break if we change the variable name).
Also note the change from type org/hibernate/engine/spi/SessionImplementor (org.hibernate.engine.spi.SessionImplementor) to org/hibernate/engine/spi/SharedSessionContractImplementor (org.hibernate.engine.spi.SharedSessionContractImplementor), which is one of needed changes that we make in your ORM 5.1 application, to work with ORM 5.3. The '/' character is used in bytecode, instead of '.', which is why the names seem a little weird.
Hopefully, if you run into problems with the transformer, you might find it useful to enable the Hibernate51CompatibilityTransformer.showTransformedClassFolder option, to show exactly what the transformed bytecode looks like. Perhaps you could use some of the information in the debug output, to understand problems that you run into.
If you would like to see what your application class looks like before its transformed, you can use the javap tool, something like "javap -l -v -p -s -constants -sysinfo BitSetType.class > /tmp/BitSetType.asm" might work for you (where the class filename and the output filename is changed to fit your need).
Summary
Eventually, you will need to rewrite your Hibernate 5.1 application to deal with the native API changes in Hibernate 5.3, but for now, please give the Hibernate bytecode transformer a try and ask questions via WildFly forums, as well as report bugs (use 'jpa' category).
Scott
[1] Hibernate ORM 5.2 migration guide
[2] Hibernate ORM 5.3 migration guide
[3] Hibernate ORM 5.1 bytecode transformer
[4] Hibernate bytecode transformer sourcecode
[5] The Hibernate bytecode transformer depends on the ASM project
Comments