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

Spring Data JPA - create @Composite key for the three tables

我从这里扩展我的问题: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[=17=](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.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

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

Actually I've three tables, Stock, Category and Product. @ManyToMany relationship between Stock and Category, as well as @ManyToMany relationship between Category and Product.

抽象地思考这个问题会有所帮助。用陈法你说的是

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

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

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

我想这就是您要找的。使用复合 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);
}