使用Hibernate / JPA

时间:2017-10-01 03:22:33

标签: spring hibernate jpa spring-data-jpa

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

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

@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对象中的货币字段被正确填充。

但是......在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适用于Basic属性。因此@Convert不适用于我正在处理的非Basic属性。

2 个答案:

答案 0 :(得分:1)

当存储数据时,您希望将Monetary类的实例转换为float以将value存储在cost表的device列中,因为数据库表中不存在其余属性。在加载时,您希望将cost转换为货币实例。我将在后面的答案中提到这一点。

在此之前,我对你的观察很少:

  1. 使用JPA 2规格,因为它比JPA 1规格有很多增强功能。使用最新版本的Hibernate,如5.2.10。
  2. 您不需要使用null初始化任何实体的属性,因为您使用的是包装类,如Float或您自己的自定义类,如Monetary。默认情况下它们将为null。
  3. 您已宣布@Embeddable个班级。因此,在使用该类存储属性时,您不需要再在实体内部使用@Embedded注释。两者都没有受到伤害,但这不是必要的。它们是彼此的替代品。将组件类标记为@Embeddable或将拥有实体类中的属性标记为@Embedded
  4. 要将实体的属性名称映射到列名,请使用@Column注释。

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

  6. 终于来到转换器了!!!

    转换器用于将应用层实体转换为Db层实体。与ORM差不多,呃。 Nopes。您的应用程序层具有货币实体,其具有值,unitId,符号和其他此类属性。但是您的数据库表只有cost列,这是使用AttributeConverter而不是@Embeddable的经典案例。

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

    稍后当您的DBA升级数据库表以在device表中包含symbol,uncertainty,unitId等列时,您可以从项目中删除MonetaryConverter.class并使货币,数量, XBRLItemType和monetaryItemType类@Embeddable。它将自动映射到您的数据库列。如果属性名称和数据库列名称不同,您只需在属性上添加@Column并映射它们即可。简单!!!

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

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

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

答案 1 :(得分:0)

经过一些研究并反思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重新创建后,它会从这些阴影字段中创建一个新的货币对象。