I am trying to persist an object that has a many-to-many relationship with other objects already persisted.
Here is my persisted object (they are already persisted in the db, which is a MySql):-
Product
@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; } }
Here is my no persisted object, which I am trying to create
@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; } }
This is my ReservationService
class, which receives an array of products names, look the products using the name and put them into the reservation object.
@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 } }
Here is my ProductDAO
interface:
public interface ProductDAO extends JpaRepository<Product, Integer> { public Product findByProductName(String productName); }
This is my spring config file:
@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; } }
Here is the full stack trace:
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)
The solution is simple, just use the CascadeType. MERGE instead of CascadeType. PERSIST or CascadeType. ALL .
A detached entity (a.k.a. a detached object) is an object that has the same ID as an entity in the persistence store but that is no longer part of a persistence context (the scope of an EntityManager session).
orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.
A detached entity is just an ordinary entity POJO whose identity value corresponds to a database row. The difference from a managed entity is that it's not tracked anymore by any persistence context. An entity can become detached when the Session used to load it was closed, or when we call Session.
The contract for persist (see section 3.2.1 of the JPA 1.0 spec) explicitly states that an EntityExistsException is thrown by the persist method when the object passed in is a detached entity. Or any other PersistenceException when the persistence context is flushed or the transaction is committed.
The child entity was immediately becoming detached because there was no active Hibernate Session context. Providing a Spring (Data JPA) transaction ensures a Hibernate Session is present.
ps: the @Id annotation is the one that hibernate uses to identify the access type. The exception is : detached entity passed to persist Why improving consistency makes that it works? Ok, consistency was repaired but object is still detached. @Sam, thanks a lot for your explanation. But, I don't understand still.
In a JPA many to many relationship, if cascade type has been set at CascadeType.PERSIST (or CascadeType.ALL, which includes CascadeType.PERSIST), then while saving the parent and updating it with references of the child, it will try to save the child again.
I had the same problem and solved it by removing the cascade = CascadeType.PERSIST
.
In your case you use CascadeType.ALL
, which is equivalent to also using the PERSIST, according to the documentation:
Defines the set of cascadable operations that are propagated to the associated entity. The value cascade=ALL is equivalent to cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}.
It means when you try to save the reservation on reservationDAO.save(reservation)
it will also try to persist the associated Product object. But this object is not attached to this session. So the error occur.
The exception comes as hibernate trying to persist associated products when you save reservation. Persisting the products is only success if they have no id because id of Product is annotated
@GeneratedValue(strategy=GenerationType.AUTO)
But you got products from repository and ids are not null.
There 2 options to resolve your issue:
(cascade = CascadeType.ALL)
on products of Reservation@GeneratedValue(strategy=GenerationType.AUTO)
on id of ProductIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With