Recently, a poster on StackOverflow asked how to design the “perfect” JPA entity.
He had been working with Hibernate for some time, but still found himself struggling with questions as property type & accessibility, immutable properties, and equals/hashCode implementation.
So here are some answers.
I’ll try to answer several key points: this is from long Hibernate/ persistence experience including several major applications.
Entity Class: should it implement Serializable?
Keys needs to implement Serializable. Stuff that’s going to go in the HttpSession, or be sent over the wire by RPC/Java EE, needs to implement Serializable. Other stuff: not so much. Spend your time on what’s important.
Constructors: create a constructor with all required fields of the entity?
Constructor(s) for application logic, should have only a few critical “foreign key” or “type/kind” fields which will always be known when creating the entity. The rest should be set by calling the setter methods — that’s what they’re for.
Avoid putting too many fields into constructors. Constructors should be convenient, and give basic sanity to the object. Name, Type and/or Parents are all typically useful.
OTOH if application rules (today) require a Customer to have an Address, leave that to a setter. That is an example of a “weak rule”. Maybe next week, you want to create a Customer object before going to the Enter Details screen? Don’t trip yourself up, leave possibility for unknown, incomplete or “partially entered” data.
Constructors: also have a package-private default constructor?
Yes, but use ‘protected’ rather than package private. Subclassing stuff is a real pain when the necessary internals are not visible.
Use ‘property’ field access for Hibernate, and from outside the instance. Within the instance, use the fields directly. Reason: allows standard reflection, the simplest & most basic method for Hibernate to work.
As for fields ‘immutable’ to the application — Hibernate still needs to be able to load these. You could try making these methods ‘private’, and/or put an annotation on them, to prevent application code making unwanted access.
Note: when writing an equals() function, use getters for values on the ‘other’ instance! Otherwise, you’ll hit uninitialized/ empty fields on proxy instances.
Protected is better for (Hibernate) performance?
How should I implement Equals/HashCode?
This is relevant to working with entities, before they’ve been saved — which is a thorny issue. Hashing/comparing on immutable values? In most business applications, there aren’t any.
A customer can change address, change the name of their business, etc etc — not common, but it happens. Corrections also need to be possible to make, when the data was not entered correctly.
The few things that are normally kept immutable, are Parenting and perhaps Type/Kind — normally the user recreates the record, rather than changing these. But these do not uniquely identify the entity!
So, long and short, the claimed “immutable” data isn’t really. Primary Key/ ID fields are generated for the precise purpose, of providing such guaranteed stability & immutability.
You need to plan & consider your need for comparison & hashing & request-processing work phases when A) working with “changed/ bound data” from the UI if you compare/hash on “infrequently changed fields”, or B) working with “unsaved data”, if you compare/hash on ID.
Equals/HashCode — if a unique Business Key is not available, use a non-transient UUID which is created when the entity is initialized
Yes, this is a good strategy when required. Be aware that UUIDs are not free performance-wise, though — and clustering complicates things.
Equals/HashCode — never refer to related entities
“If related entity (like a parent entity) needs to be part of the Business Key then add a non insertable, non updatable field to store the parent id (with the same name as the ManytoOne JoinColumn) and use this id in the equality check”
Each entity should ideally be fully keyed & identifiable, in and of itself. But in some existing database designs, we sometimes do encounter composite keys.
Rather than requiring equals() & hashCode() to traverse related objects — in the worst case, a tree — we can just map their keys & incorporate these in into equals()/ hashCode().
Are there any other important points you can suggest? Add your comment now.
- StackOverflow: Create the perfect JPA entity?