我正在使用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属性。
答案 0 :(得分:1)
当存储数据时,您希望将Monetary类的实例转换为float以将value
存储在cost
表的device
列中,因为数据库表中不存在其余属性。在加载时,您希望将cost
转换为货币实例。我将在后面的答案中提到这一点。
在此之前,我对你的观察很少:
@Embeddable
个班级。因此,在使用该类存储属性时,您不需要再在实体内部使用@Embedded
注释。两者都没有受到伤害,但这不是必要的。它们是彼此的替代品。将组件类标记为@Embeddable
或将拥有实体类中的属性标记为@Embedded
。要将实体的属性名称映射到列名,请使用@Column
注释。
@ColumnTransformer
应该用于存储在任何列中的值的某些转换。例如,将摄氏温度转换为华氏温度,反之亦然。它们应该用于嵌入式实体类的单个细粒度属性(在数据库中具有映射列)而不是嵌入式类。 Hibernate将在运行时使用它们,使用read
和write
中编写的表达式将应用程序层中显示的列值转换为值。
终于来到转换器了!!!
转换器用于将应用层实体转换为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重新创建后,它会从这些阴影字段中创建一个新的货币对象。