将 Spring 的 @Embedded 和 @Embeddable 注解与 JPA 结合使用的指南

2023年 9月 23日 121.8k 0

图片

介绍

在基于 Java 的应用程序开发领域,Spring 框架和 JPA(Java Persistence API)这两个工具彻底改变了开发人员处理持久数据的方式。@Entity通过诸如、@Table等注释,JPA 提供了对传统关系数据库操作的抽象。在这些注释中,@Embedded和@Embeddable对于在实体中嵌入对象特别有用。本指南探讨了这些注释的本质和用例。

了解 @Embedded 和 @Embeddable 的需求

由于现代应用程序呈现出巨大的复杂性,有效管理持久数据至关重要。@Embedded为了充分掌握注释的必要性@Embeddable,我们需要研究开发人员在关系数据库中建模和映射数据时经常遇到的挑战。

关系数据库和面向对象悖论

面向对象的编程允许开发人员轻松创建复杂的数据模型。您可以在对象中包含对象、继承、多态性等等。然而,这些面向对象的模型并不总是能顺利地转化为扁平的、基于表的关系数据库结构。

例如,考虑一下不起眼的User物体。典型的用户可能有简单的字段,例如姓名或电子邮件,但是地址呢?地址本身很复杂,由街道、城市、州、国家等组成。

在面向对象的世界中,您可能会创建一个Address对象并将其嵌入到该User对象中。但是,当将其转换为关系数据库时,您面临一个决定:

  • 将地址字段展平到用户表中。
  • 创建单独的地址表并建立用户和地址之间的关系。
  • 这两种方法都有其缺点。如果用户有多个地址(例如,账单、运输等),则扁平化字段可能会导致冗余的列名称。同时,为每个复杂对象创建单独的表可能会导致数据库架构臃肿和复杂的连接操作,从而影响性能。

    输入@Embeddable:简化对象表示

    JPA 引入了@Embeddable注释来弥补这一差距。通过将一个类标记为@Embeddable,您就表明该类虽然很复杂,但不需要在数据库中拥有其表。相反,它的字段可以直接嵌入到另一个实体中。

    例如,将Address类标记为@Embeddable意味着它的字段(街道、城市、国家等)可以直接集成到另一个实体的表中,例如User. 这样,我们就可以保持面向对象的设计,而不会影响数据库操作的效率。

    @Embedded:无缝集成

    虽然@Embeddable将类标记为可嵌入,但@Embedded注释在另一个实体中使用来嵌入它。正是这两个注释的双重作用为我们的问题提供了一个干净的解决方案。

    通过在实体内的字段@Embedded上使用注释,您实际上是在告诉 JPA:“嘿,从类中获取字段并将它们直接嵌入到表中。” 这消除了对冗余表和繁琐的连接操作的需要,同时仍然保留了开发人员所珍视的面向对象的设计。AddressUserAddressUser

    现实世界的类比

    想想@Embeddable并@Embedded喜欢收拾行李去旅行。有些物品,如袜子或内衣,不需要放在袋子里;它们可以嵌入您的手提箱中。注释@Embeddable就像将这些项目标记为“适合嵌入”。收拾行李时,您可以决定将它们放入哪个手提箱或袋子中。这就是@Embedded正在运行的注释,指定这些项目(或字段)所在的位置。

    深入研究@Embeddable

    在庞大的 JPA 注释生态系统中,@Embeddable占据着独特的位置,在对象关系映射和数据库设计之间提供了微妙的平衡。为了了解它的威力和局限性,让我们剖析它的基本原理、优点以及何时使用它。

    @Embeddable 的基础知识

    其核心是@Embeddable向 JPA 发出信号,表明它所注释的类不是一个成熟的实体。它没有生命周期,也不直接对应于数据库中的唯一表。相反,它是在实际实体中重用的组件。

    在面向对象编程的世界中,将复杂的对象分解为更小、更易于管理的部分是很常见的。然而,这些较小的部分并不总是整齐地映射到关系数据库中。注释@Embeddable为这一差距提供了一座桥梁。通过用 指定一个类@Embeddable,您就表明它已准备好嵌入其他实体中,而无需单独的表。

    使用@Embeddable的好处

  • 模式设计的效率: @Embeddable您无需为每个次要对象创建多个表,而是可以将较小的对象直接集成到较大的实体中,从而形成更紧凑、更高效的模式。
  • 一致性和可重用性: 如果多个实体需要相同的属性集,您可以在类中定义一次并重用它们,而不是在实体之间重复这些属性@Embeddable。这确保了一致性并减少了冗余。
  • 面向对象的设计保留: 开发人员可以在其应用程序中维护干净的、面向对象的设计,同时确保数据库保持优化并且没有不必要的表。
  • 潜在的陷阱和注意事项

    虽然@Embeddable提供了许多好处,但明智地使用它至关重要:

  • 无标识:标记为@Embeddable没有唯一标识符或主键的类。这意味着它们在持久性上下文中并不独立存在。
  • 开销复杂性:如果您发现自己经常重写属性或添加太多配置以适应各种实体中的可嵌入类,则这可能表明单独的实体(和表)可能是更好的方法。
  • 无关系:可嵌入类不应具有类似@OneToMany或 的关系@ManyToOne。如果您发现您最初认为的可嵌入类中需要建立关系,那么可能是时候重新考虑其设计了。
  • 一个说明性的例子

    想象一个DateRange包含开始日期和结束日期的类。这个简单的类可用于多种场景:定义学生的注册期限、指示优惠券的有效性或指定活动的持续时间。DateRange您可以将类定义为,@Embeddable然后将其嵌入到需要的位置,而不是在多个实体之间复制开始日期和结束日期字段。

    @Embeddable
    public class DateRange {    
        private LocalDate startDate;    
        private LocalDate endDate;
        // Constructors, getters, setters, etc.
    }
    

    通过使用这个可嵌入类,您可以提高整个应用程序的可重用性、一致性和更简洁的设计。

    使用@Embedded

    将类标记为 后@Embeddable,您就可以将其嵌入到您的主要实体中。注释@Embedded就是为了这个目的。但是,从将某些东西标记为可嵌入到实际嵌入它的过程有其自身的细微差别。在本节中,我们将探讨@Embedded注释的剖析、它与 的相互作用@Embeddable以及需要注意的事项。

    @Embedded 剖析

    从本质上讲,@Embedded注释很简单。它在实体中使用,指示特定字段是嵌入对象,派生自标有 的类@Embeddable。

    通过使用@Embedded,您实质上为 JPA 提取嵌入对象的字段并将它们表示为托管实体表中的列开了绿灯。

    @Embedded 和 @Embeddable 的相互作用

    值得注意的是,虽然@Embeddable和@Embedded看起来是配对的,但后者在技术上是可选的。如果您@Embeddable在没有注释的实体中使用类型@Embedded,JPA 仍然会识别它并表现得就像@Embedded存在一样。然而,明确的@Embedded注释可以增强可读性和理解性。

    让我们回顾一下之前的例子:

    @Entity
    public class User {
        @Id    
        private Long id;    
        private String name;
        private String email;
         @Embedded
         private Address address;
            // Constructors, getters, setters, etc.
        }
    

    Address如前所述,该类被标记为@Embeddable。在User实体内,通过使用@Embeddedforaddress字段,我们指示 JPA 将Address类的字段直接映射为表中的列User。

    需要注意的事项

  • 相同类型的多个可嵌入项: 如果一个实体具有多个相同@Embeddable类型的字段(例如 billingAddress 和shippingAddress),则确保列名称不冲突至关重要。使用@AttributeOverride或@AttributeOverrides自定义列名称。
  • 延迟加载: 与实体不同,嵌入对象不支持延迟加载,因为它们是其所在实体的一部分。请始终注意,访问具有嵌入对象的实体也会加载嵌入字段。
  • 可嵌入对象中的可嵌入对象: 虽然可以将可嵌入对象嵌套在其他可嵌入对象中,但这可能会导致复杂的层次结构,从而难以维护。确保设计保持逻辑性和直观性。
  • 无生命周期回调: 与实体不同,嵌入式不能有生命周期回调(如@PostPersist或@PreRemove)。嵌入对象的任何生命周期逻辑都必须由包含实体处理。
  • 在实践中

    使用@Embedded通常是出于设计清晰度而做出的决定。假设您有一个Event实体,并且您想要定义事件发生的时间段。如果您将DateRange类定义为@Embeddable,则可以将其无缝嵌入到Event实体中:

    @Entity
    public class Event {
        @Id    
        private Long eventId;
        private String eventName;
        @Embedded
        private DateRange eventPeriod;
        // ... additional fields, constructors, getters, setters ...
        }
    

    这样的设计决策简化了数据库操作,减少了冗余,并保持了面向对象的设计理念。

    高级用例:属性覆盖和自定义

    JPA 的主要优势之一是其灵活性。@Embedded当与和 一起使用时,这种适应性就会大放异彩@Embeddable。虽然基本用例有助于减少冗余并促进干净的代码,但有时现实场景需要进一步定制。在这种情况下,JPA 提供了@AttributeOverride和等工具@AttributeOverrides。

    使用@AttributeOverride自定义列名

    在实体包含多个相同@Embeddable类型字段的情况下,可能会出现列名称冲突。为了防止这种情况,JPA 提供了@AttributeOverride注释,允许您自定义嵌入属性的列名称。

    User考虑我们之前的具有帐单地址和送货地址的实体示例:

    @Entity
    public class User {
        @Id    
        private Long id;
        private String name;
        @Embedded    
        @AttributeOverride(name="street", column=@Column(name="billing_street"))    
        private Address billingAddress;
        @Embedded   
        @AttributeOverride(name="street", column=@Column(name="shipping_street"))   
        private Address shippingAddress;
        // ... additional fields, constructors, getters, setters ...}
    

    在此示例中,为了防止可嵌入类street中的字段Address发生冲突,我们使用@AttributeOverride自定义表中的列名称User。

    使用 @AttributeOverrides 进行多次覆盖

    当需要重写可嵌入类的多个属性时,可以使用注解@AttributeOverrides,它本质上是将多个@AttributeOverride注解分组。

    扩展前面的示例:

    @Entity
    public class User {
        @Id   
        private Long id;   
        private String name;
        @Embedded    
        @AttributeOverrides({ 
        @AttributeOverride(name="street", column=@Column(name="billing_street")),   
        @AttributeOverride(name="city", column=@Column(name="billing_city"))})    
        private Address billingAddress;
        @Embedded
        @AttributeOverrides({
        @AttributeOverride(name="street", column=@Column(name="shipping_street")),
        @AttributeOverride(name="city", column=@Column(name="shipping_city"))})
        private Address shippingAddress;
        // ... additional fields, constructors, getters, setters ...}
    

    此设置可确保帐单地址和送货地址的street和city字段在表中具有不同的列名称User。

    高级定制的注意事项

  • 清晰性优于复杂性: 虽然属性覆盖提供了一个强大的工具来使模型适应各种场景,但过度使用它们会使代码库变得复杂且难以维护。始终确保您的设计决策提高清晰度而不是引入混乱。
  • 数据库架构: 定期与数据库管理员同步或在进行大量自定义时检查生成的架构。目标是确保实际的数据库架构与对象关系映射保持一致。
  • 面向未来: 设计时考虑到可扩展性和潜在的变化。过于具体的定制可能会让未来的扩展或修改变得更加麻烦。
  • 结论

    Spring的@Embedded和@Embeddable注释提供了一种有效的方法来管理实体中的复杂对象。它们简化了数据库架构和操作,使您可以避免不必要的表创建和连接。虽然适用于许多场景,但必须了解何时使用这些注释以及何时单独的实体(和表)可能更合适。与所有工具一样,它们的有效性取决于您的应用程序的特殊性及其要求。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论