web-dev-qa-db-fra.com

JPA de données de printemps et entité détachée en veille prolongée passés pour persister sur la relation ManyToMany

J'essaie de faire persister un objet qui a une relation plusieurs-à-plusieurs avec d'autres objets déjà persistants.

Voici mon objet persistant (ils sont déjà persistants dans la base de données, qui est un MySql): -

Produit

@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;
    }
}

Voici mon objet sans persistance, que j'essaie de créer

@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;
    }
}

Il s'agit de ma classe ReservationService, qui reçoit un tableau de noms de produits, regarde les produits en utilisant le nom et les met dans l'objet de réservation.

@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
    }
}

Voici mon ProductDAO interface:

public interface ProductDAO extends JpaRepository<Product, Integer> {

    public Product findByProductName(String productName);
}

Voici mon fichier de configuration de printemps:

@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;
    }
}

Voici la trace complète de la pile:

GRAVE: Servlet.service () pour servlet [répartiteur] dans le contexte avec le chemin [/ web] a levé l'exception [Échec du traitement de la demande; l'exception imbriquée est org.springframework.dao.InvalidDataAccessApiUsageException: entité détachée transmise pour persister: com.reservation.service.domain.Product; l'exception imbriquée est org.hibernate.PersistentObjectException: entité détachée transmise à persister: com.reservation.service.domain.Product] avec la cause racine org.hibernate.PersistentObjectException: entité détachée transmise à persister: com.reservation.service.domain.Product à org.hibernate.event.internal.DefaultPersistEventListener.onPersist (DefaultPersistEventListener.Java:141)

26
nspessot

J'ai eu le même problème et l'ai résolu en supprimant le cascade = CascadeType.PERSIST.

Dans votre cas, vous utilisez CascadeType.ALL, Ce qui équivaut à utiliser également PERSIST, selon la documentation:

Définit l'ensemble des opérations en cascade qui sont propagées à l'entité associée. La valeur cascade = ALL est équivalente à cascade = {PERSIST, MERGE, REMOVE, REFRESH, DETACH}.

Cela signifie que lorsque vous essayez d'enregistrer la réservation sur reservationDAO.save(reservation), il essaiera également de conserver l'objet Product associé. Mais cet objet n'est pas attaché à cette session. Donc, l'erreur se produit.

49
Renato Borges

L'exception survient lorsque l'hibernation tente de conserver les produits associés lorsque vous enregistrez la réservation. La persistance des produits n'est un succès que s'ils n'ont pas d'identifiant car l'identifiant du produit est annoté

@GeneratedValue(strategy=GenerationType.AUTO)

Mais vous avez obtenu des produits du référentiel et les identifiants ne sont pas nuls.

Il existe 2 options pour résoudre votre problème:

  1. supprimer (cascade = CascadeType.ALL) sur les produits de réservation
  2. ou supprimez @GeneratedValue(strategy=GenerationType.AUTO) sur l'id du produit
23
My Pham

Vous devez vous assurer que les deux côtés de la relation sont correctement conservés dans votre code.

Mettez à jour la réservation comme ci-dessous, puis ajoutez les méthodes correspondantes au produit.

@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);
        }
    }
}

Et dans les produits:

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);
    }
}

Vous devriez maintenant pouvoir appeler save sur le produit ou la réservation et tout devrait fonctionner comme prévu.

2
Alan Hay

Supprimez @ CascadeType.ALL dans la relation @ManytoMany, cela a fonctionné pour moi.

1
karthik yadav

entityManager.merge() est une bonne option. Il fusionnera les objets détachés dans la session. Pas besoin de changer de cascadeType.

1
Pragyan Chand

J'ai le sentiment que vos annotations sont légèrement incorrectes. Pas beaucoup cependant, jetez un œil à exemple 7.24 ici et voyez si cela correspond à vos annotations. Ignorez cependant le type de données Collection, car vous ne devriez pas avoir de problèmes en utilisant un Set. Je remarque que vous manquez un cascade=CascadeType.ALL sur votre collection Product, mais je ne peux pas déterminer si c'est le problème ou non.

Ce que la véritable exception dit, c'est que vos objets Product n'ont pas été réellement enregistrés lorsqu'ils tentent d'enregistrer la collection de Product. C'est pourquoi je pense que c'est un problème avec vos annotations.

Essayez-le et faites-moi savoir si vous arrivez quelque part.

0
JamesENL