我在映射类的嵌入属性时遇到问题。我创建了一些类似于我试图说明的类。基本上,我有一个使用继承的@Embeddable类层次结构。顶级类“Part Number”只有一个属性,扩展类没有为“Part Number”类添加任何属性,它们只添加了一些验证/逻辑。
这就是我的意思:
PART
@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;
}
}
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;
}
}
FORD PARTNUMBER
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;
}
}
CHEVY 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忽略了继承层次结构,并且不喜欢PartNumber是抽象的这一事实。 有没有办法使用JPA或Hibernate Annotations来做到这一点?我尝试过使用@Inheritance JPA注释。
我无法重构层次结构中的“PartNumber”部分,因为原始开发人员希望能够使用N个XXXXPartNumber类扩展PartNumber。
有谁知道这样的事情是否会成为JPA 2.0或新版Hibernate的一部分?
答案 0 :(得分:15)
不支持组件(例如@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类marshall / unmarshall适当的子类中使用get / setPartNumber()访问器。您甚至可以编写自己的Hibernate自定义类型来为您执行此操作(非常简单)。
C)如果PartNumber后代在必须存储的属性上有所不同并且将它们映射为实体是不可接受的,无论出于何种原因,您可以使用marshall / unmarshall将它们作为字符串(作为XML或任何其他适合该法案的内容)和存储那。我正在使用XStream这个目的,我写了一个简单的Hibernate类型。您的零件映射看起来像
@Type(type="xmlBean")
public PartNumber getPartNumber() {
return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
this.partNumber = partNumber;
}
和PartNumber后代根本不必映射。当然,缺点是在数据库中处理XML有点麻烦,因此可能不是您可能需要报告的理想方法。 OTOH,我正在使用它来存储插件设置,它为我提供了很多的映射/数据库维护麻烦。
答案 1 :(得分:2)
我在自己的架构中遇到了类似的问题,所以我现在所采用的是这样的:
家长班:
@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
一起使用我想以不同的方式指定列名称,但需要...... annotation docs中有一个示例。
@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;
你可能会滥用这个,你不会在意......
答案 2 :(得分:1)
作为一个副节点 - 您是否考虑过使用Hibernate Validator而不是本土的验证方法?
答案 3 :(得分:1)
你想要做的是使用“Embedable Inheritance”,我不相信hibernate支持。
@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() );
}
请参阅评论中的链接...
答案 4 :(得分:1)
EclipseLink通过@Customizer支持可嵌入对象中的继承 http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Embeddable#Inheritance
答案 5 :(得分:0)
正如在其他答案中提到的那样,Hibernate似乎不支持嵌入式的继承(至少我没能使它工作)。 我提出了一些解决方法(感谢@ ChssPly76的灵感):
1)将@Embaddable
替换为@Entity
并将PartNumber
层次结构存储在专用表中。当然,它需要@OneToOne
关系才能与复合类(Part
)一起使用。
2)引入充分反映数据库表结构的中间实体类。然后手动将实体映射到您的域模型类(并返回)。这种方法以编写和测试映射代码的手工工作的代价提供了极大的灵活性。
3)以序列化形式存储PartNumber
后代并依赖序列化程序,这些序列化程序必须能够在反序列化期间实例化正确的类。
对于我的任务,我选择了第三种方法。我使用Hibernate Types将对象序列化为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
}