Hibernate Bytecode Enhancement. Association management

May 14, 2019by Nicolae S.

  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:

[code language=”xml” firstline=”0″]
<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>

[/code]

  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.

[code language=”java” firstline=”0″ title=”setUserGroup(UserGroup userGroup) before enhancement:”]
public void setUserGroup(UserGroup userGroup) {
this.userGroup = userGroup;
}

[/code]

[code language=”text” firstline=”0″ title=”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

[/code]

[code language=”java” firstline=”0″ title=”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);
}
}

[/code]

[code language=”text” firstline=”0″ title=”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

[/code]

  As can be seen from the bytecode above, after the enhancement, 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:

[code language=”java” firstline=”0″ title=”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

[/code]

[code language=”java” firstline=”0″ title=”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

[/code]

  Using the following test code:

[code language=”java” firstline=”0″]
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;
}

[/code]

  We have the following output:

[code language=”text” firstline=”0″ title=”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=[]}

[/code]

  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:

[code language=”java” firstline=”0″]
// 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);

[/code]

[code language=”text” firstline=”0″ title=”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}]}

[/code]

  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&build of the project, you will notice some log lines written by the Hibernate’s plugin:

[code language=”text” firstline=”0″ title=”Part of project build log”]
— hibernate-enhance-maven-plugin:5.2.9.Final:enhance (default) @ core —
Starting Hibernate enhancement for classes on D:projectsDEMOcoretargetclasses

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:projectsDEMOcoretargetclassescomhibernatebytecodeenhancementUserGroup.class]

[/code]

  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%. However, the important thing that we should focus on, is that we do not longer need to bother assigning objects to EACH side of a bidirectional relationship.

Upscale Your

Business TODAY
Connect with us
Bulgara Street 33/1, Chisinau MD-2001, Moldova
+ 373 22 996 170
info@isd-soft.com
De Amfoor 15, 5807 GW Venray-Oostrum, The Netherlands
+31 6 212 94 116

Subscribe to our newsletter today to receive updates on the latest news, releases and special offers.

Copyright ©2024, ISD. All rights reserved | Cookies Policy | Privacy Policy

De Nederlandse pagina’s zijn vertaald met behulp van een AI-applicatie.