创建完美的 JPA 实体 [英] Create the perfect JPA entity

查看:24
本文介绍了创建完美的 JPA 实体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been working with JPA (implementation Hibernate) for some time now and each time I need to create entities I find myself struggling with issues as AccessType, immutable properties, equals/hashCode, ... .
So I decided to try and find out the general best practice for each issue and write this down for personal use.
I would not mind however for anyone to comment on it or to tell me where I'm wrong.

  • 实现可序列化

  • implement Serializable

原因:规范要求您必须这样做,但某些 JPA 提供程序并未强制执行此操作.作为 JPA 提供程序的 Hibernate 不会强制执行此操作,但如果尚未实现 Serializable,它可能会因 ClassCastException 而在其胃深处的某个地方失败.

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

  • create a constructor with all required fields of the entity

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

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

besides this constructor: have a package private default constructor

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

  • 在需要时使用一般的字段访问和属性访问

  • Use field access in general and property access when needed

原因:这可能是最有争议的问题,因为对于其中一个(财产访问与字段访问)没有明确且令人信服的论据;然而,由于更清晰的代码、更好的封装以及不需要为不可变字段创建设置器,字段访问似乎是普遍的最爱

省略不可变字段的 setter(访问类型字段不需要)

Omit setters for immutable fields (not required for access type field)

  • 如果此 id 仅在持久化实体时设置,则切勿使用生成的 id
  • 根据偏好:使用不可变值形成唯一的业务密钥并使用它来测试相等性
  • 如果唯一的业务密钥不可用,请使用在实体初始化时创建的非瞬态UUID;见 这篇很棒的文章了解更多信息.
  • 从不引用相关实体 (ManyToOne);如果此实体(如父实体)需要成为业务密钥的一部分,则仅比较 ID.只要您使用 属性访问类型.
  • Never use a generated id if this id is only set when persisting the entity
  • By preference: use immutable values to form a unique Business Key and use this to test equality
  • if a unique Business Key is not available use a non-transient UUID which is created when the entity is initialised; See this great article for more information.
  • never refer to related entities (ManyToOne); if this entity (like a parent entity) needs to be part of the Business Key then compare the ID's only. Calling getId() on a proxy will not trigger the loading of the entity, as long as you're using property access type.
@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

}

非常欢迎添加到此列表中的其他建议...

Other suggestions to add to this list are more than welcome...

更新

自从阅读这篇文章 我已经调整了我实现 eq/hC 的方式:

Since reading this article I have adapted my way of implementing eq/hC:

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

推荐答案

我将尝试回答几个关键点:这是来自包括几个主要应用程序在内的长期 Hibernate/持久性经验.

I'll try to answer several key points: this is from long Hibernate/ persistence experience including several major applications.

实体类:实现可序列化?

Keys 需要实现 Serializable.将进入 HttpSession 或由 RPC/Java EE 通过线路发送的内容需要实现 Serializable.其他东西:没那么多.把时间花在重要的事情上.

Keys needs to implement Serializable. Stuff that's going to go in the HttpSession, or be sent over the wire by RPC/Java EE, needs to implement Serializable. Other stuff: not so much. Spend your time on what's important.

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

应用程序逻辑的构造函数应该只有几个关键的外键"或类型/种类"字段,这些字段在创建实体时总是已知的.其余的应该通过调用 setter 方法来设置——这就是它们的用途.

Constructor(s) for application logic, should have only a few critical "foreign key" or "type/kind" fields which will always be known when creating the entity. The rest should be set by calling the setter methods -- that's what they're for.

避免在构造函数中放入太多字段.构造函数应该是方便的,并赋予对象基本的理智.名称、类型和/或父级通常都很有用.

Avoid putting too many fields into constructors. Constructors should be convenient, and give basic sanity to the object. Name, Type and/or Parents are all typically useful.

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

OTOH if application rules (today) require a Customer to have an Address, leave that to a setter. That is an example of a "weak rule". Maybe next week, you want to create a Customer object before going to the Enter Details screen? Don't trip yourself up, leave possibility for unknown, incomplete or "partially entered" data.

构造函数:还有,包私有默认构造函数?

是的,但使用受保护"而不是包私有.当必要的内部结构不可见时,子类化是一件非常痛苦的事情.

Yes, but use 'protected' rather than package private. Subclassing stuff is a real pain when the necessary internals are not visible.

字段/属性

使用属性"字段访问 Hibernate,并从实例外部访问.在实例中,直接使用字段.原因:允许标准反射,最简单的&Hibernate 最基本的方法,工作.

Use 'property' field access for Hibernate, and from outside the instance. Within the instance, use the fields directly. Reason: allows standard reflection, the simplest & most basic method for Hibernate, to work.

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

As for fields 'immutable' to the application -- Hibernate still needs to be able to load these. You could try making these methods 'private', and/or put an annotation on them, to prevent application code making unwanted access.

注意:在编写equals()函数时,对'other'实例的值使用getter!否则,您将在代理实例上遇到未初始化/空字段.

Note: when writing an equals() function, use getters for values on the 'other' instance! Otherwise, you'll hit uninitialized/ empty fields on proxy instances.

受保护的(休眠)性能更好?

不太可能.

等于/哈希码?

这与在实体被保存之前处理实体有关——这是一个棘手的问题.散列/比较不可变值?在大多数业务应用程序中,没有.

This is relevant to working with entities, before they've been saved -- which is a thorny issue. Hashing/comparing on immutable values? In most business applications, there aren't any.

客户可以更改地址、更改公司名称等——这并不常见,但确实存在.当数据输入不正确时,也需要进行更正.

A customer can change address, change the name of their business, etc etc -- not common, but it happens. Corrections also need to be possible to make, when the data was not entered correctly.

通常保持不变的少数事情是养育和类型/种类——通常用户重新创建记录,而不是更改这些.但这些并不唯一标识实体!

The few things that are normally kept immutable, are Parenting and perhaps Type/Kind -- normally the user recreates the record, rather than changing these. But these do not uniquely identify the entity!

因此,无论多头还是空头,声称的不可变"数据都不是真的.生成主键/ID 字段的确切目的是提供这种有保证的稳定性和稳定性.不变性.

So, long and short, the claimed "immutable" data isn't really. Primary Key/ ID fields are generated for the precise purpose, of providing such guaranteed stability & immutability.

你需要计划&考虑您的比较需求散列&请求处理工作阶段,当 A) 处理来自 UI 的更改/绑定数据",如果您比较/散列不经常更改的字段",或 B) 处理未保存的数据",如果您比较/散列 ID.

You need to plan & consider your need for comparison & hashing & request-processing work phases when A) working with "changed/ bound data" from the UI if you compare/hash on "infrequently changed fields", or B) working with "unsaved data", if you compare/hash on ID.

Equals/HashCode -- 如果唯一的业务密钥不可用,则使用在实体初始化时创建的非瞬态 UUID

是的,在需要时这是一个很好的策略.请注意,UUID 不是免费的,但在性能方面并非如此——而且集群会使事情变得复杂.

Yes, this is a good strategy when required. Be aware that UUIDs are not free, performance-wise though -- and clustering complicates things.

Equals/HashCode -- 从不引用相关实体

如果相关实体(如父实体)需要成为业务键的一部分,则添加一个不可插入、不可更新的字段来存储父 id(与 ManytoOne JoinColumn 同名)并在等式检查"

"If related entity (like a parent entity) needs to be part of the Business Key then add a non insertable, non updatable field to store the parent id (with the same name as the ManytoOne JoinColumn) and use this id in the equality check"

听起来是个好建议.

希望这有帮助!

这篇关于创建完美的 JPA 实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆