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

北城以北 提交于 2019-11-29 15:53:59

问题


I am extending my question from here: Define CompositeKey with three tables using JPA/Hibernate?. In this example, I am looking to create Composite key to create unique combination of PRODUCT_ID, CATEGORY_ID, STOCK_ID.

I developed below code, but not sure on how to save the records into DB.

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);
    }
}

Error:

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:


Well, I know what you said, but I don't think it's what you meant. You said

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

It helps to think about this abstractly. In Chen notation what you said is

However this is probably not what you meant. This is problematic because you will need a new Category entity for every Stock and Product relationship. So, if you have a category of ETF then it will be duplicated for every stock in BobsBestETFs product, in fact for every instantiated relation.

What you meant is probably more along the lines of a Stock and Product relationship with a Category attribute, like so.

This this allows for many products each with many stocks and a specific category attribute for each product/stock relation. The issue you will have with this is that you don't want Category to be an attribute but rather a lookup table of categories, like so:

And I think this is what you are looking for. This should be fairly simple to implement with a composite ID but the examples you are showing seem to be somewhat out of date or unclear. Better to find better examples. This is how I would model the last schema.

@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;
}

And as an example to use it:

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);
}


来源:https://stackoverflow.com/questions/57311851/spring-data-jpa-create-composite-key-for-the-three-tables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!