当前位置: 首页 > 面试题库 >

创建完美的JPA实体

钱华晖
2023-03-14
问题内容

我已经使用JPA(实现hibernate)一段时间了,每次我需要创建实体时,我都会遇到与AccessType,不可变属性,equals / hashCode等有关的问题。
因此,我决定尝试找出每个问题的最佳常规做法,并写下来供个人使用。
但是,我不介意让任何人对此发表评论或告诉我我错了。

实体类

  • 实现可序列化

原因: 规范要求您必须这样做,但是某些JPA提供程序没有强制执行此操作。
作为JPA提供程序的Hibernate不会强制执行此操作,但是如果尚未实现Serializable,它可能会因ClassCastException失败而失败。

建设者

  • 用实体的所有必填字段创建一个构造函数

原因:构造函数应始终使创建的实例保持健全状态。

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

原因:Hibernate需要默认构造函数来初始化实体;
允许使用private,但是在没有字节码检测的情况下,包私有(或公共)可见性对于运行时代理生成和有效的数据检索是必需的。

字段/属性

  • 在一般情况下使用字段访问,在需要时使用属性访问

原因:这可能是最有争议的问题,因为没有明确的,令人信服的论点(财产使用权与实地使用权);
但是,由于更清晰的代码,更好的封装并且无需为不可变字段创建设置器,因此字段访问似乎是普遍喜欢的方法

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

  • 属性可能是私有的
    原因:我曾经听说保护(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

问题答案:

我将尝试回答几个关键点:这是基于长期的Hibernate /持久性经验,包括几个主要应用程序。

实体类:实现可序列化吗?

密钥 需要实现Serializable。将要在HttpSession中存储的东西,或由RPC / Java
EE通过电线发送的东西,都需要实现Serializable。其他东西:不是很多。把时间花在重要的事情上。

构造函数:使用实体的所有必填字段创建构造函数吗?

用于应用程序逻辑的构造函数应仅具有几个关键的“外键”或“类型/种类”字段,这些字段在创建实体时始终是已知的。其余的应该通过调用setter方法来设置-
这就是它们的用途。

避免将太多字段放入构造函数中。构造函数应该很方便,并且要使对象基本清醒。名称,类型和/或父母通常都是有用的。

OTOH(如果今天(今天)应用规则要求客户提供地址,请将其留给设置者。那是“弱规则”的一个例子。也许下周,您想在进入“输入详细信息”屏幕之前创建“客户”对象吗?不要绊倒自己,留下未知,不完整或“部分输入”数据的可能性。

构造函数:另外,封装私有默认构造函数?

是的,但请使用“受保护的”而不是私有包。当必要的内部结构不可见时,子类化的东西是一个真正的痛苦。

字段/属性

从实例外部对hibernate使用“属性”字段访问。在实例内,直接使用字段。原因:允许标准反射(Hibernate最简单,最基本的方法)起作用。

至于应用程序“不可变”的字段-
Hibernate仍然需要能够加载它们。您可以尝试将这些方法设为“私有”,和/或在其上添加注释,以防止应用程序代码进行不必要的访问。

注意:编写equals()函数时,请在’other’实例上使用getter作为值!否则,您将在代理实例上点击未初始化/空白的字段。

受保护的(hibernate)性能更好?

不太可能。

等于/哈希码?

这与在保存实体之前使用实体有关-这是一个棘手的问题。哈希/比较不变值?在大多数业务应用程序中,根本没有。

客户可以更改地址,更改其业务名称等,虽然不常见,但确实如此。当数据输入不正确时,还需要进行校正。

通常保持不变的几件事是育儿,也许还有类型/种类-通常,用户重新创建记录,而不是更改它们。但是这些并不能唯一地标识实体!

因此,总之,所谓的“不变”数据并不是真的。主键/ ID字段是出于精确目的而生成的,以提供这种保证的稳定性和不变性。

当A)如果在“不经常更改的字段”上进行比较/散列,或者B)在“不经常更改的字段”上进行比较/散列,则需要计划和考虑比较,散列和请求处理工作阶段的需求。未保存的数据”,如果您比较/哈希ID。

Equals / HashCode-如果没有唯一的业务密钥,请使用在初始化实体时创建的非临时UUID

是的,这是必要时的好策略。请注意,UUID不是免费的,但是从性能角度来看,它使集群变得复杂。

等于/哈希码-从不引用相关实体

“如果相关实体(例如父实体)需要成为业务密钥的一部分,则添加一个不可插入,不可更新的字段来存储父ID(与ManytoOneJoinColumn的名称相同)并在相等性检查中使用此ID ”

听起来像个好建议。

希望这可以帮助!



 类似资料:
  • Java类可以很容易地转换成实体。 对于实体转换,基本要求是 - 无参数构造函数 注解 在这里,我们将学习如何通过示例,学习将常规Java类转换为实体类 - 简单的一个学生类(Student),代码如下 - 上面的类是一个常规的java类,有三个属性: , 和 。要将此类转换为实体,请在此类中添加和注解。 - 这是一个标记注释,表明这个类是一个实体。这个注释必须放在类名称上。 - 此注释位于持有持

  • 所以我从JDBC转移到JPA,我从我的schema.sql文件中删除了“创建表”sql语句,所以现在这个文件看起来像这样: 我的实体,成员。课堂上,是这样的: 以下是我得到的例外: 如您所见,它表示找不到表成员。如果我将“createtablemember”sql语句带回模式。sql文件,也不例外 我在Eclipse上使用Spring Boot。 谢谢!

  • 问题内容: 我的JPA / Hibernate应用程序中有几个映射的对象。在网络上,我收到的数据包代表这些对象的更新,或者实际上可能完全代表新的对象。 我想写一个像 如果数据库中存在一个带有pk primaryKey的对象,则该对象将返回所提供类的一个对象,否则将创建该类的新对象,并将其持久化并返回。 接下来,我将使用该对象来更新事务中的所有字段。 在JPA中是否有惯用的方法来执行此操作,还是有更

  • 朋友们呢,我对CSS这个话题还不熟悉。我正在尝试执行脉冲效果,正如你们在这张图片中看到的。 我想我的菜单图标(图标旁边的字家)有一个类似的动画。 我的问题是,我不知道如何实现一个完美的圆在哪里实现这个动画。这是我目前的结果: 我能做什么? 这是我的代码: https://multi-level-side-menu-4bj1tj.stackblitz.io 我分享了我正在做的源代码,如果你想编辑一些

  • 问题内容: 通常,我是Hibernate用户,对于我的新项目,我们使用JPA 2.0。 我的DAO收到一个带有泛型的容器。 以下行将无法编译: 因为我必须这样指定类型: 但是我不想那样做!因为我在容器中使用了泛型!你有主意吗 问题答案: 只要您是(这是必填项),您就可以执行以下操作:

  • 问题内容: 我的JPA / Hibernate应用程序中有几个映射的对象。在网络上,我收到的数据包代表这些对象的更新,或者实际上可能完全代表新的对象。 我想写一个像 如果数据库中存在一个带有pk primaryKey的对象,则该对象将返回所提供类的一个对象,否则将创建该类的新对象,并将其持久化并返回。 接下来我将要处理的对象是在事务中更新其所有字段。 在JPA中是否有惯用的方法来执行此操作,还是有