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)
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.
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:
(cascade = CascadeType.ALL)
sur les produits de réservation@GeneratedValue(strategy=GenerationType.AUTO)
sur l'id du produitVous 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.
Supprimez @ CascadeType.ALL dans la relation @ManytoMany, cela a fonctionné pour moi.
entityManager.merge()
est une bonne option. Il fusionnera les objets détachés dans la session. Pas besoin de changer de cascadeType.
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.