【问题标题】:Hibernate ManyToMany Relation with common foreign key休眠多对多关系与公共外键
【发布时间】:2017-01-15 10:32:30
【问题描述】:

我尝试解决几年前在这里提到的相同问题: Hibernate many to many with a composite key with one field shared on either side of the relationship

我有一个 Menu 和一个 Item 类,并希望实现一个单向关系,即菜单保存它包含的所有项目。

Menu and Item both have composite keys out of the merchant_id foreign key and an auto incremental itemId/menuId.(EER 图图片)

因为当我声明复合键时,Hibernate 无法检索自动生成的 Id,并且 Id 在系统中是唯一的,所以我保存了没有额外 EmbeddedId PKClass 的实体:

 @Entity
 @Table(name="ITEM")
 public class Item extends AbstractTimestampEntity{
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="itemId", nullable=false)
    private long itemId;
    @ManyToOne
    @JoinColumn(name="merchantId", nullable=false)
    private Merchant merchant;
    @Column(name="name", length=45)
    private String name;
    @Column(name="description" , length=200)
    private String description;
    @Column(name="price")
    private double price;

    public Item(){} // getters & setters

@Entity
@Table(name="MENU")
public class Menu {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="menuId", nullable=false)
    private long menuId;
    @ManyToOne
    @JoinColumn(name="merchantId", nullable=false)
    private Merchant merchant;
    @Column(name="name", length=45)
    private String name;
    @ManyToMany
    @JoinTable(name="MENU_ITEM", joinColumns = {
        @JoinColumn(name="menuId", nullable=false, updatable=false)},
        //@JoinColumn(name="merchant.merchantId", nullable=false, updatable=false)},
        inverseJoinColumns = { @JoinColumn(name="itemId", nullable=false, updatable=false)})
        //                     @JoinColumn(name="merchantId", nullable=false, updatable=false)})
    private List<Item> items = new ArrayList<Item>(); // constructor, getters & setters

正如您从注释代码中看到的那样,这就是我提出问题的重点。如何在不修改规范化数据库表的情况下最好地映射实体? (他们需要有相同的商家在数据库中进行验证)

【问题讨论】:

  • 你能显示MENU_ITEM外键吗?

标签: java mysql hibernate foreign-keys many-to-many


【解决方案1】:

我怀疑你可能想要这样的东西:

CREATE TABLE `MENU_ITEM` (
    `merchant_id` INT NOT NULL,
    `menu_id` INT NOT NULL,
    `item_id` INT NOT NULL,

    PRIMARY KEY (`merchant_id`, `menu_id`, `item_id`),

    INDEX `ix_menuitem_item` (`item_id`, `merchant_id`),
    INDEX `ix_menuitem_menu` (`menu_id`, `merchant_id`),
    INDEX `ix_menuitem_merchant` (`merchant_id`),

    CONSTRAINT `fk_menuitem_merchant`
        FOREIGN KEY (`merchant_id`)
        REFERENCES `merchant` (`id`),

    CONSTRAINT `fk_menuitem_menu`
        FOREIGN KEY (`menu_id`, `merchant_id`)
        REFERENCES `menu` (`id`, `merchant_id`),

    CONSTRAINT `fk_menuitem_item`
        FOREIGN KEY (`item_id`, `merchant_id`)
        REFERENCES `item` (`id`, `merchant_id`)
)

但很遗憾,这是不可能

一列可以最多使用1个外键,在这种情况下MENU_ITEM.merchant_id被使用3次(最多2次,删除fk_menuitem_merchant)。

所以,你可能想要等效的东西:

CREATE TABLE `MENU_ITEM` (
    `merchant_id` INT NOT NULL,
    `menu_id` INT NOT NULL,
    `menu_merchant_id` INT NOT NULL,
    `item_id` INT NOT NULL,
    `item_merchant_id` INT NOT NULL,

    PRIMARY KEY (`merchant_id`, `menu_id`, `item_id`),

    INDEX `ix_menuitem_item` (`item_id`, `merchant_id`),
    INDEX `ix_menuitem_menu` (`menu_id`, `merchant_id`),
    INDEX `ix_menuitem_merchant` (`merchant_id`),

    CONSTRAINT `fk_menuitem_merchant`
        FOREIGN KEY (`merchant_id`)
        REFERENCES `merchant` (`id`),

    CONSTRAINT `fk_menuitem_menu`
        FOREIGN KEY (`menu_id`, `menu_merchant_id`)
        REFERENCES `menu` (`id`, `merchant_id`),

    CONSTRAINT `fk_menuitem_item`
        FOREIGN KEY (`item_id`, `item_merchant_id`)
        REFERENCES `item` (`id`, `merchant_id`),

    CHECK (`merchant_id` = `menu_merchant_id`),
    CHECK (`merchant_id` = `item_merchant_id`)
)

但是,不幸的是,MySQL 不支持 CHECK

如您所见,这不是 ORM 问题。

所以,你有两个选择:

  1. 实现一些触发器来模拟CHECK (see here)
  2. 让应用程序进行检查:

    @Entity
    public class Menu
    {
        protected class ItemList extends AbstractList<Item>
        {
            protected ArrayList<Item> list;
    
            public ItemList()
            {
                super();
                list = new ArrayList<>();
            }
    
            public ItemList(Collection<? extends Item> c)
            {
                super();
                list = new ArrayList<>(c.size());
                addAll(c);
            }
    
            @Override
            public boolean add(Item item)
            {
                if(!Objects.equals(merchant, item.merchant))
                {
                    throw new IllegalArgumentException();
                    // or return false;
                }
    
                return list.add(item);
            }
    
            @Override
            public Item get(int index)
            {
                return list.get(index);
            }
    
            @Override
            public int size()
            {
                return list.size();
            }
        }
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id", nullable = false)
        protected long id;
    
        @ManyToOne
        @JoinColumn(name = "merchant_id", nullable = false)
        protected Merchant merchant;
    
        @Column(name = "name", length = 45)
        protected String name;
    
        @ManyToMany
        @JoinTable(name = "MENU_ITEM",
            joinColumns = @JoinColumn(name = "menu_id"),
            inverseJoinColumns = @JoinColumn(name = "item_id"))
        protected List<Item> items = new ItemList();
    
        public List<Item> getItems()
        {
            return items;
        }
    
        public void setItems(List<Item> items)
        {
            this.items = new ItemList(items);
        }
    }
    

【讨论】:

  • 谢谢!是的,我正在考虑只在应用层进行检查。但是我不确定是否有更好的方法不会改变规范化的 DB-Design。
猜你喜欢
  • 2013-05-27
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 2023-03-13
相关资源
最近更新 更多