我已经使用 JPA(实现 Hibernate)有一段时间了,每次我需要创建实体时,我都会发现自己在为 AccessType、不可变属性、equals/hashCode 等问题而苦苦挣扎。
因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。
但是,我不介意任何人对此发表评论或告诉我我错在哪里。
实体类
- 实现可序列化
原因: 规范说你必须这样做,但是一些 JPA 提供者并没有强制执行。作为 JPA 提供者的 Hibernate 不会强制执行此操作,但如果尚未实现 Serializable,它可能会因 ClassCastException 而失败。
构造函数
- 使用实体的所有必需字段创建构造函数
原因:构造函数应该始终让创建的实例处于正常状态。
- 除了这个构造函数:有一个包私有默认构造函数
原因:需要默认构造函数让Hibernate初始化实体;允许私有,但运行时代理生成和高效数据检索需要包私有(或公共)可见性,而无需字节码检测。
字段/属性
- 一般使用字段访问权限,并在需要时使用属性访问权限
原因:这可能是最有争议的问题,因为其中一个没有明确和令人信服的论据(财产访问与字段访问);然而,由于更清晰的代码、更好的封装以及无需为不可变字段创建设置器,字段访问似乎是普遍的最爱
省略不可变字段的设置器(访问类型字段不需要)
属性可能是私有的
原因:我曾经听说 protected 对(Hibernate)性能更好,但我在网上只能找到: Hibernate 可以直接访问公共、私有和受保护的访问器方法,以及公共、私有和受保护的字段。选择取决于您,您可以匹配它以适合您的应用程序设计。
等于/哈希码
- 如果仅在持久化实体时设置此 id,则切勿使用生成的 id
- 通过偏好:使用不可变的值来形成唯一的业务密钥并使用它来测试相等性
- 如果唯一的业务密钥不可用,则使用在初始化实体时创建的非瞬态 UUID ;有关更多信息,请参阅 这篇精彩的文章。
- 从不 引用相关实体(ManyToOne);如果该实体(如父实体)需要成为业务密钥的一部分,则仅比较 ID。只要您使用 属性访问类型,在代理上调用 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
}
非常欢迎添加到此列表中的其他建议…
更新
自从阅读 本文 以来,我已经调整了实现 eq/hC 的方式:
- 如果一个不可变的简单业务密钥可用:使用它
- 在所有其他情况下:使用 uuid
原文由 Stijn Geukens 发布,翻译遵循 CC BY-SA 4.0 许可协议
我将尝试回答几个关键点:这是来自长期的 Hibernate/持久性经验,包括几个主要的应用程序。
实体类:实现可序列化?
Keys 需要实现Serializable。将进入 HttpSession 或通过 RPC/Java EE 通过网络发送的东西需要实现 Serializable。其他东西:没那么多。把时间花在重要的事情上。
构造函数:使用实体的所有必需字段创建构造函数?
应用程序逻辑的构造函数应该只有几个关键的“外键”或“类型/种类”字段,这些字段在创建实体时总是已知的。其余的应该通过调用 setter 方法来设置——这就是它们的用途。
避免将太多字段放入构造函数中。构造函数应该是方便的,并且给对象基本的理智。姓名、类型和/或父母通常都很有用。
OTOH 如果应用程序规则(今天)要求客户拥有地址,请将其留给设置者。这是“弱规则”的一个例子。也许下周,您想在进入“输入详细信息”屏幕之前创建一个客户对象?不要绊倒自己,留下未知、不完整或“部分输入”数据的可能性。
构造函数:还有,封装私有默认构造函数?
是的,但使用“受保护”而不是包私有。当必要的内部结构不可见时,子类化是一件非常痛苦的事情。
字段/属性
对 Hibernate 和从实例外部使用“属性”字段访问。在实例中,直接使用字段。原因:允许标准反射(Hibernate 最简单和最基本的方法)工作。
至于对应用程序“不可变”的字段——Hibernate 仍然需要能够加载这些字段。您可以尝试将这些方法设为“私有”,和/或在其上添加注释,以防止应用程序代码进行不必要的访问。
注意:在编写 equals() 函数时,使用 getter 获取“其他”实例上的值!否则,您将在代理实例上遇到未初始化/空字段。
受保护对(休眠)性能更好?
不太可能。
等于/哈希码?
这与在实体被保存之前与实体合作有关——这是一个棘手的问题。对不可变值进行散列/比较?在大多数业务应用程序中,没有。
客户可以更改地址、更改他们的公司名称等等——这并不常见,但它确实发生了。当数据输入不正确时,还需要进行更正。
通常保持不可变的几件事是 Parenting,也许是 Type/Kind - 通常用户重新创建记录,而不是更改这些记录。但是这些并不能唯一地标识实体!
因此,无论长短,声称的“不可变”数据并不是真的。生成主键/ ID 字段的确切目的是提供这种有保证的稳定性和不变性。
当您在“不经常更改的字段”上进行比较/散列时,您需要计划并考虑您对比较、散列和请求处理工作阶段的需求 A)使用 UI 中的“更改/绑定数据”,或 B)使用“未保存的数据”,如果您比较/散列 ID。
Equals/HashCode – 如果唯一的业务密钥不可用,则使用初始化实体时创建的非瞬态 UUID
是的,在需要时这是一个很好的策略。请注意,UUID 不是免费的,但在性能方面 - 并且集群使事情变得复杂。
Equals/HashCode – 从不引用相关实体
“如果相关实体(如父实体)需要成为业务密钥的一部分,则添加一个不可插入、不可更新的字段来存储父 id(与 ManytoOne JoinColumn 同名)并在相等性检查中使用此 id “
听起来不错的建议。
希望这可以帮助!