Данные Spring JPA и отсоединенная сущность в спящем состоянии передаются для сохранения во взаимосвязи ManyToMany
Я пытаюсь сохранить объект, который имеет отношение многие ко многим с другими объектами, которые уже сохранились.
Вот мой постоянный объект (они уже сохранены в БД, которая является MySql): -
Товар
@Entity
@Table(name="PRODUCT")
public class Product {
private int productId;
private String productName;
private Set<Reservation> reservations = new HashSet<Reservation>(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<Reservation> getReservations() {
return reservations;
}
public void setReservations(Set<Reservation> reservations) {
this.reservations = reservations;
}
}
Вот мой не сохранившийся объект, который я пытаюсь создать
@Entity
@Table(name = "RESERVATION")
public class Reservation {
private int reservationId;
private Set<Product> products = new HashSet<Product>(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<Product> getProducts() {
return products;
}
public void setProducts(Set<Product> 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<Product> products = new HashSet<Product>();
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<Product, Integer> {
public Product findByProductName(String productName);
}
Это мой весенний конфигурационный файл:
@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() для сервлета [dispatcher] в контексте с путем [/web] вызвала исключение [Ошибка обработки запроса; вложенным исключением является org.springframework.dao.InvalidDataAccessApiUsageException: отдельная сущность передана в постоянное хранилище: com.reservation.service.domain.Product; вложенным исключением является org.hibernate.PersistentObjectException: отдельная сущность передана в persist: com.reservation.service.domain.Product] с первопричиной org.hibernate.PersistentObjectException: отдельная сущность передана в persist: com.reservation.service.domain.Product at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:141)
Ответы
Ответ 1
У меня была такая же проблема и она была решена, удалив cascade = CascadeType.PERSIST
.
В вашем случае вы используете CascadeType.ALL
, что эквивалентно также использованию PERSIST, в соответствии с документацией:
Определяет набор каскадируемых операций, которые распространяются на связанный объект. Значение cascade = ALL равнозначно каскаду = {PERSIST, MERGE, REMOVE, REFRESH, DETACH}.
Это означает, что при попытке сохранить резервирование на reservationDAO.save(reservation)
он также попытается сохранить связанный объект Product. Но этот объект не привязан к этому сеансу. Таким образом, ошибка возникает.
Ответ 2
Исключением является спящий режим, пытающийся сохранить связанные продукты при сохранении бронирования. Сохранение продуктов является успешным, только если у них нет идентификатора, потому что идентификатор продукта аннотирован
@GeneratedValue(strategy=GenerationType.AUTO)
Но вы получили продукты из хранилища и идентификаторы не равны нулю.
Есть 2 варианта решения вашей проблемы:
- удалить
(cascade = CascadeType.ALL)
по продуктам бронирования - или удалите
@GeneratedValue(strategy=GenerationType.AUTO)
для идентификатора продукта
Ответ 3
Вам необходимо убедиться, что обе стороны отношений правильно сохранены в вашем коде.
Обновить резервную копию, как показано ниже, а затем добавить соответствующие методы к продукту.
@Entity
@Table(name = "RESERVATION")
public class Reservation {
private int reservationId;
private Set<Product> products = new HashSet<Product>(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<Product> 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);
}
}
Теперь вы должны иметь возможность называть сохранение либо Продуктом, либо Резервированием, и все должно работать как ожидалось.
Ответ 4
entityManager.merge()
- хороший вариант. Он объединит отдельные объекты в сеансе. Не нужно изменять какой-либо cascadeType.
Ответ 5
У меня такое ощущение, что ваши аннотации немного неверны. Не очень много, посмотрите на Пример 7.24 здесь и посмотрите, соответствует ли он вашим аннотациям. Игнорируйте тип данных Collection
, хотя у вас не должно быть проблем с использованием Set
. Я замечаю, что вам не хватает cascade=CascadeType.ALL
в вашей коллекции Product
, но я не могу это решить, если это проблема или нет.
Фактическое исключение говорит о том, что ваши объекты Product
на самом деле не были сохранены при попытке сохранить коллекцию Product
. Вот почему я думаю, что это проблема с вашими аннотациями.
Попробуйте и дайте мне знать, если вы где угодно.
Ответ 6
Удалите @CascadeType.ALL в отношении @ManytoMany, это сработало для меня.