【发布时间】:2011-08-27 09:28:43
【问题描述】:
我使用 JPA(实现 Hibernate)已经有一段时间了,每次我需要创建实体时,我都会发现自己在为诸如 AccessType、不可变属性、equals/hashCode 等问题而苦苦挣扎。...
因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。
但是,我不介意任何人对此发表评论或告诉我哪里错了。
实体类
-
实现可序列化
原因:规范说你必须这样做,但是一些 JPA 提供者并没有强制执行。 Hibernate 作为 JPA 提供者不会强制执行此操作,但如果尚未实现 Serializable,它可能会因 ClassCastException 而失败。
构造函数
-
使用实体的所有必需字段创建构造函数
原因:构造函数应该始终让创建的实例保持正常状态。
-
除了这个构造函数:还有一个包私有的默认构造函数
原因:需要默认构造函数让 Hibernate 初始化实体;允许私有,但运行时代理生成和高效数据检索需要包私有(或公共)可见性,无需字节码检测。
字段/属性
-
一般使用字段访问权限,并在需要时使用属性访问权限
原因:这可能是最有争议的问题,因为其中一个没有明确和令人信服的论据(财产访问与字段访问);然而,由于更清晰的代码、更好的封装以及无需为不可变字段创建设置器,字段访问似乎受到普遍欢迎
省略不可变字段的设置器(访问类型字段不需要)
- 属性可能是私有的
原因:我曾经听说 protected 对(Hibernate)性能更好,但我在网上只能找到:Hibernate 可以直接访问公共、私有和受保护的访问器方法,以及公共、私有和受保护的字段。选择权由您决定,您可以根据自己的应用设计进行匹配。
等于/hashCode
- 如果此 id 仅在持久化实体时设置,则切勿使用生成的 id
- 根据偏好:使用不可变的值来形成唯一的业务密钥并使用它来测试相等性
- 如果唯一的业务密钥不可用,则使用在初始化实体时创建的非瞬态 UUID;请参阅this great article 了解更多信息。
- 从不引用相关实体 (ManyToOne);如果该实体(如父实体)需要成为业务密钥的一部分,则仅比较 ID。只要您使用的是property access type,在代理上调用 getId() 就不会触发实体的加载。
示例实体
@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
}
非常欢迎添加到此列表中的其他建议...
更新
自从阅读this article 以来,我已经调整了实现 eq/hC 的方式:
- 如果不可变的简单业务密钥可用:使用它
- 在所有其他情况下:使用 uuid
【问题讨论】:
-
这不是一个问题,它是一个审查请求和一个列表请求。此外,它非常开放和模糊,或者换一种说法:JPA 实体是否完美取决于它的用途。我们是否应该列出一个实体在所有可能的实体使用中可能需要的所有东西?
-
我知道这不是一个明确的问题,对此我深表歉意。这实际上不是对列表的请求,而是对 cmets/remarks 的请求,尽管欢迎其他建议。随意详细说明 JPA 实体的可能用途。
-
我还希望字段为
final(根据您省略设置器来判断,我猜您也是)。 -
必须尝试一下,但我认为 final 不会起作用,因为 Hibernate 仍然需要能够设置这些属性的值。
-
notNull来自哪里?