Embeddables are simple Java objects. They provide an easy way to define and group a set of attributes that become part of your entity. Developers often use them to create reusable mapping information and handle them using the same piece of business code.
Unfortunately, the JPA specification and Hibernate until version 6.0.0, required your embeddable to have a default constructor. This might be OK if you’re fetching an entity from the database. Hibernate then automatically sets all attributes before providing the object to your business code. But a default constructor is not always a great idea. E.g., if some of the attributes are mandatory and you instantiate embeddables in your business code or frontend. In that case, a constructor that sets all mandatory attributes would be a much better fit.
Since Hibernate 6.0.0, you can easily define how Hibernate instantiates and initializes your embeddable. You can use that, for example, to remove the requirement of a default constructor. I will show you how to do that in this article.
An embeddable is a composition of multiple attributes and their mapping definitions. You can use it as an attribute type in one or more entity classes. When doing that, all attributes of the embeddable become part of the entity object and follow its lifecycle.
Here you can see the definition of Address embeddable. If you want to rely on Hibernate’s default mapping for all its attributes, you only need to annotate the class with an @Embeddable annotation. If you’re not already familiar with this mapping, I recommend checking Hibernate’s documentation or watching the Embeddable lecture of the Advanced Hibernate courses included in the Persistence Hub.
@Embeddable
public class Address {
private String street;
private String city;
private String postalCode;
// getter and setter methods
}
After you define the embeddable, you can use it as an attribute type in your entity classes and use it the same way as any other entity attribute. In this example, all attributes of the Address embeddable become part of the Author entity and get mapped to the author table.
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
private int version;
private String firstName;
private String lastName;
@Embedded
private Address address;
// getter and setter methods
}
By default, Hibernate would call the default constructor of the Address embeddable. After that, it uses reflection to set all its attributes when you fetch an Author entity from the database. Since Hibernate 6, you can customize the instantiation of the embeddable by providing an EmbeddableInstantiator.
Let’s use this to avoid the default constructor of the Address embeddable and use a constructor that sets all attributes instead. This requires 2 minor changes to the Address class. I need to add the additional constructor, and I need to register my EmbeddableInstantiator. You can do that by annotating the embeddable class or an entity attribute of your embeddable’s type with @EmbeddableInstantiator.
@Embeddable
@EmbeddableInstantiator(AddressInstantiator.class)
public class Address {
private String street;
private String city;
private String postalCode;
public Address(String street, String city, String postalCode) {
this.street = street;
this.city = city;
this.postalCode = postalCode;
}
// getter methods
}
In the next step, you have to implement the EmbeddableInstantiator interface. This isn’t complicated. The interface only defines 3 methods. One method checks if an object is an instance of the handled embeddable class. Another one checks if an object is of the same class as the embeddable. And the last method instantiates the embeddable object.
Here you can see the AddressInstantiator class I referenced in the @EmbeddableInstantiator annotation in the previous code snippet.
public class AddressInstantiator implements EmbeddableInstantiator {
Logger log = LogManager.getLogger(this.getClass().getName());
public boolean isInstance(Object object, SessionFactoryImplementor sessionFactory) {
return object instanceof Address;
}
public boolean isSameClass(Object object, SessionFactoryImplementor sessionFactory) {
return object.getClass().equals( Address.class );
}
public Object instantiate(Supplier<Object[]> valuesAccess, SessionFactoryImplementor sessionFactory) {
final Object[] values = valuesAccess.get();
// valuesAccess contains attribute values in alphabetical order
final String city = (String) values[0];
final String postalCode = (String) values[1];
final String street = (String) values[2];
log.info("Instantiate Address embeddable for "+street+" "+postalCode+" "+city);
return new Address( street, city, postalCode );
}
}
As you can see in the code snippet, the instantiate method contains the code to instantiate and initialize the embeddable. The implementation of this method is, of course, application-specific.
But there is one thing I need to point out. The method parameter Supplier<Object[]> valuesAccess contains the attribute values selected from the database in the alphabetical order of their attribute names. In the code snippet, you can see that I get the 3 values from the Supplier. I assign them to named variables and cast each of them to String. This mapping might cause maintainability issues in the future but it at least makes the code better understandable. If you have a better idea for this, I would love to read about it in the comments.
After you have defined and registered the EmbeddableInstantiator, you can use your embeddable in the same way as any other embeddable.
You can use it as an attribute type on one of your entity classes and annotate the attribute with @Embedded.
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
private int version;
private String firstName;
private String lastName;
@Embedded
private Address address;
...
}
After that, you can use the attribute like any other attribute in your business code.
Author a = new Author();
a.setFirstName("firstName");
a.setLastName("lastName");
Address home = new Address("homeStreet", "homeCity", "12345");
a.setAddress(home);
em.persist(a);
When I execute this code, you can see in the log output that Hibernate mapped all attributes of the Address embeddable to the Author table and used the AddressInstantiator to instantiate the Address object.
As you saw in this article, the EmbeddableInstantiator contract introduced in Hibernate 6 gives you more flexibility when working with embeddables. It gives you full control over the instantiation and initialization of your embeddable objects. You can use this for various things. You could perform additional business logic or transform or calculate attributes values before instantiating the embeddable. Or you can avoid the default constructor and call a constructor that fully initializes your embeddable object instead.