`
cowwen
  • 浏览: 26680 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

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/

分享到:
评论

相关推荐

    hibernate 多表关联 中间表

    通过以上步骤,我们就能在Hibernate中有效地管理多对多关联,并利用中间表实现复杂的数据操作。理解并熟练掌握这些知识,对于任何Java开发人员来说都是非常有价值的,特别是在大型企业级应用中,这种关联映射的使用...

    Hibernate(多对多表操作)

    在Java的持久化框架中,Hibernate是一个非常流行的ORM...通过以上步骤,我们就可以在Hibernate中有效地管理和操作多对多关系了。在实际开发中,理解并熟练掌握这些概念和技术,对于提升应用的性能和可维护性至关重要。

    hibernate多对多关联映射

    本文将深入探讨Hibernate如何实现多对多关联映射,并通过实际例子解析相关配置和操作。 一、多对多关联概述 在数据库设计中,多对多关联表示两个表之间的关系,如学生和课程的关系,一个学生可以选修多门课程,而...

    hibernate多对多

    四、操作多对多关系 在代码中,我们可以方便地进行多对多关系的增删查改: - 添加关系:`student.getCourses().add(course);` - 删除关系:`student.getCourses().remove(course);` - 查询:通过`student....

    hibernate 多对多源代码

    虽然多对多关联方便了数据操作,但也可能导致性能问题,因为每次关联操作都需要涉及中间表。在大数据量的场景下,可能需要优化查询策略,比如使用JOIN查询而非多次单独查询,或者在业务层缓存部分关联数据。 总结,...

    hibernate多对多查询

    总之,Hibernate的多对多查询涉及实体类的定义、中间表的映射以及查询语句的编写。理解和熟练运用这些概念对于开发复杂的Java应用程序至关重要。在实际项目中,应根据业务需求灵活调整关联策略,确保数据的一致性和...

    Hibernate多对多数据表操作-插入

    在这个例子中,通过调用`save`方法,Hibernate会自动处理插入到中间表的操作,确保多对多关系的正确建立。 总结来说,使用Hibernate处理多对多关系,关键在于理解`@ManyToMany`注解的使用,中间实体类的设计(如果...

    Hibernate多对多实例+数据库代码

    通过查看代码和运行示例,开发者可以深入理解Hibernate是如何自动处理复杂的关联操作,比如通过中间表实现多对多的添加、删除和查找。 **总结** 这个"Hibernate多对多实例+数据库代码"涵盖了ORM的核心概念,特别是...

    hibernate(多对多关系映射)

    在Hibernate中,多对多关系通常通过中间表来实现,这个中间表包含了两个实体的主键作为外键。你需要在实体类中定义对应的集合属性,并在对应的`@ManyToMany`注解中指定关联的实体和中间表的信息。例如: ```java ...

    hibernate多对多的简单实现

    - 多对多关系通常需要一个中间表来存储两个实体的关联。在这个例子中,可能有一个名为`student_course`的中间表,包含`student_id`和`course_id`两个字段,分别引用学生和课程的主键。 4. **JPA实体(Entities)**...

    Hibernate ORM - 多对多双向连接表关联关系

    6. **源码分析**:为了更好地理解这一过程,你可以查看Hibernate源码,了解它如何处理多对多关联的SQL语句生成、中间表的操作以及事务管理。这有助于提高对Hibernate框架的理解。 通过以上步骤,我们可以在...

    Hibernate关于注解的一对多,多对多,一对一

    2. **@JoinTable**:多对多关系通常需要一个中间表来存储双方的关联,`@JoinTable`用来定义这个中间表,包括它的名字、连接的列名等。 ```java @Entity public class User { @ManyToMany @JoinTable(name = "user...

    hibernate多对多双向关联

    3. **中间表**:多对多关系通常需要一个中间表来存储两个表的连接。在Hibernate中,可以通过`@JoinTable`注解定义这个中间表,包括它的名字、连接两个实体的外键等信息。 4. **关联维护**:双向关联需要在两个实体...

    hibernate 多对多操作

    标题“hibernate 多对多操作”表明了本文将探讨的是在Java编程中,使用Hibernate框架处理数据库中的多对多关联关系。Hibernate是Java领域广泛使用的对象关系映射(ORM)工具,它允许开发者用面向对象的方式处理...

    Hibernate多对多关联添加及查询示例

    在上述代码中,`@ManyToMany`注解定义了多对多关联,`@JoinTable`指定了中间表的名称和连接列。`mappedBy`属性用于指定另一端的关联字段,这表示`Course`实体的`students`集合是由`Student`实体的`courses`集合维护...

    hibernate多对多关联映射(单项关联)

    在Java的持久化框架Hibernate中,多对多关联映射是一种常见的关系模型,它用于处理两个实体类之间存在多个对应关系的情况。这篇博客"hibernate多对多关联映射(单项关联)"深入探讨了如何在Hibernate中实现这种映射...

    hibernate 映射关系学习入门 多对多实体映射

    五、操作多对多关系 在实际应用中,我们可以通过Hibernate提供的API来添加、删除和查找多对多关系。例如,向`Student`添加一门`Course`,可以使用`addCourse(Course course)`方法;删除则通过`removeCourse(Course ...

    hibernate 多对多映射实例,学生 选课

    在这个“hibernate 多对多映射实例,学生 选课”项目中,我们将探讨如何使用Hibernate处理多对多关系,以及如何构建一个学生选课的系统。 首先,我们要理解多对多关系。在现实生活中,学生和课程之间就是一个典型的...

    Hibernate多对多关联关系demo

    通过这个"Hibernate多对多关联关系demo",开发者可以学习如何在实际项目中设置和操作多对多关联,同时理解这些关联关系可能带来的性能和设计挑战。通过练习和实践,你将能够熟练地在Hibernate环境中管理复杂的实体...

Global site tag (gtag.js) - Google Analytics