I am trying to understand the principles of ManyToMany relations in Hibernate. I've created a small test project and the question is:
I want "Managers" to be saving in db when the "User" is saving. So Manager entity is dependent on User.
When I am saving user1 - everything is fine. I am getting user1 and manager1 and manager3 saved to db. But on the next row where I am trying to save user2 to db I am getting an Exception:
Converting org.hibernate.PersistentObjectException to JPA PersistenceException : detached entity passed to persist: ua.testing.entities.Manager
I think the problem is because I am trying to save user2 which contains manager1, which was saved in db in previous row.
But how can I avoid this problem and make everything work?
User entity:
package ua.testing.entities;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
private List<Manager> managerList = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Manager> getManagerList() {
return managerList;
}
public void setManagerList(List<Manager> managerList) {
this.managerList = managerList;
}
}
Manager entity:
package ua.testing.entities;
import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity
#Table(name = "manager")
public class Manager {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(mappedBy = "managerList")
private List<User> userList = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
}
Main method:
package ua.testing;
import ua.testing.dao.UserDao;
import ua.testing.entities.*;
public class App
{
public static void main( String[] args )
{
User user1 = new User();
User user2 = new User();
User user3 = new User();
Manager manager1 = new Manager();
Manager manager2 = new Manager();
Manager manager3 = new Manager();
manager1.getUserList().add(user1);
manager1.getUserList().add(user2);
manager2.getUserList().add(user1);
manager2.getUserList().add(user3);
manager3.getUserList().add(user2);
manager3.getUserList().add(user3);
user1.getManagerList().add(manager1);
user1.getManagerList().add(manager3);
user2.getManagerList().add(manager1);
user2.getManagerList().add(manager2);
user3.getManagerList().add(manager2);
user3.getManagerList().add(manager3);
UserDao userDao = new UserDao();
userDao.persist(user1);
userDao.persist(user2); // EXCEPTION HERE
}
}
Exception I am getting:
Exception in thread "main" jakarta.persistence.PersistenceException: Converting `org.hibernate.PersistentObjectException` to JPA `PersistenceException` : detached entity passed to persist: ua.testing.entities.Manager
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:165)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:175)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:182)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:783)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:725)
at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:299)
at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:289)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:511)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:432)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
at org.hibernate.engine.internal.Cascade.cascadeCollectionElements(Cascade.java:545)
at org.hibernate.engine.internal.Cascade.cascadeCollection(Cascade.java:475)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:435)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:218)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:151)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:474)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:298)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:122)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:184)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:129)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:53)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:735)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:719)
at ua.testing.dao.UserDao.persist(UserDao.java:13)
at ua.testing.App.main(App.java:34)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: ua.testing.entities.Manager
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:121)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:118)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)
... 23 more
Related
When trying to send a request, with the same "flower_id", to Postman, returns 500 with message:
"could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement."
At the same time, it does not matter if the same ids are in the same request or in different ones with different users, if one flower has already been added earlier, it is no longer possible to add it to another user.
Entity Order:
import javax.persistence.*;
import java.time.LocalDate;
import java.util.List;
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDate orderCreateDate;
private LocalDate orderCompleteDate;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
#ManyToMany
private List<Flower> flower;
private Integer price;
public Order() {
}
public Order(LocalDate orderCreateDate, LocalDate orderCompleteDate, User user, List<Flower> flower) {
this.orderCreateDate = orderCreateDate;
this.orderCompleteDate = orderCompleteDate;
this.user = user;
this.flower = flower;
}
//Getters and setters
}
Entity Flower:
import javax.persistence.*;
#Entity
#Table(name = "flowers")
public class Flower {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer price;
public Flower() {
}
public Flower (String name, Integer price) {
this.name = name;
this.price = price;
}
//Getters and Setters
}
OrderService:
import com.learning.flowershop.Entity.Order;
import com.learning.flowershop.Repositories.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
#Service
public class OrderService {
private final OrderRepository orderRepository;
#Autowired
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public List<Order> getAllOrdersByUserId(Long userId) {
return orderRepository.findAllByUserId(userId);
}
#Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
}
}
Did you check the constraints in your database? The 500 error indicates an internal server error. It seems like there might be a unique constraint in your relation table which causes an SQL exception. If this exception is not properly caught it will get rethrown as an internal server error.
I still don't fully understand why this is the case, but I still want to leave a solution to my question.
It was only worth adding a save method for User
public User saveUser(User user) {
return userRepository.save(user);
}
Which is strange, because before that all my users were quietly saved.
I Have two models, One is used to store user details and with that Id, I'm trying to save the user location in another table, which uses the same id of the user as the primary key.
User Model
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true)
private String username;
#Column(unique=true)
private String email;
private String password;
private String Role;
#OneToOne(mappedBy = "user")
private UserLocation location;
//...constructors, getters and setters
}
UserLocation Model
#Entity
public class UserLocation {
#Id
private Long id;
#OneToOne(cascade = CascadeType.MERGE)
#JoinColumn(name="id")
#MapsId
private User user;
private Point location;
//...constructors, getters and setters
}
My Spring Boot Application class where I try to insert records to both the table
#SpringBootApplication
public class ContainmentZoneAlertApp implements CommandLineRunner {
#Autowired
private UserlocationRepository userLocRepo;
#Autowired
private UserRepository userRepo;
public static void main(String[] args) {
SpringApplication.run(ContainmentZoneAlertApp.class, args);
}
#Override
public void run(String... args) throws Exception {
UserLocation userLocation = new UserLocation();
Geometry geometry = GeometryUtil.wktToGeometry(String.format("POINT (13.0827 80.2707)"));
//GeometryUtil is a utility class
User user = new User(null,"user 3","user3#gmail.com","hello123","user");
user = userRepo.save(user);
userLocation.setUser(user);
userLocation.setLocation((Point)geometry);
userLocRepo.save(userLocation);
}
}
Im getting
caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.saravanan.models.User
Why am I getting this error? and what does persist mean? & I'm using MySQL database
You have to make sure that both save operations are in the same transaction otherwise the first entity is detached an this leads to this exception.
So simply add #Transactional to the run method:
#Transactional
#Override
public void run(String... args) throws Exception {
I have two entities which are linked via a OneToMany relationship:
#Entity
#Table(name="bookcase")
public class BookCase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
/*
https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/
*/
#OneToMany(mappedBy = "bookCase", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Bookshelf> bookShelves = new HashSet<>();
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Set<Bookshelf> getBookShelves() { return bookShelves; }
public void setBookShelves(Set<Bookshelf> bookShelves) { this.bookShelves = bookShelves; }
}
#Entity
#Table(name="bookshelf")
public class Bookshelf {
private static final Logger log = LoggerFactory.getLogger(Bookshelf.class);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Transient
#Getter #Setter private Long oldId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bookcase_id")
private BookCase bookCase;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public BookCase getBookCase() { return bookCase; }
public void setBookCase(BookCase bookCase) {
this.bookCase = bookCase;
bookCase.getBookShelves().add(this);
}
#Transient
#Setter private OldIdListener oldIdListener;
/*
When the id is saved, listening DTOs can update their ids
*/
#PostPersist
public void triggerOldId() {
log.info("Postpersist triggered for {}", id);
if (oldIdListener != null) {
oldIdListener.updateId(oldId, id);
}
}
}
public interface OldIdListener {
void updateId(long oldId, long newId);
}
The following test fails:
#Test
public void testThatCascadingListenerIsTriggered() {
var mock = mock(OldIdListener.class);
var mock2 = mock(OldIdListener.class);
var mock3 = mock(OldIdListener.class);
var bookcase = new BookCase();
var shelf1 = new Bookshelf();
shelf1.setOldId(-5L);
shelf1.setBookCase(bookcase);
shelf1.setOldIdListener(mock);
var shelf2 = new Bookshelf();
shelf2.setOldId(-6L);
shelf2.setBookCase(bookcase);
shelf2.setOldIdListener(mock2);
var saved = bookCaseRepository.save(bookcase);
verify(mock).updateId(eq(-5L), anyLong());
verify(mock2).updateId(eq(-6L), anyLong());
var savedBookCase = bookCaseRepository.findById(saved.getId()).get();
assertThat(savedBookCase.getBookShelves()).hasSize(2);
var shelf3 = new Bookshelf();
shelf3.setOldId(-10L);
shelf3.setBookCase(savedBookCase);
shelf3.setOldIdListener(mock3);
savedBookCase.getBookShelves().add(shelf3);
bookCaseRepository.save(savedBookCase);
verify(mock3).updateId(eq(-10L), anyLong());
}
mock3 is never called.
When debugging the code, I can see that the transient fields oldId and oldIdListener are set to null when the #PostPersist method is called on object shelf3, not on shelf1 and 2.
I think this is because I am modifying the Set object; but the object is correctly persisted, it just loses all transient fields. This does not happen when the entire tree is persisted for the first time.
Is this the wrong way to insert a new element to a OneToMany set or where is the error here?
I'm using Spring Boot 2.1.
Thanks!
The field which annotation with #Transient will not persist to the database, so if you want it to persist, you must remove #Transient.
I am using JPA createquery API to fetch the data.
Here is my query data
#PersistenceContext
EntityManager entityManager;
#Override
public List<String> fetchAllReleaseNumbers() {
Query query = entityManager.createQuery("SELECT release FROM ReleaseModel", String.class);
return query.getResultList();
}
and here is my pojo class.
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "dbname.tablenamefromDB")
public class ReleaseModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dbcolumnname", unique = true, nullable = false)
private String release;
#Column(name = "dbcolumnname")
private String releaseDesc;
#Column(name = "dbcolumnname")
private Integer releaseStatus;
#Column(name = "dbcolumnname")
private Integer releaseMode;
public String getRelease() {
return release;
}
public void setRelease(String release) {
this.release = release;
}
public String getReleaseDesc() {
return releaseDesc;
}
public void setReleaseDesc(String releaseDesc) {
this.releaseDesc = releaseDesc;
}
public Integer getReleaseStatus() {
return releaseStatus;
}
public void setReleaseStatus(Integer releaseStatus) {
this.releaseStatus = releaseStatus;
}
public Integer getReleaseMode() {
return releaseMode;
}
public void setReleaseMode(Integer releaseMode) {
this.releaseMode = releaseMode;
}
}
Though the table exists in db its throwing not exist.Any ideas where I made mistake.
I tried whether any aliases can be given to the table name.
I am using pojo class name only for createQuery.
TIA.
You should specify a schema name by this way
#Table(schema = "dbname", name = "tablenamefromDB")
You have an incorrect mapping:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "dbcolumnname", unique = true, nullable = false)
private String release;
I think String can't be auto generated.
Also all your columns have dbcolumnname name.
The issue was that the schema was not specified in the entity class or the user did not login using proxy. If the user login using a proxy access i.e. userName[schemaName] they do not need to specify schema in the entity class. But if the user login using just the userName, they need to specify the schema in the entity. This is to specify where the table can be found in the database.
I have a many-to-many relationship between Stores and Products, represented by the following code (mostly based in this answer):
#Entity
#Table(name = "Store")
public class Store {
private long idStore;
// ...
private Collection<StoreHasProduct> storeHasProducts = new ArrayList<>();
#OneToMany(mappedBy = "store", cascade = {CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER)
public Collection<StoreHasProduct> getStoreHasProducts() {
return storeHasProducts;
}
public void setStoreHasProducts(Collection<StoreHasProduct> storeHasProducts) {
this.storeHasProducts = storeHasProducts;
}
}
#Entity
#Table(name="Product")
public class Product {
private long idProduct;
// ...
private Collection<StoreHasProduct> storeHasProducts = new ArrayList<>();
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
public Collection<StoreHasProduct> getStoreHasProducts() {
return storeHasProducts;
}
public void setStoreHasProducts(Collection<StoreHasProduct> storeHasProducts) {
this.storeHasProducts = storeHasProducts;
}
}
#Entity
#Table(name = "Store_has_Product")
#IdClass(StoreHasProductPK.class)
public class StoreHasProduct implements java.io.Serializable {
#Id
#ManyToOne
#JoinColumn(name = "Store_idStore",updatable = true)
private Store store;
#Id
#ManyToOne
#JoinColumn(name = "Product_idProduct", updatable = true)
private Product product;
}
public class StoreHasProductPK implements java.io.Serializable {
private Long store;
private Long product;
}
All basic insertion are working fine. However, when I try to add new Products to a existing Store I'm having a PersistentObjectException: detached entity passed to persist exception. This happens, for example, in the following test:
#Test
public void testAssignProductToAnExistingStore() throws Exception {
//Create a store
Store store = getStore();
//Create and save a product
Product product = getProduct();
StoreHasProduct storeHasProduct = getStoreHasProduct(store, product);
store.getStoreHasProducts().add(storeHasProduct);
storeRepository.save(store);
//Create and save a second product
Product productTwo = getProduct();
Store s = storeRepository.findOne(store.getIdStore());
product.getStoreHasProducts().add(getStoreHasProduct(s, productTwo));
productRepository.save(product);
// s.getStoreHasProducts().add(getStoreHasProduct(s, productTwo));
// storeRepository.save(s);
}
If I try to persist the product, I get detached entity passed to persist: Product. If instead I try to persist the store (commented code) I get the same exception but for store.
What should I do? I'm trying to use the CASCADE.DETACH, but I'm not sure if this is the appropriate path to follow.
Thanks
it's all about configuring Entity manager and/or Transaction manager
take a look
How to save a new entity that refers existing entity in Spring JPA?