问题
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