我已经使用JPA(实现Hibernate)一段时间了,每次我需要创建实体时,我发现自己在AccessType、不可变属性、等于/hashCode、... .等问题上苦苦挣扎 所以我决定试着找出每个问题的一般最佳实践,并把它写下来供个人使用。 我不介意任何人对此发表评论,或者告诉我哪里错了。
实体类
实现序列化 原因:规范要求您必须这样做,但是一些JPA提供者并没有强制执行这一点。Hibernate作为JPA提供者并没有强制执行这一点,但是如果没有实现Serializable,它可能会在ClassCastException中失败。
构造函数
创建一个包含实体所有必需字段的构造函数 原因:构造函数应该始终让创建的实例处于正常状态。 除了这个构造函数:有一个包的私有默认构造函数 原因:默认构造函数需要Hibernate初始化实体;允许私有,但是需要包私有(或公共)可见性来生成运行时代理和高效的数据检索,而不需要字节码插装。
字段/属性
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) properties may be private 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.
= / hashCode
Never use a generated id if this id is only set when persisting the entity By preference: use immutable values to form a unique Business Key and use this to test equality if a unique Business Key is not available use a non-transient UUID which is created when the entity is initialised; See this great article for more information. never refer to related entities (ManyToOne); if this entity (like a parent entity) needs to be part of the Business Key then compare the ID's only. Calling getId() on a proxy will not trigger the loading of the entity, as long as you're using property access type.
实体例子
@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
}
欢迎其他建议加入到这个列表中……
更新
读了这篇文章后,我调整了eq/hC的实现方式:
如果一个不可变的简单业务密钥可用:使用它 在所有其他情况下:使用uuid