Данные 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 варианта решения вашей проблемы:

  1. удалить (cascade = CascadeType.ALL) по продуктам бронирования
  2. или удалите @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, это сработало для меня.