[英]JPA/Hibernate - Embedding an Attribute
我在映射类的嵌入式属性时遇到问题。 我创建了一些类,这些类与我试图说明的类相似。 基本上,我有一个使用继承的@Embeddable 类层次结构。 顶级类“零件号”只有一个属性,扩展类不向“零件号”类添加任何属性,它们只添加一些验证/逻辑。
这就是我的意思:
部分
@Entity
@Table(name="PART")
public class Part {
private Integer id;
private String name;
private PartNumber partNumber;
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name="PART_NAME")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Embedded
public PartNumber getPartNumber() {
return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
this.partNumber = partNumber;
}
}
零件号
@Embeddable
public abstract class PartNumber {
protected String partNumber;
private String generalPartNumber;
private String specificPartNumber;
private PartNumber() {
}
public PartNumber(String partNumber) {
this.partNumber = partNumber;
}
@Column(name = "PART_NUMBER")
public String getPartNumber() {
return partNumber;
}
public void setPartNumber(String partNumber) {
this.partNumber = partNumber;
}
/**
* @param partNumber
* @return
*/
public boolean validate(String partNumber) {
// do some validation
return true;
}
/**
* Returns the first half of the Part Number
*
* @return generalPartNumber
*/
@Transient
public String getGeneralPartNumber() {
return generalPartNumber;
}
/**
* Returns the last half of the Part Number
* which is specific to each Car Brand
*
* @return specificPartNumber
*/
@Transient
public String getSpecificPartNumber() {
return specificPartNumber;
}
}
福特零件号
public class FordPartNumber extends PartNumber {
/**
* Ford Part Number is formatted as 1234-#1234
*
* @param partNumber
*/
public FordPartNumber(String partNumber) {
super(partNumber);
validate(partNumber);
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#validate(java.lang.String)
*/
@Override
public boolean validate(String partNumber) {
// do some validation
return true;
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#getGeneralPartNumber()
*/
@Override
public String getGeneralPartNumber() {
return partNumber;
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#getSpecificPartNumber()
*/
@Override
public String getSpecificPartNumber() {
return partNumber;
}
}
雪佛兰零件编号
public class ChevyPartNumber extends PartNumber {
/**
* Chevy Part Number is formatted as 1234-$1234
*
* @param partNumber
*/
public ChevyPartNumber(String partNumber) {
super(partNumber);
validate(partNumber);
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#validate(java.lang.String)
*/
@Override
public boolean validate(String partNumber) {
// do some validation
return true;
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#getGeneralPartNumber()
*/
@Override
public String getGeneralPartNumber() {
return partNumber;
}
/*
* (non-Javadoc)
*
* @see com.test.PartNumber#getSpecificPartNumber()
*/
@Override
public String getSpecificPartNumber() {
return partNumber;
}
}
当然这是行不通的,因为 Hibernate 忽略了 Inheritance Hierarchy 并且不喜欢 PartNumber 是抽象的这一事实。 有没有办法使用 JPA 或 Hibernate 注释来做到这一点? 我试过使用 @Inheritance JPA 注释。
我无法重构层次结构的“PartNumber”部分,因为原始开发人员希望能够使用 N 个 XXXXPartNumber 类扩展 PartNumber。
有谁知道这样的东西是否会成为 JPA 2.0 或新版本的 Hibernate 的一部分?
不支持组件(例如@Embeddable)继承,很可能永远不会。 这是有充分理由的——实体标识符在 Hibernate 支持的所有继承策略中起着至关重要的作用,而组件没有(映射的)标识符。
你有三个选择:
A) 将 PartNumber(及其所有后代)映射为实体。 PartNumber 可能仍然是抽象的:
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="part_type", discriminatorType=DiscriminatorType.STRING)
public abstract class PartNumber {
...
}
@Entity
@DiscriminatorValue("Ford")
public class FordPartNumber extends PartNumber {
...
}
B) 根据您的示例,所有 PartNumber 后代似乎仅在行为上有所不同(它们不引入任何要存储的新属性)。 如果情况确实如此,您可以将 PartNumber 属性加上您自己的鉴别器值(以便您知道要实例化哪个类)映射为 @Embedded 私有属性,并在 Part 类编组/解组适当的子类中具有 get/setPartNumber() 访问器。 您甚至可以编写自己的 Hibernate 自定义类型来为您执行此操作(非常简单)。
C) 如果 PartNumber 后代确实在必须存储的属性上有所不同,并且无论出于何种原因将它们映射为实体都是不可接受的,您可以使用编组/解组将它们编组为字符串(作为 XML 或任何其他符合要求的格式)并存储它。 我使用 XStream 正是为了这个目的,我写了一个简单的 Hibernate 类型来配合它。 您的零件映射看起来像
@Type(type="xmlBean")
public PartNumber getPartNumber() {
return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
this.partNumber = partNumber;
}
并且 PartNumber 后代根本不需要映射。 当然,缺点是在数据库中处理 XML 比较麻烦,因此对于您可能需要报告的内容,这可能不是理想的方法。 OTOH,我用它来存储插件设置,它为我节省了很多映射/数据库维护的麻烦。
我在自己的架构中遇到了类似的问题,所以我现在采用的是这样的:
父类:
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@SequenceGenerator(name="SEQ", sequenceName="part_id_seq", initialValue=1, allocationSize=1)
public abstract class BasePart {
@Id
@Column(name="part_id")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQ")
protected Long partId;
@YourBusinessKeyAnnotation
@Column(name="part_number")
protected String partNumber
...
}
儿童班:
@Entity
public class FordPart extends BasePart {
...
}
@Entity
public class ChevyPart extends BasePart {
...
}
现在我可以根据需要操作 biz 键,并且效果很好,因为每个不同的零件类型都有自己的表(这对我们很有用)。
您也可以将@Embedded
与@AttributeOverrides
一起使用,我认为可以根据需要以不同方式指定列名称... 注释文档中有一个示例。
@Entity
public class Person implements Serializable {
// Persistent component using defaults
Address homeAddress;
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
@AttributeOverride(name="name", column = @Column(name="bornCountryName") )
} )
Country bornIn;
...
}
...
@Entity
public class Person implements Serializable {
// Persistent component using defaults
Address homeAddress;
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="iso2", column = @Column(name="bornIso2") ),
@AttributeOverride(name="name", column = @Column(name="bornCountryName") )
} )
Country bornIn;
...
}
...
@Embedded
@AttributeOverrides( {
@AttributeOverride(name="city", column = @Column(name="fld_city") ),
@AttributeOverride(name="nationality.iso2", column = @Column(name="nat_Iso2") ),
@AttributeOverride(name="nationality.name", column = @Column(name="nat_CountryName") )
//nationality columns in homeAddress are overridden
} )
Address homeAddress;
你可能会滥用它,以至于你不会在意......
嗯,不确定。 您是否尝试过将 @MappedSuperclasson PartNumber 和 @Embeddable 放在子类上。 另见 - https://forum.hibernate.org/viewtopic.php?t=966129
作为一个侧节点——你有没有考虑过使用 Hibernate Validator 而不是自己开发的验证方法?
看起来您正在尝试做的是使用“可嵌入继承”,我认为休眠还不支持它。
@Embeddable
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="partType", discriminatorType=DiscriminatorType.STRING)
public PartNumber getPartNumber()
{
return partNumber;
}
因为它看起来像福特和雪佛兰之间的区别值看起来像一种格式。 我可能会隐藏映射并添加一个不同的函数来返回您想要的特定类型。
@Embeddable
public PartNumber getPartNumber()
{
return partNumber;
}
@Transient
public SpecificPartNumber getSpecificPartNumber()
{
return PartNumberFactory.create( getPartNumber() );
}
请参阅评论中的链接...
EclipseLink 通过@Customizer 支持嵌入可继承http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Embeddable#Inheritance
正如其他答案中提到的那样,Hibernate 似乎不支持可嵌入对象的继承(至少我没能让它起作用)。 我提出了一些解决方法(感谢@ChssPly76 的启发):
1) 将@Embaddable
替换为@Entity
并将PartNumber
层次结构存储在专用表中。 当然,它需要@OneToOne
关系与复合类( Part
)一起使用。
2) 引入充分反映数据库表结构的中间实体类。 然后手动将实体映射到您的域模型类(并返回)。 这种方法以编写和测试映射代码的手动工作为代价提供了极大的灵活性。
3) 以序列化形式存储PartNumber
后代,并依赖序列化程序,序列化程序必须能够在反序列化期间实例化正确的类。
对于我的任务,我选择了第三种方法。 我使用Hibernate 类型将对象序列化为 JSON,并将结果存储在 PostgreSQL 的jsonb
列中。 Hibernate 类型在底层使用 Jackson,因此它的所有注释(包括控制多态行为的注释)都可供您使用。 在您的情况下,代码将是这样的:
@Entity
public class Part {
// ...
@Type(type = "com.vladmihalcea.hibernate.type.json.JsonBinaryType")
private PartNumber partNumber;
}
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
@JsonSubTypes(value = [
JsonSubTypes.Type(value = FordPartNumber::class, name = "FordPartNumber"),
JsonSubTypes.Type(value = ChevyPartNumber::class, name = "ChevyPartNumber")
])
public abstract class PartNumber { /* ... */ }
public class FordPartNumber extends PartNumber { /* ... */ }
public class ChevyPartNumber extends PartNumber { /* ... */ }
数据库中生成的 JSON 看起来像
{
"type": "FordPartNumber",
// ... other serialized properties of the FordPartNumber class
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.