Create the perfect JPA entity
I've been working with JPA (implementation Hibernate) for some time now and each time I need to create entities I find myself struggling with issues as AccessType, immutable properties, equals/hashCode, ... .
So I decided to try and find out the general best practice for each issue and write this down for personal use.
I would not mind however for anyone to comment on it or to tell me where I'm wrong.
Entity Class
implement Serializable
Reason: The specification says you have to, but some JPA providers do not enforce this. Hibernate as JPA provider does not enforce this, but it can fail somewhere deep in its stomach with ClassCastException, if Serializable has not been implemented.
Constructors
create a constructor with all required fields of the entity
Reason: A constructor should always leave the instance created in a sane state.
besides this constructor: have a package private default constructor
Reason: Default constructor is required to have Hibernate initialize the entity; private is allowed but package private (or public) visibility is required for runtime proxy generation and efficient data retrieval without bytecode instrumentation.
Fields/Properties
Use field access in general and property access when needed
Reason: this is probably the most debatable issue since there are no clear and convincing arguments for one or the other (property access vs field access); however, field access seems to be general favourite because of clearer code, better encapsulation and no need to create setters for immutable fields
Omit setters for immutable fields (not required for access type field)
Reason: I once heard that protected is better for (Hibernate) performance but all I can find on the web is: Hibernate can access public, private, and protected accessor methods, as well as public, private and protected fields directly. The choice is up to you and you can match it to fit your application design.
Equals/hashCode
Example Entity
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Other suggestions to add to this list are more than welcome...
UPDATE
Since reading this article I have adapted my way of implementing eq/hC:
The JPA 2.0 Specification states that:
The specification contains no requirements about the implementation of equals and hashCode methods for entities, only for primary key classes and map keys as far as I know.
I'll try to answer several key points: this is from long Hibernate/ persistence experience including several major applications.
Entity Class: 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, 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.
Fields/Properties
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?
Unlikely.
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"
Sounds like good advice.
Hope this helps!
After expressing my admiration to the Stijns semi-comprehensive list, 2 corrections are:
With reference to Field or Property access (away from performance considerations) both are legitimately accessed by means of getters and setters, thus, my model logic can set/get them in the same manner. The difference comes to play when the persistence runtime provider (Hibernate, EclipseLink or else) needs to persist/set some record in Table A which has a foreign key referring to some column in Table B. In case of a Property access type, the persistence runtime system uses my coded setter method to assign the cell in Table B column a new value. In case of a Field access type, the persistence runtime system sets the cell in Table B column directly. This difference is not of importance in the context of a uni-directional relationship, yet it is a MUST to use my own coded setter method (Property access type) for a bi-directional relationship provided the setter method is well designed to account for consistency. Consistency is a critical issue for bi-directional relationships refer to this link for a simple example for a well-designed setter.
With reference to Equals/hashCode: It is impossible to use the Eclipse auto-generated Equals/hashCode methods for entities participating in a bi-directional relationship, otherwise they will have a circular reference resulting in a stackoverflow Exception. Once you try a bidirectional relationship (say OneToOne) and auto-generate Equals() or hashCode() or even toString() you will get caught in this stackoverflow exception.
上一篇: 休眠OneToMany标准返回重复项
下一篇: 创建完美的JPA实体