Hibernate与同一个类的多个多对多关系

时间:2015-07-01 08:43:54

标签: java hibernate jpa

我遇到了hibernate和映射相同类型的多个对象的问题,也许我只是愚蠢。

我有一个产品

@Entity
@Table(name = "product")
public class Product {

  @Id
  private int id;

  @ManyToMany(cascade = CascadeType.ALL)
  private List<LanguageBasedText> name;

  @ManyToMany(cascade = CascadeType.ALL)
  private List<LanguageBasedText> longDesc;

 (...)
}

和基于语言的文字

@Entity
@Table(name = "languageBasedText")
public class LanguageBasedText {

  @EmbeddedId
  private LanguageTextEmbeddedKey embeddedID;

  @Column(name = "text")
  private String text;

}

带有嵌入式密钥

@Embeddable
public class LanguageTextEmbeddedKey implements Serializable {

  @Column(name = "productID")
  private int productID;
  @Column(name = "lang")
  private String lang;
  @Column(name = "type")
  private String type; 
  //i get the type from an other source and it is the information where 
  //the text goes (name or longDesc or 100 other posibil multi 
  //language text fields)
}

问题是:我如何让hibernate映射正确

我想要一张像

这样的表格

TEXT

product_id |郎|类型|文字

产品

product_id |其他一些东西|

2 个答案:

答案 0 :(得分:1)

总结:您实际上并不想要多对多,您希望在多方面实现一对多的继承(使用单表每层次结构策略),或者在多方面使用过滤器使用@Where的OneToMany属性。前一种方法可能更多OO&#34;后者更少打字,如果你有许多不同的i18n文本字段,可能会阻止类爆炸。 @DiscriminatorOptions@Where都是hibernate注释,因此您无论如何都偏离了标准的JPA注释。

模型

如果您考虑采用ManyToMany方法,可以看到如果没有指定type='name'条件的过滤条件,name可以撤回longDesc文字以及name同一种语言的文字 - 这可能不对。因此,如果您应用过滤器,那么现在只有OneToMany,因为每name只有一个{productID, lang}

如果您在纯建模术语中考虑这一点:

  • 一个Product可以包含多个Name个,但lang只有一个名称;
  • 因此每个Name对于候选键{ProductId, Lang};
  • 都是唯一的
  • LongDesc;
  • 也是如此
  • 因此,您可以将NameLongDesc视为单独的业务实体。

因此,这是[{1}}中每个OneToMany属性的LanguageBasedText模型。 ProductName(以及您提到的其他类似属性)碰巧共享相同的属性:它们实现相同的接口,因此可以将它们存储在一个带有鉴别器的表中。

继承策略

您可以利用文本实体的通用接口并实现继承策略。在您的情况下,使用discriminator列,您可以跳过并自然地使用Single-Table-Per-Hierarchy继承模型。在您的代码中,您实际上有:

LongDesc

代码

用JPA&amp; amp;表达这一点使它适用于Hibernate:

  1. 创建根实体类型,通常为public abstract class LanguageBasedText {} public class NameText extends LanguageBasedText {} public class LongDescText extends LanguageBasedText {} public class Product { private Set<NameText> names; private Set<LongDescText> longDescs; } abstract,以防止直接存储。为清晰起见,请指定@Entity以及@Table
  2. 同时添加@DiscriminatorFormula - 这解决了将错误类型加载到@org.hibernate.annotations.DiscriminatorOptions(force=true)
  3. 中的i18n属性的问题
  4. Product@Entity中继承它。
  5. NameText extends LanguageBasedText属性添加到Product中,键入为文本类子类型参数化的集合(例如@OneToMany)。
  6. 在代码中:

    Set<NameText>

    N.B。上面还有很多其他(不太相关)的代码,包括所有类型的强制性no-args ctors,mutators / accessors等。我还在文本类的ctors中使用了@Entity @Table(name = "product") public class Product { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; private Product() {} public Product(String sku) { this.sku = sku; } // omitting the other properties @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "productID", insertable = false, updatable = false) private Set<NameText> name = new HashSet<>(); @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "productID", insertable = false, updatable = false) private Set<LongDescText> longDesc = new HashSet<>(); private String sku; ... } @Embeddable public class LanguageTextEmbeddedKey implements Serializable { private LanguageTextEmbeddedKey(){} public LanguageTextEmbeddedKey(int productID, String lang, String type) { this.productID = productID; this.lang = lang; this.type = type; } @Column(name = "productID") private int productID; @Column(name = "lang") private String lang; @Column(name = "type") private String type; ... } @Entity @Table(name = "languageBasedText") @Inheritance(strategy=InheritanceType.SINGLE_TABLE) @DiscriminatorFormula(value="type") @org.hibernate.annotations.DiscriminatorOptions(force=true) public abstract class LanguageBasedText { @EmbeddedId private LanguageTextEmbeddedKey embeddedID; @ManyToOne() @JoinColumn(name="productID", insertable=false,updatable=false, nullable=false) protected Product p; private String text; protected LanguageBasedText(){} protected LanguageBasedText(int productID, String lang, String type, String text) { this.embeddedID = new LanguageTextEmbeddedKey(productID, lang, type); this.text = text; } ... } @Entity @DiscriminatorValue("name") public class NameText extends LanguageBasedText { private NameText(){} public NameText(int productID, String lang, String text) { super(productID, lang, "name", text); } } // similarly for LongDescText ,但我建议你用DDD方法替换那些ctors,例如productID来封装那个id。

答案 1 :(得分:1)

首先,Product中的关联不是@ManyToManyLanguageBasedText实体只能引用一个Product,因此Product中的关联必须为@OneToMany

但是,我认为您要实现的目标是让name中的Product列表包含引用LanguageBasedText并且具有Product的{​​{1}}个对象的列表a Type&#34; name&#34;。 longDesc列表应包含LanguageBasedTexttype对象&#34; longDesc&#34;。

由于您使用的是Hibernate,因此最简单的解决方案是在@Where列表中使用@OneToMany注释,因此您需要Product

@OneToMany(cascade = CascadeType.ALL)
@Where(clause="type='name'")
private List<LanguageBasedText> names;

@OneToMany(cascade = CascadeType.ALL)
@Where(clause="type='longDesc'")
private List<LanguageBasedText> longDescs;

注意我还将您的列表更改为复数,因为这更有意义(英文)。

另一个(更复杂的)解决方案是使用继承并为每种类型定义LanguageBasedText的子类,使用单个表策略并使用type列作为鉴别器值。因此,您将拥有以下子类:

@Entity
@DiscriminatorValue("name")
public class NameLanguageBasedText {
    ... etc

但是你最终会得到数百个这样的子类,而且我不确定鉴别器列如何在嵌入式Id中工作。