Spring Data JPA - 为三个表创建 @Composite 键

Posted

技术标签:

【中文标题】Spring Data JPA - 为三个表创建 @Composite 键【英文标题】:Spring Data JPA - create @Composite key for the three tables 【发布时间】:2019-08-01 14:58:03 【问题描述】:

我从这里扩展我的问题:Define CompositeKey with three tables using JPA/Hibernate?。在此示例中,我希望创建 复合键 来创建 PRODUCT_ID、CATEGORY_ID、STOCK_ID 的唯一组合。

我开发了以下代码,但不确定如何将记录保存到数据库中。

Stock.java

@Entity
public class Stock implements Serializable 

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "STOCK_ID", unique = true, nullable = false)
    private Integer stockId;

    @Column(name = "STOCK_CODE", unique = true, nullable = false, length = 10)
    private String stockCode;

    @Column(name = "STOCK_NAME", unique = true, nullable = false, length = 20)
    private String stockName;

    // Owner of the relationship
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "linkPk.stock", cascade = CascadeType.ALL)
    private Set<StockCategoryProductLink> stockCategoryProductLinks = new HashSet<>(0);

    public Stock() 
    

    public Stock(Integer stockId, String stockCode, String stockName,
            Set<StockCategoryProductLink> stockCategoryProductLinks) 
        super();
        this.stockId = stockId;
        this.stockCode = stockCode;
        this.stockName = stockName;
        this.stockCategoryProductLinks = stockCategoryProductLinks;
    

    public Integer getStockId() 
        return stockId;
    

    public void setStockId(Integer stockId) 
        this.stockId = stockId;
    

    public String getStockCode() 
        return stockCode;
    

    public void setStockCode(String stockCode) 
        this.stockCode = stockCode;
    

    public String getStockName() 
        return stockName;
    

    public void setStockName(String stockName) 
        this.stockName = stockName;
    

    public Set<StockCategoryProductLink> getStockCategoryProductLinks() 
        return stockCategoryProductLinks;
    

    public void setStockCategoryProductLinks(Set<StockCategoryProductLink> stockCategoryProductLinks) 
        this.stockCategoryProductLinks = stockCategoryProductLinks;
    

Product.java

public class Product implements Serializable 
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "PRODUCT_ID", unique = true, nullable = false)
    private Integer productId;

    @Column(name = "PRODUCT_NAME")
    private String productName;

    @Column(name = "PRODUCT_CODE", unique = true, nullable = false, length = 10)
    private String productCode;

    @OneToMany(mappedBy = "linkPk.product", cascade =  CascadeType.PERSIST, CascadeType.MERGE )
    private List<StockCategoryProductLink> userDepartmentRoleLinks;

    public Product(String productName, String productCode) 
        this.productName = productName;
        this.productCode = productCode;
    

    public Integer getProductId() 
        return productId;
    

    public void setProductId(Integer productId) 
        this.productId = productId;
    

    public String getProductName() 
        return productName;
    

    public void setProductName(String productName) 
        this.productName = productName;
    

    public String getProductCode() 
        return productCode;
    

    public void setProductCode(String productCode) 
        this.productCode = productCode;
    

    public List<StockCategoryProductLink> getUserDepartmentRoleLinks() 
        return userDepartmentRoleLinks;
    

    public void setUserDepartmentRoleLinks(List<StockCategoryProductLink> userDepartmentRoleLinks) 
        this.userDepartmentRoleLinks = userDepartmentRoleLinks;
    

Category.java

Entity
@Table(name = "category", catalog = "mkyongdb")
public class Category implements java.io.Serializable 
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "CATEGORY_ID", unique = true, nullable = false)
    private Integer categoryId;

    @Column(name = "NAME", nullable = false, length = 10)
    private String name;

    @Column(name = "[DESC]", nullable = false)
    private String desc;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "linkPk.category")
    private Set<StockCategoryProductLink> stockCategories = new HashSet<>(0);

    public Category() 
    

    public Category(String name, String desc) 
        this.name = name;
        this.desc = desc;
    

    public Category(String name, String desc, Set<StockCategoryProductLink> stockCategories) 
        this.name = name;
        this.desc = desc;
        this.stockCategories = stockCategories;
    

    public Integer getCategoryId() 
        return categoryId;
    

    public void setCategoryId(Integer categoryId) 
        this.categoryId = categoryId;
    

    public String getName() 
        return name;
    

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

    public String getDesc() 
        return desc;
    

    public void setDesc(String desc) 
        this.desc = desc;
    

    public Set<StockCategoryProductLink> getStockCategories() 
        return stockCategories;
    

    public void setStockCategories(Set<StockCategoryProductLink> stockCategories) 
        this.stockCategories = stockCategories;
    


StockCategoryProductLink.java

@Embeddable
public class StockCategoryProductLink implements Serializable 
    private static final long serialVersionUID = 1L;

    @EmbeddedId
    private StockCategoryProductLinkId linkPk = new StockCategoryProductLinkId();

    @Transient
    public Stock getStock() 
        return getLinkPk().getStock();
    

    @Transient
    public Category getCategory() 
        return getLinkPk().getCategory();
    

    @Transient
    public Product getProduct() 
        return getLinkPk().getProduct();
    


    public StockCategoryProductLinkId getLinkPk() 
        return linkPk;
    

    public void setLinkPk(StockCategoryProductLinkId linkPk) 
        this.linkPk = linkPk;
    

StockCategoryProductLinkId.java

@Embeddable
public class StockCategoryProductLinkId 
    @ManyToOne(cascade =  CascadeType.PERSIST, CascadeType.MERGE )
    @JoinColumn(name = "CATEGORY_ID")
    private Category category;

    @ManyToOne(cascade =  CascadeType.PERSIST, CascadeType.MERGE )
    @JoinColumn(name = "STOCK_ID")
    private Stock stock;

    @ManyToOne(cascade =  CascadeType.PERSIST, CascadeType.MERGE )
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;

    public Category getCategory() 
        return category;
    

    public void setCategory(Category category) 
        this.category = category;
    

    public Stock getStock() 
        return stock;
    

    public void setStock(Stock stock) 
        this.stock = stock;
    

    public Product getProduct() 
        return product;
    

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

ManyToManyApplication.java

@SpringBootApplication
public class ManyToManyApplication implements CommandLineRunner

    public static void main(String[] args) 
        SpringApplication.run(ManyToManyApplication.class, args);
    

    @Autowired
    private CategoryRepository categoryRepository;
    @Autowired
    private StockRepository stockRepository;
    @Autowired
    private ProductRepository productRepository;

    @Override
    public void run(String... args) throws Exception 
        saveDataFirstTime();

    


    private void saveDataFirstTime() 
        // Category
        Category category1 = new Category("CONSUMER", "CONSUMER COMPANY");
        categoryRepository.save(category1);

        // Product
        Product product = new Product("Product-1", "AB");
        productRepository.save(product);

        // Stock
        Stock stock = new Stock();
        stock.setStockCode("7052");
        stock.setStockName("PADINI");

        // StockCategoryProductLink
        StockCategoryProductLink link = new StockCategoryProductLink();
        link.getLinkPk().setCategory(category1);
        link.getLinkPk().setProduct(product);
        link.getLinkPk().setStock(stock);

        stock.getStockCategoryProductLinks().add(link);

        stockRepository.save(stock);
    

错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: Use of @OneToMany or @ManyToMany targeting an unmapped class: com.example.entity.Stock.stockCategoryProductLinks[com.example.entity.StockCategoryProductLink]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) [spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at com.example.ManyToManyApplication.main(ManyToManyApplication.java:20) [classes/:na]
Caused by: org.hibernate.AnnotationException: Use of @OneToMany or @ManyToMany targeting an unmapped class: com.example.entity.Stock.stockCategoryProductLinks[com.example.entity.StockCategoryProductLink]
    at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1274) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:811) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:736) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1696) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1664) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:287) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935) ~[hibernate-core-5.3.10.Final.jar:5.3.10.Final]
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774) ~[spring-beans-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    ... 15 common frames omitted

【问题讨论】:

【参考方案1】:

嗯,我知道你说的是什么,但我认为这不是你的意思。你说

实际上我有三个表,Stock、Category 和 Product。 @ManyToMany Stock 和 Category 的关系,以及@ManyToMany Category 和 Product 的关系。

抽象地思考这一点会有所帮助。用陈记法你说的是

但是,这可能不是您的意思。这是有问题的,因为每个StockProduct 关系都需要一个新的Category 实体。因此,如果您有一个 ETF 类别,那么 BobsBestETFs 产品中的每只股票都会复制它,实际上是每一个实例化的关系。

您的意思可能更像是 StockProductCategory 属性的关系,就像这样。

这允许许多产品,每个产品都有许多库存,并且每个产品/库存关系都有一个特定的类别属性。您将遇到的问题是您不希望 Category 成为一个属性,而是一个类别查找表,如下所示:

我认为这就是您正在寻找的。使用复合 ID 实现这应该相当简单,但您展示的示例似乎有些过时或不清楚。最好找到更好的例子。这就是我对最后一个模式进行建模的方式。

@Entity
public class Stock 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

@Entity
@Data
public class Product 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

@Entity
public class Category 
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

@Entity
@Data
public class StockProduct 
    @EmbeddedId
    private StockProductPk id;

    @ManyToOne
    @MapsId("productId")
    private Product product;
    @ManyToOne
    @MapsId("stockId")
    private Stock stock;

    @ManyToOne
    private Category category;

@Embeddable
@Data
public class StockProductPk implements Serializable 
    private static final long serialVersionUID = 1L;
    private Long stockId;
    private Long productId;

并作为使用它的示例:

private void create() 
    Category catEtf = new Category();
    categoryRepo.save(catEtf);
    Stock s1 = new Stock();
    stockRepo.save(s1);
    Product bobEtfs = new Product();
    productRepo.save(bobEtfs);

    // create a relationship
    StockProduct bs1 = new StockProduct();
    bs1.setId(new StockProductPk());
    bs1.setProduct(bobEtfs);
    bs1.setStock(s1);
    bs1.setCategory(catEtf);
    stockProductRepo.save(bs1); 

private void read() 
    StockProduct sp1 = new StockProduct();
    Product p1 = new Product();
    p1.setId(1L);
    sp1.setProduct(p1);
    List<StockProduct> bobEtfs = stockProductRepo.findAll(Example.of(sp1, ExampleMatcher.matching()));
    System.out.println(bobEtfs);

【讨论】:

谢谢!完美,我们如何给约束命名alter table stock_product_category add constraint FK2vtbnakkg59skleh35oebx3lk foreign key (category_category_id) references category (category_id) 我认为这是ForeignKey 注释。 谢谢,如果我不想使用复合键,有什么办法可以通过约束来申请?在这里提出新问题:***.com/questions/57352521/… @K. Nicholas - 我不想使用 CategoryId,而是想使用 CategoryName,你能告诉我如何调整它吗?

以上是关于Spring Data JPA - 为三个表创建 @Composite 键的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring DATA JPA 创建自定义查询?

spring-data-jpa 3-way-intersection 表

JPA Hibernate jpa spring data jpa

Spring Data JPA 原生查询结果实体

Spring Data JPA 多个实体类表联合视图查询

Spring Data JPA 常用注解 @Query@NamedQuery