hibernate多对多中间表的操作

Intro

It is not straightforward to realise a many-to-many association with JPA when in the join table there is at least an extra column. In this small tutorial I’m going to show how to design entity objects that will handle the many-to-many relation and which annotations are needed in order to fix a redundancy that we will see in the solution adopted in the following wiki.

This tutorial is a mix up of different sources. The first solution I’m going to show is the one suggested in a wiki.

Mapping a Join Table with Additional Columns (in a JPA pure style)

Source: en.wikibooks.org

A frequent problem is that two classes have a ManyToMany relationship, but the relational join table has additional data. For example if Employee has a ManyToMany with Project but the PROJ_EMP join table also has an IS_TEAM_LEAD column. In this case the best solution is to create a class that models the join table. So an ProjectAssociation class would be created. It would have a ManyToOne to Employee and Project, and attributes for the additional data. Employee and Project would have a OneToMany to the ProjectAssociation. Some JPA providers also provide additional support for mapping to join tables with additional data.

Unfortunately mapping this type of model becomes more complicated in JPA because it requires a composite primary key. The association object’s Id is composed of the Employee and Project ids. The JPA spec does not allow an Id to be used on a ManyToOne so the association class must have two duplicate attributes to also store the ids, and use an IdClass, these duplicate attributes must be kept in synch with the ManyToOne attributes. Some JPA providers may allow a ManyToOne to be part of an Id, so this may be simpler with some JPA providers. To make your life simpler, I would recommend adding a generated Id attribute to the association class. This will give the object a simpler Id and not require duplicating the Employee and Project ids.

This same pattern can be used no matter what the additional data in the join table is. Another usage is if you have a Map relationship between two objects, with a third unrelated object or data representing the Map key. The JPA spec requires that the Map key be an attribute of the Map value, so the association object pattern can be used to model the relationship.

If the additional data in the join table is only required on the database and not used in Java, such as auditing information, it may also be possible to use database triggers to automatically set the data.

Example join table association object annotations

 

@Entity
public class Employee {
  @Id
  private long id;
  ...
  @OneToMany
  private List<ProjectAssociation> projects;
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

 

@Entity
public class Employee {
  @Id
  private long id;
  ...
  @OneToMany
  private List<ProjectAssociation> projects;
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

 

@Entity
public class Project {
  @Id
  private long id;
  ...
  @OneToMany
  private List<ProjectAssociation> employees;
  ...
  // Add an employee to the project.
  // Create an association object for the relationship and set its' data.
  public void addEmployee(Employee employee, boolean teamLead) {
    ProjectAssociation association = new ProjectAssociation();
    association.setEmployee(employee);
    association.setProject(this);
    association.setEmployeeId(employee.getId());
    association.setProjectId(this.getId());
    association.setIsTeamLead(teamLead);

    employees.add(association);
  }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

@Entity
@Table(name="PROJ_EMP")
@IdClass(ProjectAssociationId.class)
public class ProjectAssociation {
  @Id
  private long employeeId;
  @Id
  private long projectId;
  @Column("IS_PROJECT_LEAD")
  private boolean isProjectLead;
  @ManyToOne
  @PrimaryKeyJoinColumn(name="EMPLOYEEID", referencedColumnName="ID")
  private Employee employee;
  @ManyToOne
  @PrimaryKeyJoinColumn(name="PROJECTID", referencedColumnName="ID")
  private Project project;
  ...
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

 

 

public class ProjectAssociationId {

  private long employeeId;

  private long projectId;
  ...
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

 

As you can see in this solution the ProjectAssociation class contains twice the information related to Employee and Project. As explained above, this is due to JPA specification. Googling I found another solution to this problem that allowed me to avoid the redundancy.

Hibernate annotations: The many-to-many association with composite key (in a pure JPA style without redundancy)

Source: boris.kirzner.info

This post contains an evolution of another solution realized in 2006. Since things changed a bit in the meanwhile, the original solution was not the best.

Basically what the author is trying to do is to hold a relation between three item: Produc, Item and ProductItem.

Here is the last part of the post in which the author introduce the solution:

The database part is the same: we have three tables (item, product and product_item), two POJO classes, and two classes for a many-to-many association and its primary key. The main difference from Marsel’s solution is that I’m not using any kind of “fake” properties on ProductItem in order to reference Item and Product, but just a plain transient properties delegating to ProductItemPk.

Here is the source:

@Entity
@Table(name = "item")
public class Item {

    private Integer id;
    private String name;
    private List<ProductItem> productItems = new LinkedList<ProductItem>();

    public Item() {
    }

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "item_id", nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "name")
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.item")
    public List<ProductItem> getProductItems() {
        return this.productItems;
    }

    public void setProductItems(List<ProductItem> productItems) {
        this.productItems = productItems;
    }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

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

    private Integer id;
    private String name;
    private List<ProductItem> productItems = new LinkedList<ProductItem>();

    public Product() {
    }

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "product_id", nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "name")
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.product")
    public List<ProductItem> getProductItems() {
        return this.productItems;
    }

    public void setProductItems(List<ProductItem> productItems) {
        this.productItems = productItems;
    }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

@Entity
@Table(name = "product_item")
@AssociationOverrides({
@AssociationOverride(name = "pk.item", joinColumns = @JoinColumn(name = "item_id")),
@AssociationOverride(name = "pk.product", joinColumns = @JoinColumn(name = "product_id"))
        })
public class ProductItem {

    private ProductItemPk pk = new ProductItemPk();

    @EmbeddedId
    private ProductItemPk getPk() {
        return pk;
    }

    private void setPk(ProductItemPk pk) {
        this.pk = pk;
    }

    @Transient
    public Item getItem() {
        return getPk().getItem();
    }

    public void setItem(Item item) {
        getPk().setItem(item);
    }

    @Transient
    public Product getProduct() {
        return getPk().getProduct();
    }

    public void setProduct(Product product) {
        getPk().setProduct(product);
    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ProductItem that = (ProductItem) o;

        if (getPk() != null ? !getPk().equals(that.getPk()) : that.getPk() != null) return false;

        return true;
    }

    public int hashCode() {
        return (getPk() != null ? getPk().hashCode() : 0);
    }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

@Embeddable
public class ProductItemPk implements Serializable {

    private Item item;
    private Product product;

    @ManyToOne
    public Item getItem() {
        return item;
    }

    public void setItem(Item item) {
        this.item = item;
    }

    @ManyToOne
    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ProductItemPk that = (ProductItemPk) o;

        if (item != null ? !item.equals(that.item) : that.item != null) return false;
        if (product != null ? !product.equals(that.product) : that.product != null)
            return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = (item != null ? item.hashCode() : 0);
        result = 31 * result + (product != null ? product.hashCode() : 0);
        return result;
    }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

This solution is perfect from a model point of view. I used this solution in my project and together with Spring, Hibernate and Maven I’ve been able to generate the schema in an automatic fashion (hbm2dll plugin). The schema produced is exactly what you would expect.

Unfortunately JPA doesn’t allow developer to use this configuration to work with inserts and updates. For instance if you have an Item object with few ProductItem and you perform an insert on the Item, ProductItem object contained in the list will not be inserted in the database. Same stuff happens for a Product and its ProductItem list.

JPA in this case won’t help us anymore. The only way to make inserts and updates to work in cascade, we must recur to provider’s specific annotation. The source code below is the final evolution of the previous example. The JPA provider I used is Hibernate.

Here is the code for the Product class, use the same annotation for the Item class as well and Hibernate will take care to insert/update ProductItem (if any) as well.

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

    private Integer id;
    private String name;
    private List<ProductItem> productItems = new LinkedList<ProductItem>();

    public Product() {
    }

    @Id
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    @Column(name = "product_id", nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "name")
    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /*
     * Here is the annotation to add in order to
     * Hibernate to automatically insert and update
     * ProducItems (if any)
     */
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.product", cascade =
    {CascadeType.PERSIST, CascadeType.MERGE})
    @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
    org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
    public List<ProductItem> getProductItems() {
        return this.productItems;
    }

    public void setProductItems(List<ProductItem> productItems) {
        this.productItems = productItems;
    }
}

<!-- Code inserted with Steve Dunn's Windows Live Writer Code Formatter Plugin. http://dunnhq.com -->

 

Since I only have experience with Hibernate I invite all the reader that have expertise with other JPA provider to reply to this post with the solution offered by other framework such as iBatis.

Thanks for have reading this tutorial and feel free to reply with comments.

摘自:

http://sieze.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table-with-extra-column-using-jpa/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值