创建完美的 JPA 实体 [已关闭]
我已经使用JPA(实现Hibernate)一段时间了,每次我需要创建实体时,我发现自己都在为AccessType,不可变属性,equals/hashCode等问题而苦苦挣扎。
因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。
然而,我不介意任何人对此发表评论或告诉我我错在哪里。
实体类
-
实现可序列化
原因:规范说你必须这样做,但一些JPA提供者不强制执行。作为JPA提供者的Hibernate不会强制执行这一点,但是如果Sscribializable尚未实现,它可能会在ClassCastException的某个地方失败。
构造 函数
-
创建一个包含实体所有必填字段的构造函数
原因:构造函数应始终使创建的实例保持正常状态。
-
除了这个构造函数:有一个包私有默认构造函数
原因:需要默认构造函数才能让休眠初始化实体;允许 private,但包私有(或公共)可见性对于运行时代理生成和高效数据检索(无需字节码检测)是必需的。
字段/属性
-
通常使用字段访问,并在需要时使用属性访问
原因:这可能是最有争议的问题,因为没有明确和令人信服的论据(财产访问与现场访问);但是,字段访问似乎是普遍的最爱,因为代码更清晰,封装更好,并且不需要为不可变字段创建 setter
省略不可变字段的 setter(访问类型字段不需要)
- 属性可能是私有
原因:我曾经听说过受保护更适合(休眠)性能,但我在网络上能找到的只是:休眠可以直接访问公共,私有和受保护的访问器方法,以及公共,私有和受保护的字段。选择取决于您,您可以匹配它以适合您的应用程序设计。
等于/哈希代码
- 如果仅在持久化实体时设置了此 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