spring数据jpa和hibernate分离的实体传递给ManyToMany关系持久化

我试图坚持一个与已经存在的其他对象有很多关系的对象。

这是我的持久化对象(它们已经存在于db中,这是一个mysql):产品

@Entity @Table(name="PRODUCT") public class Product { private int productId; private String productName; private Set reservations = new HashSet(0); @Id @GeneratedValue(strategy=GenerationType.AUTO) public int getProductId() { return productId; } public void setProductId(int productId) { this.productId = productId; } @Column(nullable = false) public String getProduct() { return product; } public void setProduct(String product) { this.product = product; } @ManyToMany(fetch = FetchType.LAZY, mappedBy = "products") public Set getReservations() { return reservations; } public void setReservations(Set reservations) { this.reservations = reservations; } } 

这是我没有持久化的对象,我正在尝试创建它

 @Entity @Table(name = "RESERVATION") public class Reservation { private int reservationId; private Set products = new HashSet(0); @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getReservationId() { return reservationId; } public void setReservationId(int reservationId) { this.reservationId = reservationId; } @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "product_reservation", joinColumns = { @JoinColumn(name = "reservationId", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "productId", nullable = false, updatable = false) }) public Set getProducts() { return products; } public void setProducts(Set products) { this.products = products; } } 

这是我的ReservationService类,它接收一系列产品名称,使用名称查看产品并将它们放入预留对象中。

 @Service public class ReservationServiceImpl implements ReservationService { @Autowired private ProductDAO productDAO; @Autowired private ReservationDAO reservationDAO; @Transactional public void createReservation(String[] productNames) { Set products = new HashSet(); for (String productName : productNames) { Product pi = productDAO.findByProductName(productName); products.add(pi); } Reservation reservation = new Reservation(); reservation.setProducts(products); reservationDAO.save(reservation); ---> Here I am getting detached entity passed to persist } } 

这是我的ProductDAO界面:

 public interface ProductDAO extends JpaRepository { public Product findByProductName(String productName); } 

这是我的spring配置文件:

 @Configuration @PropertySource(value = { "classpath:base.properties" }) @EnableTransactionManagement @EnableJpaRepositories(basePackages = "com.reservation.dao") public class RepositoryConfig { @Autowired private Environment env; @Bean public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } @Bean public PlatformTransactionManager transactionManager() { EntityManagerFactory factory = entityManagerFactory().getObject(); return new JpaTransactionManager(factory); } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); vendorAdapter.setGenerateDdl(Boolean.valueOf(env .getProperty("hibernate.generate.ddl"))); vendorAdapter.setShowSql(Boolean.valueOf(env .getProperty("hibernate.show_sql"))); Properties jpaProperties = new Properties(); jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); jpaProperties.put("hibernate.dialect", env.getProperty("hibernate.dialect")); LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource()); factory.setJpaVendorAdapter(vendorAdapter); factory.setPackagesToScan("com.reservation.service.domain"); factory.setJpaProperties(jpaProperties); factory.afterPropertiesSet(); factory.setLoadTimeWeaver(new InstrumentationLoadTimeWeaver()); return factory; } @Bean public HibernateExceptionTranslator hibernateExceptionTranslator() { return new HibernateExceptionTranslator(); } @Bean public DataSource dataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName")); dataSource.setUrl(env.getProperty("jdbc.url")); dataSource.setUsername(env.getProperty("jdbc.username")); dataSource.setPassword(env.getProperty("jdbc.password")); return dataSource; } } 

这是完整的堆栈跟踪:

 SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/web] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.reservation.service.domain.Product; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.reservation.service.domain.Product] with root cause org.hibernate.PersistentObjectException: detached entity passed to persist: com.reservation.service.domain.Product at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141) 

我遇到了同样的问题,并通过删除cascade = CascadeType.PERSIST解决了这个问题。

在您的情况下,您使用CascadeType.ALL ,这相当于使用PERSIST,根据文档:

定义传播到关联实体的可级联操作集。 值cascade = ALL等于cascade = {PERSIST,MERGE,REMOVE,REFRESH,DETACH}。

这意味着当您尝试在reservationDAO.save(reservation)上保存预订时,它还会尝试保留关联的Product对象。 但是此对象未附加到此会话。 所以错误发生了。

hibernate试图在保存预订时保留相关产品,这是个例外。 坚持产品只有在没有id的情况下才会成功,因为产品的ID是带注释的@GeneratedValue(strategy = GenerationType.AUTO)

但是你从存储库得到的产品和ids不是空的。

有2个选项可以解决您的问题:

  1. 在Reservation的产品上删除(cascade = CascadeType.ALL)
  2. 或删除Product的id上的@GeneratedValue(strategy = GenerationType.AUTO)

您需要确保在代码中正确维护关系的两端。

如下更新预订,然后将相应的方法添加到产品。

 @Entity @Table(name = "RESERVATION") public class Reservation { private int reservationId; private Set products = new HashSet(0); @Id @GeneratedValue(strategy = GenerationType.AUTO) public int getReservationId() { return reservationId; } public void setReservationId(int reservationId) { this.reservationId = reservationId; } @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "product_reservation", joinColumns = { @JoinColumn(name = "reservationId", nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "productId", nullable = false, updatable = false) }) public Set getProducts() { //force clients through our add and remove methods return Collections.unmodifiableSet(products); } public void addProduct(Product product){ //avoid circular calls : assumes equals and hashcode implemented if(! products.contains(product){ products.add(product); //add method to Product : sets 'other side' of association product.addReservation(this); } } public void removeProduct(Product product){ //avoid circular calls: assumes equals and hashcode implemented: if(product.contains(product){ products.remove(product); //add method to Product: set 'other side' of association: product.removeReservation(this); } } } 

在产品中:

 public void addReservation(Reservation reservation){ //assumes equals and hashcode implemented: avoid circular calls if(! reservations.contains(reservation){ reservations.add(reservation); //add method to Product : sets 'other side' of association reservation.addProduct(this); } } public void removeReservation(Reservation reservation){ //assumes equals and hashcode implemented: avoid circular calls if(! reservations.contains(reservation){ reservations.remove(reservation); //add method to Product : sets 'other side' of association reservation.reomveProduct(this); } } 

现在您应该能够在产品或预订上调用保存,一切都应该按预期工作。

我觉得你的注释有些不正确。 尽管如此,请查看示例7.24 ,看看它是否与您的注释相匹配。 但是忽略Collection数据类型,因为使用Set不应该有任何问题。 我注意到你在Product系列上缺少cascade=CascadeType.ALL ,但是如果这是问题我就无法解决。

实际的例外是,当您尝试保存Product的集合时,您的Product对象实际上没有被保存。 这就是我认为你的注释有问题的原因。

尝试一下,如果你到处都可以告诉我。

entityManager.merge()是个不错的选择。 它将合并会话中的分离对象。 无需更改任何cascadeType。