JPA/Hibernate - 嵌入属性

8

我遇到了一个问题,无法映射类的嵌入式属性。我创建了一些类来说明我试图做的事情。基本上,我有一个使用继承的@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;

    }

}

FORD零件编号

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零件号

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注释来解决此问题的方法?我已经尝试过使用@Inheritance JPA注释,但无法重构继承层次结构中的“PartNumber”部分,因为原始开发人员希望能够使用N个XXXXPartNumber类扩展PartNumber。是否有人知道像这样的东西是否将成为JPA 2.0或Hibernate的新版本的一部分?
6个回答

16

组件(例如@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 类型来配合使用。您的 Part 映射看起来会像这样。
@Type(type="xmlBean")
public PartNumber getPartNumber() {
    return partNumber;
}
public void setPartNumber(PartNumber partNumber) {
    this.partNumber = partNumber;
}

而PartNumber的后代将不必完全映射。当然,缺点是在数据库中处理XML有一点麻烦,所以这可能不是你需要报告的理想方法。另一方面,我正在使用它来存储插件设置,它为我省去了大量与映射/数据库维护相关的麻烦。


针对B,您是否知道有关使用自定义Hibernate类型进行编组/取消编组适当子类的好教程? - mainstringargs
你只需要实现Hibernate的UserType接口 - 这相当简单。这里有一个来自Hibernate文档的例子:(https://www.hibernate.org/172.html)。在你的情况下,你将从“returnedClass()”方法返回PartNumber,并根据你从列中存储的值(并从ResultSet获取)在“nullSafeGet()”中动态实例化适当的后代类。 - ChssPly76

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 {
    ...
}

现在,我可以随意操纵业务键,这很有效,因为每个不同的部分类型都有自己的表格(这对我们非常有用)。
您还可以使用@AttributeOverrides@Embedded来指定不同的列名,以满足您的需要... 在注释文档中有一个示例。
@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;

您可能可以滥用这些功能,以至于您不再关心...

1

看起来你想要做的是使用“可嵌入继承”,但我不认为Hibernate目前支持这个功能。

@Embeddable
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="partType", discriminatorType=DiscriminatorType.STRING)
public PartNumber getPartNumber() 
{
  return partNumber;
}

由于看起来Ford和Chevy之间的区别值似乎是一种格式,我可能会隐藏映射并添加一个不同的函数来返回您想要的特定类型。

@Embeddable
public PartNumber getPartNumber() 
{
  return partNumber;
}

@Transient
public SpecificPartNumber getSpecificPartNumber()
{
  return PartNumberFactory.create( getPartNumber() );
}

请查看评论中的链接...


References: https://dev59.com/WXNA5IYBdhLWcg3wjOlShttp://www.coderanch.com/t/415365/Object-Relational-Mapping/java/Embeddable-inheritance - ccclark

1
“嗯,不太确定。你尝试在PartNumber上加@MappedSuperclass,在子类上加@Embeddable吗?另外可以参考这个链接:https://forum.hibernate.org/viewtopic.php?t=966129
“顺便说一下,你考虑过使用Hibernate Validator而不是自己编写验证方法吗?”

不错的调用。我很好奇它是如何发现子类的,因为配置文件中没有提到它们,而且JVM也不允许你发现它们。 - skaffman
“JVM不允许你发现它们”是什么意思? - Hardy
@skaffman - 我认为你需要将@Embedded标签移到父类并使用@MappedSuperclass...可能吗? - Petriborg


0

正如其他答案中提到的,似乎Hibernate不支持嵌入式继承(至少我没有成功地使其工作)。 我想出了一些解决方法(感谢@ChssPly76的启发):

1)将@Embaddable替换为@Entity,并在专用表中存储PartNumber层次结构。当然,需要使用@OneToOne关系与复合类(Part)一起使用。

2)引入中间实体类,完全反映数据库表的结构。然后手动将实体映射到您的域模型类(以及反向)。这种方法提供了极大的灵活性,但需要编写和测试映射代码的手动工作。

3)以序列化形式存储PartNumber后代,并依赖于能够在反序列化期间实例化正确类的序列化器。

对于我的任务,我选择了第三种方法。我使用Hibernate Types将对象序列化为JSON,并将结果存储在PostgreSQL的jsonb列中。Hibernate Types在底层使用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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接