Persisting complex Embeddable/Embedded objects in Spring/Hibernate

By: (plus.google.com) +David Herron; 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) https://en.wikibooks.org/wiki/Java_Persistence/Embeddables#Nesting

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) http://www.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.

« Amazon's amazing warehouse robots automate assembling your purchases for shipping Google's AMP technology makes spear-phishing sites look legit »
2016 Election Acer C720 Ad block AkashaCMS Amazon Amazon Kindle Amiga Android Anti-Fascism AntiVirus Software Apple Apple Hardware History Apple iPhone Apple iPhone Hardware April 1st Arduino ARM Compilation Astronomy Asynchronous Programming Authoritarianism Automated Social Posting Ayo.JS Bells Law Big Brother Big Finish Bitcoin Mining Black Holes Blade Runner Blockchain Blogger Blogging Books Botnet Botnets Cassette Tapes Cellphones Christopher Eccleston Chrome Chrome Apps Chromebook Chromebooks Chromebox ChromeOS CIA CitiCards Citizen Journalism Civil Liberties Clinton Cluster Computing Command Line Tools Comment Systems Computer Hardware Computer Repair Computers Cross Compilation Crouton Cryptocurrency Curiosity Rover Cyber Security Cybermen Daleks Darth Vader Data backup Data Storage Database Database Backup Databases David Tenant DDoS Botnet Detect Adblocker Developers Editors Digital Photography Diskless Booting Disqus DIY DIY Repair DNP3 Do it yourself Docker Docker MAMP Docker Swarm Doctor Who Doctor Who Paradox Drobo Drupal Drupal Themes DVD E-Books E-Readers Early Computers Election Hacks Electric Bicycles Electric Vehicles Electron Emdebian Encabulators Energy Efficiency Enterprise Node EPUB ESP8266 Ethical Curation Eurovision Event Driven Asynchronous Express Facebook Fake News Fedora VirtualBox File transfer without iTunes FireFly Fraud Freedom of Speech Gallifrey git Gitlab GMAIL Google Google Chrome Google Gnome Google+ Government Spying Great Britain Heat Loss Hibernate Hoax Science Home Automation HTTPS Human ID I2C Protocol Image Analysis Image Conversion Image Processing ImageMagick In-memory Computing InfluxDB Infrared Thermometers Insulation Internet Internet Advertising Internet Law Internet of Things Internet Policy Internet Privacy iOS Devices iPad iPhone iPhone hacking Iron Man Iternet of Things iTunes Java JavaScript JavaScript Injection JDBC John Simms Journalism Joyent Kaspersky Labs Kindle Kindle Marketplace Lets Encrypt LibreOffice Linux Linux Hints Linux Single Board Computers Logging Mac OS Mac OS X Machine Readable ID macOS MacOS X setup Make Money Online MariaDB Mars Matt Lucas MEADS Anti-Missile Mercurial Michele Gomez Micro Apartments Military Hardware Minification Minimized CSS Minimized HTML Minimized JavaScript Missy Mobile Applications MODBUS Mondas MongoDB Mongoose Monty Python MQTT Music Player Music Streaming MySQL NanoPi Nardole NASA Net Neutrality Node Web Development Node.js Node.js Database Node.js Testing Node.JS Web Development Node.x North Korea npm NY Times Online advertising Online Community Online Fraud Online Journalism Online Video Open Media Vault Open Source Governance Open Source Licenses Open Source Software OpenAPI OpenVPN Paywalls Personal Flight Peter Capaldi Photography PHP Plex Plex Media Server Political Protest Postal Service Power Control Privacy Production use Public Violence Raspberry Pi Raspberry Pi 3 Raspberry Pi Zero Recycling Refurbished Computers Remote Desktop Republicans Retro-Technology Reviews Right to Repair River Song Robotics Rocket Ships RSS News Readers rsync Russia Russia Troll Factory Russian Hacking SCADA Scheme Science Fiction Search Engine Ranking Season 1 Season 10 Season 11 Security Security Cameras Server-side JavaScript Shell Scripts Silence Simsimi Skype Social Media Social Media Warfare Social Networks Software Development Space Flight Space Ship Reuse Space Ships SpaceX Spear Phishing Spring Spring Boot SQLite3 SSD Drives SSD upgrade SSH SSH Key SSL Swagger Synchronizing Files Telescopes Terrorism The Cybermen The Daleks The Master Time-Series Database Torchwood Total Information Awareness Trump Trump Administration Trump Campaign Ubuntu UDOO Virtual Private Networks VirtualBox VLC VNC VOIP Web Applications Web Developer Resources Web Development Web Development Tools Web Marketing Website Advertising Weeping Angels WhatsApp Window Insulation Wordpress YouTube YouTube Monetization