Java 创建完美的JPA实体
我使用JPA(实现Hibernate)已经有一段时间了,每次我需要创建实体时,我都会发现自己在解决诸如AccessType、不可变属性、equals/hashCode等问题Java 创建完美的JPA实体,java,hibernate,jpa,equals,Java,Hibernate,Jpa,Equals,我使用JPA(实现Hibernate)已经有一段时间了,每次我需要创建实体时,我都会发现自己在解决诸如AccessType、不可变属性、equals/hashCode等问题 因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。 不过,我不介意任何人对此发表评论或告诉我哪里错了 实体类 实现可序列化 原因:规范说您必须这样做,但一些JPA提供商没有强制执行这一点。Hibernate作为JPA提供者并不强制执行这一点,但如果Serializable没有实现,它可能会在ClassCa
因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。
不过,我不介意任何人对此发表评论或告诉我哪里错了 实体类
- 实现可序列化 原因:规范说您必须这样做,但一些JPA提供商没有强制执行这一点。Hibernate作为JPA提供者并不强制执行这一点,但如果Serializable没有实现,它可能会在ClassCastException的肚子深处的某个地方失败
- 使用实体的所有必填字段创建构造函数 原因:构造函数应该始终保持创建的实例处于正常状态
- 除了这个构造函数之外:还有一个包私有的默认构造函数 原因:默认构造函数需要Hibernate初始化实体;允许私有,但运行时代理生成和高效数据检索需要包私有(或公共)可见性,而无需字节码插装
- 通常使用字段访问,必要时使用属性访问 原因:这可能是最有争议的问题,因为没有明确和令人信服的论据支持其中一个(财产访问与现场访问);然而,字段访问似乎是最受欢迎的,因为代码更清晰,封装更好,并且不需要为不可变字段创建setter
- 省略不可变字段的设置器(访问类型字段不需要)
- 属性可能是私有的
原因:我曾经听说受保护的(Hibernate)性能更好,但我在web上只能找到:Hibernate可以直接访问public、private和protected访问器方法,以及public、private和protected字段。选择取决于您,您可以匹配它以适合您的应用程序设计
- 如果仅在持久化实体时才设置此id,则切勿使用生成的id
- 按偏好:使用不可变值来形成唯一的业务键,并使用它来测试相等性
- 如果唯一的业务密钥不可用,则使用在实体初始化时创建的非瞬态UUID;有关更多信息,请参阅
- 从不提及相关实体(多个实体);如果此实体(如父实体)需要是业务密钥的一部分,则只比较ID。在代理上调用getId()不会触发实体的加载,只要您正在使用
- 如果一个不可变的简单业务密钥可用:使用它
- 在所有其他情况下:使用uuid
- 实体类必须具有无参数构造函数。它也可能有其他构造函数。无参数构造函数必须是公共的或受保护的
- 实体类必须是顶级类。枚举或接口不能为空 被指定为一个实体
- 实体类不能是final。实体类的任何方法或持久实例变量都不能是最终的
- 如果实体实例要作为分离对象按值传递(例如,通过远程接口),则实体类必须实现可序列化接口
- 抽象类和具体类都可以是实体。实体可以扩展非实体类和实体类,非实体类可以扩展实体类
该规范不包含实体的equals和hashCode方法的实现要求,据我所知,仅适用于主键类和映射键。我对以下答案的补充是:
我将尝试回答几个关键问题:这来自于长期的Hibernate/持久性体验,包括几个主要的应用程序 实体类:实现可序列化? 密钥需要实现可序列化。将进入HttpSession的内容,或通过RPC/JavaEE通过线路发送的内容,
@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
}
public interface Entity<I> extends Serializable {
/**
* @return entity identity
*/
I getId();
/**
* @return HashCode of entity identity
*/
int identityHashCode();
/**
* @param other
* Other entity
* @return true if identities of entities are equal
*/
boolean identityEquals(Entity<?> other);
}
public abstract class AbstractEntity<I> implements Entity<I> {
@Override
public final boolean identityEquals(Entity<?> other) {
if (getId() == null) {
return false;
}
return getId().equals(other.getId());
}
@Override
public final int identityHashCode() {
return new HashCodeBuilder().append(this.getId()).toHashCode();
}
@Override
public final int hashCode() {
return identityHashCode();
}
@Override
public final boolean equals(final Object o) {
if (this == o) {
return true;
}
if ((o == null) || (getClass() != o.getClass())) {
return false;
}
return identityEquals((Entity<?>) o);
}
@Override
public String toString() {
return getClass().getSimpleName() + ": " + identity();
// OR
// return ReflectionToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}
@Entity
@Table(name = "ROOM")
public class Room extends AbstractEntity<Integer> {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@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;
}
public Integer getId(){
return id;
}
public Building getBuilding() {
return building;
}
public String getNumber() {
return number;
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}