【问题标题】:Persisting a complex @Embeddable using Hibernate/JPA使用 Hibernate/JPA 持久化复杂的 @Embeddable
【发布时间】:2017-10-01 03:22:33
【问题描述】:

我正在使用 Spring Boot 和 Hibernate 开发 REST 服务,并使用 JPA 实现持久性。有一组 UML 人员定义的类看起来很难实现,我还不知道该怎么做。

这些类用于存储类型化数据。一组类派生自一个名为 XBRLUnitType 的类,并包括一个名为 moneyUnitType 的类,该类包含一个货币代码。另一组类包含一个 Quantity,它是一个“值”加上数据类型。其中一个 Quantities 是 Monetary,它有一个 Float 来保存该值,并且它具有 currencyUnitType,如下所示:

@Embeddable
public class Monetary extends Quantity {

    @JsonProperty("itemUnit")
    @Embedded
    private monetaryItemType itemUnit = null;

}

@Embeddable
public class Quantity {

    @JsonProperty("uncertainty")
    private Float uncertainty = null;

    @JsonProperty("value")
    private Float value = null;

    @JsonProperty("itemUnit")
    @Embedded
    private XBRLItemType itemUnit = null;
}

@Embeddable
public class monetaryItemType extends XBRLItemType {
}

@Embeddable
public class XBRLItemType {

    @JsonProperty("symbol")
    protected String symbol = null;

    @JsonProperty("unitID")
    private String unitID = null;

    @JsonProperty("unitName")
    private String unitName = null;
}

然后在其中一个类中我声明了这个字段:

@Entity
public class Device {

    ... many other fields 

    @Embedded
    private Monetary currency = null;

    ... many other fields 

}

为了创建一个项目,我发布了一些 JSON,我看到 JSON 被正确解释,生成的 Java 对象中的货币字段被正确填写。

BUT ... 在 Hibernate 调试输出中 Hibernate 不会在数据库中创建与货币列匹配的列。

create table device (dtype varchar(31) not null, deviceid binary not null, description varchar(255), meta varchar(255), name varchar(255), documentation varchar(255), type integer, cost float, manual varchar(255), manufacture_date_time binary(255), manufacturer varchar(255), model_number varchar(255), purchase_date_time binary(255), serial_number varchar(255), warranty_promise varchar(255), has_built_in_meter boolean, inverter_type integer, phase_type integer, ground_coverage_ratio float, hasdcoptimizer boolean, has_micro_inverter boolean, module_type integer, firmwareid integer, nameplateid binary, pv_arrayid binary, sub_arrayid binary, pv_stringid binary, systemid binary, primary key (deviceid))

另外,数据库表没有匹配的列。

当我使用 GET 请求检索对象时,货币字段为空。大概是因为它没有数据库列。

在这本在线电子书中我发现了一个声明,Java Persistence 不支持复杂的@Embedded 对象:https://en.wikibooks.org/wiki/Java_Persistence/Embeddables

那是“JPA 规范不允许在可嵌入对象中继承”......显然我有继承。而且,“JPA 1.0 规范只允许嵌入对象中的基本关系,因此不支持嵌套嵌入”,我相信我所拥有的是嵌套嵌入。

我研究的一件事是转换器:

@Embedded
@Column(name="currency", columnDefinition="VARCHAR")
@Convert(converter = MonetaryConverter.class, attributeName="currency")
private Monetary currency = null;

但是,MonetaryConverter 类的方法从未被调用过。

还有这个:

@Type(type="com.amzur.orangebuttonapi.model.primitives.Monetary")
@Columns(columns = { 
        @Column(name="value"), @Column(name="uncertainty"), @Column(name="currencyCode")
})
@ColumnTransformers(value = {
        @ColumnTransformer(
                forColumn="value",
                read="value",
                write="?"
        ),
        @ColumnTransformer(
                forColumn="uncertainty",
                read="uncertainty",
                write="?"
        ),
        @ColumnTransformer(
                forColumn="currencyCode",
                read="itemUnit.symbol",
                write="?"
        )
})
private Monetary currency = null;

它应该创建一些列来存储一些可以解决问题的值。

基本上,我正在寻找持久化 Embeddable 的方法,这一定很复杂。

否则我需要以更多表为代价将这些类转换为@Entity?

更新:上面做了一点澄清。

在 javax.persistence 注释 (https://javaee.github.io/javaee-spec/javadocs/) 中,我看到 @Convert 适用于基本属性。因此@Convert 不适用于我正在处理的非基本属性。

【问题讨论】:

  • 边缘:a) 从大写字母开始上课 (monetaryItemType ) b) 人们报告派生的空课程有一些困难。
  • 我相信所有目标都可以在纯 JPA 2.0/2.1(和新鲜的 2.2)中实现,多年来没有强烈的理由使用方言。 EDIT 2.0 比 1.0 有很多很多的优势
  • 为什么monetaryItemType 实际上没有字段currency?如果它真的有然后更新你的帖子,包括它。对于任何试图处理继承的嵌入式字段的 JPA 提供程序,我也希望有一个鉴别器
  • monetaryItemType 确实是一个空的派生类
  • 对了,你说的这个神奇的领域“货币”在哪里?! in one of the classes 非常精确……

标签: spring hibernate jpa spring-data-jpa


【解决方案1】:

当存储数据时,您希望将 Monetary 类的实例转换为浮点型以将 value 存储在 device 表的 cost 列中,因为数据库表中没有其余属性。加载时您想将cost 转换为Monetary 的一个实例。我稍后会在我的回答中谈到这一点。

在此之前,我有一些观察要告诉你:

  1. 使用 JPA 2 规范,因为它比 JPA 1 规范有很多增强。使用最新版本的 Hibernate,例如 5.2.10。
  2. 您不需要使用 null 初始化任何实体的属性,因为您使用的是 Float 等包装类或您自己的自定义类(例如 Monetary)。它们默认为空。
  3. 您已经声明了@Embeddable 类。因此,在使用该类存储属性时,您无需再次在实体内使用 @Embedded 注释。两者都没有伤害,但这不是必需的。它们是彼此的替代品。将组件类标记为@Embeddable 或将所属实体类中的属性标记为@Embedded
  4. 要将实体的属性名称映射到列名称,请使用@Column 注释。

  5. @ColumnTransformer 应该用于对存储在任何列中的值进行某种转换。例如将摄氏度转换为华氏度,反之亦然。它们应该用于嵌入式实体类的单个细粒度属性(在数据库中具有映射列),而不是嵌入式类。 Hibernate 将在运行时使用它们将应用层中显示的列值转换为使用readwrite 编写的表达式的值。

终于来到了转换器!!!

转换器用于将应用层实体转换为 Db 层实体。与 ORM 几乎相同,嗯。不。您的应用程序层有一个货币实体,它具有价值、单位 ID、符号和其他此类属性。但是您的数据库表只有 cost 列,这是使用 AttributeConverter 而不是 @Embeddable 的经典案例。

现在您应该删除所有 @Embeddable@Embedded 注释 货币、数量、XBRLItemType 和货币项目类型(类名称在 Java 中应始终以大写开头,抱怨 !!!!)类,因为它们没有映射到数据库列的所有属性。它们应该被视为实现 Serializable 接口的简单 POJO 类。然后你应该写 MonetaryConverter.class 以从 Monetary 转换为 cost ,反之亦然。

稍后当您的 DBA 升级您的数据库表以在您的 device 表中包含符号、不确定性、unitId 等列时,您可以从项目中删除 MonetaryConverter.class 并创建 Monetary、Quantity、XBRLItemType 和 moneyItemType 类@Embeddable。它将自动映射到您的数据库列。如果属性名称和数据库列名称会有所不同,您只需在属性上添加@Column 并映射它们。简单!!!

假设您将通过实现javax.persistence.AttributeConverter 接口并覆盖这两个方法来正确编写您的MonetaryConverter.class convertToEntityAttribute()convertToDatabaseColumn()。您的代码应如下所示:

@Convert(converter = MonetaryConverter.class, disableConversion=false)
@Column(name="cost")
private Monetary currency;

这会将您的货币财产转换为成本,反之亦然。

【讨论】:

  • 我了解 Java 的传统是使用 UpperClassClassNames。这只是一个传统,Java 编译器不会强制执行该限制。在某些情况下,类名必须与其他系统中定义的名称相对应。在我的例子中,moneyItemType 是由我正在开发的 Java 系统之外的 UML 定义的,我觉得有必要使用他们定义的大小写。无论如何,当我查找有关 Converters 的信息时,它看起来并不适用,而是找到了我在答案中提供的有关 EntityListeners 的信息。
【解决方案2】:

经过一些研究和反思 JPA 注释,我在这篇文章中使用建议找到了答案:http://www.concretepage.com/java/jpa/jpa-entitylisteners-example-with-callbacks-prepersist-postpersist-postload-preupdate-postupdate-preremove-postremove

@Entity
@EntityListeners(DeviceEntityListener.class)
public class Device {

    ... other fields

    // Because these are not marked @JsonProperty("xyzzy")
    // Jackson won't serialize these to/from JSON

    private Float currencyUncertainty = null;
    private Float currencyValue = null;
    private String currencyCode = null;

    @JsonProperty("currency")
    @Transient
    private Monetary currency = null;

    ... getters/setters etc
}

这就设置了一个监听器来捕获实体事件。新字段将影响货币对象中的值。 Monetary 对象被标记为 Transient,以便 Hibernate 不会持久化它。

public class DeviceEntityListener {

@PrePersist
public void devicePrePersist(Device device) {
    if (device.getCurrency() != null) {
        Monetary currency = device.getCurrency();
        device.setCurrencyUncertainty(currency.getUncertainty());
        device.setCurrencyValue(currency.getValue());
        device.setCurrencyCode(currency.getItemUnit().getSymbol());
    }
}

@PostUpdate
@PostLoad
public void devicePostLoad(Device device) {
    if (device.getCurrencyCode() != null 
            || device.getCurrencyUncertainty() != null 
            || device.getCurrencyValue() != null) {
        Monetary currency = new Monetary();
        currency.setUncertainty(device.getCurrencyUncertainty());
        currency.setValue(device.getCurrencyValue());
        currency.setItemUnit(new monetaryItemType());
        currency.getItemUnit().setSymbol(device.getCurrencyCode());
        device.setCurrency(currency);
    }
}   
}

然后在这些事件上执行此代码。在持久化到 DB 之前,它将值从 Monetary 对象复制到影子字段中。在从 DB 重新创建之后,它会从这些影子字段中创建一个新的 Monetary 对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-12
    • 2023-03-11
    • 1970-01-01
    • 2016-11-29
    • 1970-01-01
    • 2017-04-09
    • 1970-01-01
    • 2016-02-20
    相关资源
    最近更新 更多