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 的关系。
抽象地思考这一点会有所帮助。用陈记法你说的是
但是,这可能不是您的意思。这是有问题的,因为每个Stock
和Product
关系都需要一个新的Category
实体。因此,如果您有一个 ETF 类别,那么 BobsBestETFs 产品中的每只股票都会复制它,实际上是每一个实例化的关系。
您的意思可能更像是 Stock
和 Product
与 Category
属性的关系,就像这样。
这允许许多产品,每个产品都有许多库存,并且每个产品/库存关系都有一个特定的类别属性。您将遇到的问题是您不希望 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 3-way-intersection 表