首页 > 解决方案 > 休眠关系(一对多、多对多、一对一)

问题描述

我正在做一个项目,并希望在其中实现 Hibernate。我看过许多讨论关系的视频,例如客户订单,其中客户可以有很多订单,但订单仅指一个客户。或者,学生课程,学生可以有许多课程,反之亦然。在我的特殊情况下,我有一个客户表和一个称呼表,我想确保使用一组标准的称呼,所以我有以下内容:

在此处输入图像描述

我不确定如何建立这种类型的关系。在我看到的所有示例中,您都会在 Customer 表中放置对称呼对象的引用,但这似乎没有意义。下面我的客户表中的称呼字段是 int 类型。那么如何为这样的实例建立关系呢?谢谢您的帮助!

标签: hibernate

解决方案


hibernate正如您所说,您正在学习并且除了喜欢spring或之外没有标记任何其他标签spring-boot,让我详细介绍一下,以便您完成工作。

让我们以Cart and Items表格为例。. . . . 我们使用外键约束进行一对多映射:

CREATE TABLE `Cart` (
  `cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
 
CREATE TABLE `Items` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `cart_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `cart_id` (`cart_id`),
  CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

如果您使用的是 springboot,那么您可以跳过配置步骤,因为它预先配置了一个依赖项。

Maven 依赖项

然后,我们将 Hibernate 和 H2 驱动程序依赖项添加到我们的 pom.xml 文件中。Hibernate 依赖项使用 JBoss 日志记录,它会自动添加为传递依赖项:

  • 休眠版本 5.2.7.Final
  • H2驱动版本1.4.197

请访问 Maven 中央存储库以获取最新版本的 Hibernate 和 H2 依赖项。

休眠配置

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.h2.Driver</property>
        <property name="hibernate.connection.password"></property>
        <property name="hibernate.connection.url">
          jdbc:h2:mem:spring_hibernate_one_to_many</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
        <property name="hibernate.current_session_context_class">thread</property>
        <property name="hibernate.show_sql">true</property>
    </session-factory>
</hibernate-configuration>

HibernateAnnotationUtil 类

private static SessionFactory sessionFactory;
 
private SessionFactory buildSessionFactory() {
      
    ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().
      configure("hibernate-annotation.cfg.xml").build();
    Metadata metadata = new MetadataSources(serviceRegistry).getMetadataBuilder().build();
    SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
             
    return sessionFactory;
}   
 
public SessionFactory getSessionFactory() {
    if(sessionFactory == null) sessionFactory = buildSessionFactory();
    return sessionFactory;
}

模型

映射相关配置将使用模型类中的 JPA 注释完成:

@Entity
@Table(name="CART")
public class Cart {
 
    //...
 
    @OneToMany(mappedBy="cart")
    private Set<Items> items;
     
    // getters and setters
}

请注意,@OneToMany 注释用于定义 Items 类中的属性,该属性将用于映射 mappedBy 变量。这就是为什么我们在 Items 类中有一个名为“cart”的属性:

@Entity
@Table(name="ITEMS")
public class Items {
     
    //...
    @ManyToOne
    @JoinColumn(name="cart_id", nullable=false)
    private Cart cart;
 
    public Items() {}
     
    // getters and setters
}

在行动

在测试程序中,我们创建了一个带有 main() 方法的类,用于获取 Hibernate Session 并将模型对象保存到实现一对多关联的数据库中:

sessionFactory = HibernateAnnotationUtil.getSessionFactory();
session = sessionFactory.getCurrentSession();
System.out.println("Session created");
         
tx = session.beginTransaction();
 
session.save(cart);
session.save(item1);
session.save(item2);
         
tx.commit();
System.out.println("Cart ID=" + cart.getId());
System.out.println("item1 ID=" + item1.getId()
  + ", Foreign Key Cart ID=" + item.getCart().getId());
System.out.println("item2 ID=" + item2.getId()
+ ", Foreign Key Cart ID=" + item.getCart().getId());

这是我们测试程序的输出:

 Session created    

Hibernate: insert into CART values ()    

Hibernate: insert into ITEMS (cart_id)

  values (?)    

Hibernate: insert into ITEMS (cart_id)    

  values (?)

Cart ID=7


item1 ID=11, Foreign Key Cart ID=7


item2 ID=12, Foreign Key Cart ID=7


Closing SessionFactory

@ManyToOne注释_

正如我们在上面看到的,我们可以many-to-one通过使用@ManyToOne注解来指定关系。映射many-to-one意味着该实体的许多实例都映射到另一个实体的一个实例——一个购物车中的许多物品。

@ManyToOne注释允许我们创建双向关系。我将进一步详细介绍这一点。

不一致和所有权

现在,如果 Cart 引用了 Items,但 Items 又没有引用 Cart,我们的关系将是单向的。这些对象也将具有自然的一致性。

然而,在我们的例子中,这种关系是双向的,带来了不一致的可能性。

让我们想象这样一种情况,开发人员想将 item1 添加到 cart 并将 item2 添加到 cart2,但是犯了一个错误,导致 cart2 和 item2 之间的引用变得不一致:

Cart cart1 = new Cart();
Cart cart2 = new Cart();
 
Items item1 = new Items(cart1);
Items item2 = new Items(cart2); 
Set<Items> itemsSet = new HashSet<Items>();
itemsSet.add(item1);
itemsSet.add(item2); 
cart1.setItems(itemsSet); // wrong!

如上所示,item2 引用了 cart2,而 cart2 没有引用 item2——这很糟糕。

Hibernate 应该如何将 item2 保存到数据库中?item2 会foreign key引用 cart1 还是 cart2?

我们使用关系拥有方的想法解决了这种歧义——属于拥有方的引用优先并保存到数据库中。

项目作为拥有方 将多对一的一方标记为拥有方是一种很好的做法。

换句话说,Items 是拥有方,而 Cart 是相反方,这正是我们之前所做的。

那么,我们是如何做到这一点的呢?

通过在 Cart 类中包含 mappedBy 属性,我们将其标记为反面。

同时,我们还在 Items.cart 字段中添加了@ManyToOne 注释,使 Items 成为拥有方。

回到我们的“不一致”示例,现在 Hibernate 知道 item2 的引用更重要,并将 item2 的引用保存到数据库。

让我们检查一下结果:

item1 ID=1, Foreign Key Cart ID=1

item2 ID=2, Foreign Key Cart ID=2

购物车作为拥有方

也可以将一对多的面标记为拥有面,将多对一的面标记为反面。

虽然这不是推荐的做法,但让我们继续尝试一下。

下面的代码片段显示了一对多方作为拥有方的实现:

public class ItemsOIO {
     
    //  ...
    @ManyToOne
    @JoinColumn(name = "cart_id", insertable = false, updatable = false)
    private CartOIO cart;
    //..
}
 
public class CartOIO {
     
    //..  
    @OneToMany
    @JoinColumn(name = "cart_id") // we need to duplicate the physical information
    private Set<ItemsOIO> items;
    //..
}

请注意我们如何删除 mappedBy 元素并将多对一 @JoinColumn 设置为可插入且可更新为 false。

如果我们运行相同的代码,结果将相反:

item1 ID=1, Foreign Key Cart ID=1

item2 ID=2, Foreign Key Cart ID=1

如上图,现在 item2 属于购物车。

希望从学习中就足够了。


推荐阅读