Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/391.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 创建完美的JPA实体_Java_Hibernate_Jpa_Equals - Fatal编程技术网

Java 创建完美的JPA实体

Java 创建完美的JPA实体,java,hibernate,jpa,equals,Java,Hibernate,Jpa,Equals,我使用JPA(实现Hibernate)已经有一段时间了,每次我需要创建实体时,我都会发现自己在解决诸如AccessType、不可变属性、equals/hashCode等问题 因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。 不过,我不介意任何人对此发表评论或告诉我哪里错了 实体类 实现可序列化 原因:规范说您必须这样做,但一些JPA提供商没有强制执行这一点。Hibernate作为JPA提供者并不强制执行这一点,但如果Serializable没有实现,它可能会在ClassCa

我使用JPA(实现Hibernate)已经有一段时间了,每次我需要创建实体时,我都会发现自己在解决诸如AccessType、不可变属性、equals/hashCode等问题
因此,我决定尝试找出每个问题的一般最佳实践,并将其写下来供个人使用。
不过,我不介意任何人对此发表评论或告诉我哪里错了

实体类
  • 实现可序列化

    原因:规范说您必须这样做,但一些JPA提供商没有强制执行这一点。Hibernate作为JPA提供者并不强制执行这一点,但如果Serializable没有实现,它可能会在ClassCastException的肚子深处的某个地方失败

建设者
  • 使用实体的所有必填字段创建构造函数

    原因:构造函数应该始终保持创建的实例处于正常状态

  • 除了这个构造函数之外:还有一个包私有的默认构造函数

    原因:默认构造函数需要Hibernate初始化实体;允许私有,但运行时代理生成和高效数据检索需要包私有(或公共)可见性,而无需字节码插装

字段/属性
  • 通常使用字段访问,必要时使用属性访问

    原因:这可能是最有争议的问题,因为没有明确和令人信服的论据支持其中一个(财产访问与现场访问);然而,字段访问似乎是最受欢迎的,因为代码更清晰,封装更好,并且不需要为不可变字段创建setter

  • 省略不可变字段的设置器(访问类型字段不需要)

  • 属性可能是私有的
    原因:我曾经听说受保护的(Hibernate)性能更好,但我在web上只能找到:Hibernate可以直接访问public、private和protected访问器方法,以及public、private和protected字段。选择取决于您,您可以匹配它以适合您的应用程序设计
等于/哈希代码
  • 如果仅在持久化实体时才设置此id,则切勿使用生成的id
  • 按偏好:使用不可变值来形成唯一的业务键,并使用它来测试相等性
  • 如果唯一的业务密钥不可用,则使用在实体初始化时创建的非瞬态UUID;有关更多信息,请参阅
  • 从不提及相关实体(多个实体);如果此实体(如父实体)需要是业务密钥的一部分,则只比较ID。在代理上调用getId()不会触发实体的加载,只要您正在使用
示例实体 其他建议添加到此列表中是非常受欢迎的

更新

阅读后,我调整了实施eq/hC的方式:

  • 如果一个不可变的简单业务密钥可用:使用它
  • 在所有其他情况下:使用uuid
声明:

  • 实体类必须具有无参数构造函数。它也可能有其他构造函数。无参数构造函数必须是公共的或受保护的
  • 实体类必须是顶级类。枚举或接口不能为空 被指定为一个实体
  • 实体类不能是final。实体类的任何方法或持久实例变量都不能是最终的
  • 如果实体实例要作为分离对象按值传递(例如,通过远程接口),则实体类必须实现可序列化接口
  • 抽象类和具体类都可以是实体。实体可以扩展非实体类和实体类,非实体类可以扩展实体类

该规范不包含实体的equals和hashCode方法的实现要求,据我所知,仅适用于主键类和映射键。

我对以下答案的补充是:

  • 关于字段或属性访问(远离性能考虑),这两种访问都是通过getter和setter合法访问的,因此,我的模型逻辑可以以相同的方式设置/获取它们。 当持久性运行时提供程序(Hibernate、EclipseLink或其他)需要持久化/设置表A中的某个记录时,就会出现这种差异。表A中有一个外键引用表B中的某个列。对于属性访问类型,持久性运行时系统使用我的编码setter方法为表B列中的单元格分配一个新值。对于字段访问类型,持久性运行时系统直接在表B列中设置单元格。 这种差异在单向关系的上下文中并不重要,但必须使用我自己的编码setter方法(属性访问类型)来处理双向关系,前提是setter方法经过了良好的设计,以保证一致性。一致性是双向关系中的一个关键问题。请参阅此,了解设计良好的setter的一个简单示例

  • 关于Equals/hashCode:对于参与双向关系的实体,不可能使用Eclipse自动生成的Equals/hashCode方法,否则它们将具有循环引用,从而导致stackoverflow异常。一旦您尝试双向关系(比如OneToOne)并自动生成Equals()或hashCode()甚至toString(),您将陷入此stackoverflow异常


  • 我将尝试回答几个关键问题:这来自于长期的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
    }