{{announcement.body}}
{{announcement.title}}

Hibernate Bytecode Enhancement: Association Management

DZone 's Guide to

Hibernate Bytecode Enhancement: Association Management

Let's see how to enable the bytecode enhancement.

· Database Zone ·
Free Resource

In the previous article, Hibernate Bytecode Enhancement. Dirty Tracking, I explained how to optimize Hibernate’s Dirty Tracking mechanism.

The bytecode enhancement, however, can be achieved via one more property: association management. When this feature is enabled, Hibernate will take care of automatically updating the “other side” of a bidirectional relation with a reverse mapping defined when one side changes. Similar to Dirty Tracking, this will as well result in additional changes made to the bytecode of the entities.

Let’s see how can this be done.

How to Enable the Bytecode Enhancement: Association Management

To enhance all @Entity classes, you need to add the following Maven plugin:

<plugin>
    <groupId>org.hibernate.orm.tooling</groupId>
    <artifactId>hibernate-enhance-maven-plugin</artifactId>
    <version>${hibernate.version}</version>
    <executions>
        <execution>
            <configuration>
                <enableAssociationManagement>true</enableAssociationManagement>
            </configuration>
            <goals>
                <goal>enhance</goal>
            </goals>
        </execution>
    </executions>
</plugin>

As soon as the Java classes are compiled, the plugin goes through all @Entity classes and modifies their bytecode representation according to the provided configuration.

Notice the property specified within the <configuration> tags. With this setup, Hibernate will manipulate the bytecode of the classes to add instructions that would invoke the setter of the “other side” of a bidirectional relation.

Changes Made to the Bytecode

When the association management is enabled, the bytecode of the classes changes. For instance, before enabling the bytecode enhancement, a class with two reverse mapping fields had 9.10 KB, and after enabling it, the compiled class size is 12.5 KB. The same thing happened to the targeted entity; before, it had 9.66 KB, and after the enhancement, its size grew to 13.5 KB.

If we would inspect the bytecode of the classes, we’ll see that inside each setter method of the fields, which are part of a bidirectional relationship, Hibernate inserted some code — a call to the setter method of the “other side.” Besides the changes made to the setters, after the enhancement, the class implements one more interface: ManagedEntity.

setUserGroup(UserGroup userGroup) before enhancement:

public void setUserGroup(UserGroup userGroup) {
    this.userGroup = userGroup;
}

Here's how the bytecode of this method looks before the enhancement:

public void setUserGroup(com.hibernate.bytecode.enhancement.UserGroup);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast   #76   // class com/hibernate/bytecode/enhancement/UserGroup
       5: putfield    #72   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
       8: return

setUserGroup(UserGroup userGroup) after enhancement, these methods are generated by Hibernate:

public void setUserGroup(UserGroup userGroup) {
    $$_hibernate_write_userGroup(userGroup);
}

public void $$_hibernate_write_userGroup(UserGroup paramUserGroup) {
    if (this.userGroup != null && Hibernate.isPropertyInitialized(this.userGroup, "users")) {
        Set set = ((UserGroup) this.userGroup).$$_hibernate_read_users();
        if (set != null)
            set.remove(this);
    }

    User user = this;
    UserGroup userGroup1 = paramUserGroup;
    user.userGroup = userGroup1;

    if (paramUserGroup != null && Hibernate.isPropertyInitialized(paramUserGroup, "users")) {
        Set set = ((UserGroup) paramUserGroup).$$_hibernate_read_users();
        if (set != null && !set.contains(this))
            set.add(this);
    }
}

And now the bytecode looks as follows:

public void setUserGroup(com.hibernate.bytecode.enhancement.UserGroup);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #96    // class com/hibernate/bytecode/enhancement/UserGroup
       5: invokevirtual #100   // Method $$_hibernate_write_userGroup:(Lcom/hibernate/bytecode/enhancement/UserGroup;)V
       8: return

public void $$_hibernate_write_userGroup(com.hibernate.bytecode.enhancement.UserGroup);
    Code:
       0: aload_0
       1: getfield      #298   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
       4: aconst_null
       5: if_acmpeq     21
       8: aload_0
       9: getfield      #298   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
      12: ldc_w         #302   // String users
      15: invokestatic  #308   // Method org/hibernate/Hibernate.isPropertyInitialized:(Ljava/lang/Object;Ljava/lang/String;)Z
      18: ifne          24
      21: goto          35
      24: aload_0
      25: getfield      #298   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
      28: invokevirtual #312   // Method com/hibernate/bytecode/enhancement/UserGroup.$$_hibernate_read_users:()Ljava/util/Set;
      31: aconst_null
      32: if_acmpne     38
      35: goto          52
      38: aload_0
      39: getfield      #298   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
      42: invokevirtual #312   // Method com/hibernate/bytecode/enhancement/UserGroup.$$_hibernate_read_users:()Ljava/util/Set;
      45: aload_0
      46: invokeinterface #316,  2   // InterfaceMethod java/util/Set.remove:(Ljava/lang/Object;)Z
      51: pop
      52: aload_0
      53: aload_1
      54: putfield      #298   // Field userGroup:Lcom/hibernate/bytecode/enhancement/UserGroup;
      57: goto          60
      60: aconst_null
      61: astore_3
      62: aload_1
      63: aconst_null
      64: if_acmpeq     77
      67: aload_1
      68: ldc_w         #302   // String users
      71: invokestatic  #308   // Method org/hibernate/Hibernate.isPropertyInitialized:(Ljava/lang/Object;Ljava/lang/String;)Z
      74: ifne          80
      77: goto          115
      80: aload_1
      81: invokevirtual #312   // Method com/hibernate/bytecode/enhancement/UserGroup.$$_hibernate_read_users:()Ljava/util/Set;
      84: astore        4
      86: aload         4
      88: aconst_null
      89: if_acmpeq     103
      92: aload         4
      94: aload_0
      95: invokeinterface #321,  2   // InterfaceMethod java/util/Collection.contains:(Ljava/lang/Object;)Z
     100: ifeq          106
     103: goto          115
     106: aload         4
     108: aload_0
     109: invokeinterface #324,  2   // InterfaceMethod java/util/Collection.add:(Ljava/lang/Object;)Z
     114: pop
     115: return

As can be seen from the bytecode above, after the enhancement and inside the setter method, a call to $$_hibernate_write_userGroup(...) has been introduced. If we look into the method generated by Hibernate, we’ll notice that once the UserGroup has been set on the User entity, a call to UserGroup.$$_hibernate_read_users() is expected. It reads the collection from the other side (the collection users from UserGroup.java), followed by some checks, and in the end, a call to Collection.add(...) is performed, which adds the current User to that collection on the other side of the relation.

So, having the Association management enabled, when we do a call to user.setUserGroup(userGroup), on the other side of the relation, via reflection, the method userGroup.getUsers().add(user) is triggered. This way bytecode-enhanced bi-directional association management is achieved.

Test Case

Given User and UserGroup entities with the following class structure:

User.java

@Named
@Entity
@Table(name = "USER")
public class User extends AbstractLogicalIdEntity {

  @ManyToOne(targetEntity = UserGroup.class, optional = false)
  private UserGroup userGroup;

  // rest of the code has been omitted for brevity

UserGroup.java

@Named
@Entity
@Table(name = "USERGROUP")
public class UserGroup extends AbstractLogicalIdEntity {

  @OneToMany(mappedBy = "userGroup", targetEntity = User.class)
  private Set<User> users;

  // rest of the code has been omitted for brevity

Using the following test code:

public static void main(String[] args) {
    setUserOnUserGroup();
    setUserGroupOnUser();
   }

public static User setUserOnUserGroup() {
    User user = new User();
    user.setLogicalId("User 1");

    UserGroup userGroup = new UserGroup();
    userGroup.setLogicalId("UserGroup 1");
    userGroup.setDescription("Some description.");

    Set<User> users = new HashSet<>();
    users.add(user);
    userGroup.setUsers(users);

    System.out.println(“Setting user on userGroup:”);
    System.out.println(user);
    System.out.println(userGroup);

    return user;
  }

  public static UserGroup setUserGroupOnUser() {
    User user = new User();
    user.setLogicalId("User 2");

    UserGroup userGroup = new UserGroup();
    userGroup.setLogicalId("UserGroup 2");
    userGroup.setDescription("Some description.");

    Set<User> users = new HashSet<>();
    userGroup.setUsers(users);

    user.setUserGroup(userGroup);

    System.out.println(“Setting userGroup on user:”);
    System.out.println(user);
    System.out.println(userGroup);

    return userGroup;
  }

We have the following output:

Before applying bytecode enhancement

Setting user on userGroup:
User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 1, userGroup=null}
UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 1, description=Some description., users=[User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 1}]}

Setting userGroup on user:
User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 2, userGroup=UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 2, description=Some description.}}
UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 2, description=Some description., users=[]}

As demonstrated by the execution of the code from above, when the user was added to the collection of users of the userGroup entity, the userGroup field from the user entity was not automatically updated. To achieve that, we would’ve had to execute one more line of code:

// Here we set the users on the userGroup entity
userGroup.setUsers(users);

// Then, for each user we would’ve had to set the userGroup
user.setUserGroup(userGroup);

After applying bytecode enhancement

Setting user on userGroup:
User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 1, userGroup=UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 1, description=Some description.}
UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 1, description=Some description., users=[User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 1}]}

Setting userGroup on user:
User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 2, userGroup=UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 2, description=Some description.}}
UserGroup{id=03b9d0ea-c41e-4c5f-8abb-b629739df82d, logicalId=UserGroup 2, description=Some description., users=[User{id=fa0f98f7-eec9-4b4f-9041-ac60df4a0fd3, logicalId=User 2}]}

From the output above, you can notice that with the Association management set up, the “other side” of a bidirectional association is automatically managed whenever one side is manipulated.

Project Build

During the usual clean and build of the project, you will notice some log lines written by the Hibernate’s plugin:

Part of project build log

--- hibernate-enhance-maven-plugin:5.2.9.Final:enhance (default) @ core ---
Starting Hibernate enhancement for classes on D:\projects\DEMO\core\target\classes

...

Apr 22, 2019 12:31:14 PM org.hibernate.bytecode.enhance.internal.javassist.EnhancerImpl enhance
INFO: Enhancing [com.hibernate.bytecode.enhancement.UserGroup] as Entity
Successfully enhanced class [D:\projects\DEMO\core\target\classes\com\hibernate\bytecode\enhancement\UserGroup.class]

...

Here’s the build time of a Maven module containing 27 Hibernate reverse mappings:

  • Bytecode enhancement disabled: 23.188 s

  • Bytecode enhancement enabled: 30.870 s

The overhead that is added to the compile-time is nearly 25 percent. However, the important thing that we should focus on is that we no longer need to bother assigning objects to EACH side of a bidirectional relationship.

Topics:
hibernate 5 ,hibernate performance tuning ,hibernate tips ,bytecode ,database ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}