Persisting complex Embeddable/Embedded objects in Spring/Hibernate

; Date: October 8, 2017

Tags: Spring Boot »»»» Hibernate

Hibernate and Spring and the Java Persistence packages make for a powerful combination. You can define a complex tree of objects, and the system automagically creates the database schema, and automagically saves and retrieves that object tree from the database. It means you can innocently code away, until one day you try to include an Embedded object but it silently fails to be saved to the database, Hibernate silently fails to create database columns for that object, and when retrieving the object for which the Embedded object is a field, the object is null. No error messages are printed in the log. Instead you're left wondering, why didn't Hibernate persist this object? What's presented here is a solution using EntityListener methods to convert values in and out of shadow values stored in the Entity.

Why use @Embedded

When defining a Model class one of the questions to ask is whether that class should be stored in its own database table. To put it into Hibernate terms, an @Entity is an object that defines its own "lifecycle" which practically means the Entity must have an @Id field, and a corresponding database table. Hibernate defines two general types of objects, the Entity type being what we just described.

The Value type is for simpler objects, that do not define their own lifecycle. Practically speaking that means no @Id field and no corresponding database table. In other words, a Value type object is something that shouldn't stand on its own, and will always be contained within other objects. An Entity type object does stand on its own, and instead of being contained within another object, it is referred-to by other objects.

The Basic type objects, Double, String, etc, are obviously Value types. But, often we need to use a slightly more complex object the same way. For example:

public class Person {
    private Name name;
    private Address homeAddress;
    private Address vacationHome;
    private TelephoneNumber homePhone;
    private TelephoneNumber mobilePhone;
    private TelephoneNumber workPhone;
}

That hypothetical example demonstrates some higher-level objects that could be used in a system. We don't need to go into the definitions of Name, Address or TelephoneNumber. The primary observation is that these classes should be contained within another class, and shouldn't stand on their own.

Each would be defined in Hibernate as:

@Embeddable public class Xyzzy { ... fields }

For example:

@Embeddable
public class Name {

    private String firstName;

    private String middleName;

    private String lastName;

    ...
}

And then the Person class would be defined as:

public class Person {
    @Embedded
    private Name name;

    @Embedded
    private Address homeAddress;

    @Embedded
    private Address vacationHome;

    @Embedded
    private TelephoneNumber homePhone;

    @Embedded
    private TelephoneNumber mobilePhone;

    @Embedded
    private TelephoneNumber workPhone;
}

Those two steps inform Hibernate what to do to persist those fields in the Person object.

Complex @Embedded objects

The slippery slope I described at the top is that adding Embedded fields to an Entity class feels so easy. You easily define the Embeddable object, add it to the Entity class with @Embedded, and there's no fuss about defining a database table nor anything else. Hibernate takes care of it all for you.

The problem arises when your Embeddable classes become complex. Hibernate can deal with simple Embeddable classes, but fails with complex ones.

Let's take the TelephoneNumber object used in our Person class. What if you decide it should be implemented as so:

@Embeddable
public class TelephoneNumber {

    @Embedded
    private CountryCode countryCode;

    private String phoneNumber;
}

The CountryCode object could be backed up with a list of legal country codes. That way your system could have a small measure of protection against users entering badly formatted telephone numbers.

This is called Nesting and according to the Java Persistence Wikibook, Nesting was not allowed in Java Persistence 1.0, but is allowed in JPA 2.0: (en.wikibooks.org) wikibooks.org Java_Persistence Embeddables

The next section down on that page covers Inheritance, and says that JPA does not support Embeddable classes inherited from other objects. That is:

@Embeddable
public class MobileTelephoneNumber extends TelephoneNumber {
    ...
}

Is not supported by JPA.

The JavaDoc for Java Persistence is silent on both of these issues: (javaee.github.io) https://javaee.github.io/javaee-spec/javadocs/

Concrete example

In my case, the guy defining the UML I'm implementing defined a Quantity type, with fields uncertainty and value (both Float's), and a type field denoting the type of the quantity. That type field is its own object class, XBRLItemType, whose purpose is storing the suffix to be put on a number.

For example a Velocity class was defined as a Quantity subclass. Its type field used a class named velocityItemType that was in turn a subclass of XBRLItemType. That type field contained strings like miles/hr so that the program can print 185 miles/hr.

Both the Velocity and velocityItemType classes are subclassed from their respective parent classes. And, the arrangement is a Nested class. In other words, the Velocity object, as defined by the UML guys, presents two levels of difficulty in using it with Hibernate.

@Embeddable
public class Velocity extends Quantity {

    @Embedded
    private velocityItemType itemUnit = new velocityItemType();
}

The system I'm building is a REST API to manage a database full of information.

Creating an object using the Velocity class might involve a POST operation with a JSON payload including this snippet:

{
    ...
    "velocity": {
        "uncertainty": 20,
        "value: 185,
        "itemUnit": {
            "symbol": "miles/hr"
        }
    }
    ...
}

What happened is I could see the object received in the Controller method had the Velocity object properly instantiated. But when following that up with a GET operation to retrieve the object, the received JSON had this:

{
    "velocity": null
}

Hurm... the data is there, but Hibernate didn't save it.

Turning on Hibernate SQL debugging

The first step to seeing what's off is to turn on Hibernate SQL debugging.

In the application.properties make these settings:

hibernate.show_sql=true
logging.level.org.hibernate.SQL=debug

This makes voluminous output in the Spring console log. Carefully read the output and you'll find a section where database tables are created, and another section where the data is written to the database.

What I found in my case is that the generated database table did not contain columns for the complex Embedded objects I'd used. There were database columns for every other field I'd defined, just not for the complex Embedded objects.

That's when I found the constraints I listed above.

EntityListeners, PrePersist and PostLoad, to the rescue

After some research and reflecting upon the JPA annotations, I found an answer using advice in this post: (www.concretepage.com) concretepage.com java jpa jpa-entitylisteners-example-with-callbacks-prepersist-postpersist-postload-preupdate-postupdate-preremove-postremove

Suppose we have an Entity to record Hurricanes:

@Entity
public class Hurricane extends Storm {

    ...

    @Embedded
    private Velocity maximumWindSpeed;

    ...
}

What's necessary is to add a couple fields to store the values contained within the Velocity class, and then implement some code to marshall values in and out of the object.

@Entity
@EntityListeners(HurricaneEntityListener.class)
public class Hurricane extends Storm {

    ...

    @JsonProperty("maximumWindSpeed")
    @Transient
    private Velocity maximumWindSpeed;

    @JsonIgnore
    private Float hurricaneMaximumWindSpeedUncertainty;
    @JsonIgnore
    private Float hurricaneMaximumWindSpeedValue;

    ...
}

The @EntityListeners annotation references a class, that we'll implement in a second, containing methods that are triggered in certain Entity lifecycle events.

Switching the maximumWindSpeed field to @Transient tells Hibernate to not persist that field. We're instead going to persist the data values manually using the hurricaneMaximumWindSpeedUncertainty and hurricaneMaximumWindSpeedValue fields, and the code in HurricaneEntityListener.

The @JsonProperty attribute is from Jackson, and tells Jackson to serialize/deserialize that field into the JSON. By contrast the two fields with @JsonIgnore do not get serialized/deserialized by Jackson into the JSON. We want those fields for internal use only, and the values in those fields aren't to be exposed in the JSON. Hibernate will go ahead and allocate database columns for these fields, and persist the values, but Jackson will not serialize/deserialize those values to/from the JSON.

public class HurricaneEntityListener {

    @PrePersist
    public void hurricanePrePersist(Hurricane hurricane) {
        if (hurricane.getMaximumWindSpeed() != null) {
            Velocity maximumWindSpeed = hurricane.getMaximumWindSpeed();
            hurricane.setMaximumWindSpeedUncertainty(maximumWindSpeed.getUncertainty());
            hurricane.setMaximumWindSpeedValue(maximumWindSpeed.getValue());
        }
    }

    @PostUpdate
    @PostLoad
    public void hurricanePostLoad(Hurricane hurricane) {
        if (hurricane.getMaximumWindSpeedUncertainty() != null 
            || hurricane.getMaximumWindSpeedValue() != null) {
            Velocity maximumWindSpeed = new Velocity();
            maximumWindSpeed.setUncertainty(hurricane.getMaximumWindSpeedUncertainty());
            maximumWindSpeed.setValue(hurricane.getMaximumWindSpeedValue());
            maximumWindSpeed.setItemUnit(new velocityItemType());
            hurricane.setMaximumWindSpeed(maximumWindSpeed);
        }
    }   
}

Generally speaking the methods in an EntityListener class are invoked at certain moments in the lifecycle of an Entity. The methods here manages the existence and value of the maximumWindSpeed field by copying data in and out of the shadow fields.

The @PrePersist method is called just before the Entity is to be persisted to the database. We then copy values from the transient object into the shadow objects, so the data can be persisted.

The @PostUpdate and @PostLoad methods are called after modifications are made to the Entity. We use that moment to instantiate an instance of the transient object using the values stored in the shadow objects.

When declared this way these methods will automatically trigger at the corresponding points in the lifecycle of the objects.