PersistentObjectException: detached entity passed to persist [JpaRepository] - java

I'm using Spring Data JPA with JpaRepository and I couldn't find an answer to why my entity is detached and I can't save with children entity.
I want to save Recipe but first I need to save Ingredient in another service. Recipe and Ingredient is connected many to many relations by RecipeIngredients object.
#Transactional
public RecipeDto updateRecipe(UserPrincipal userPrincipal, String recipeName, RecipeDto recipeDto) {
Recipe recipeToEdit = recipeRepository.findByName(recipeName).orElseThrow(EntityNotFoundException::new);
recipeToEdit.setIngredients(recipeIngredientsService.refillRecipeIngredientsList(recipeDto.getIngredients(), recipeToEdit));
}
#Entity
public class RecipeIngredients {
#EmbeddedId
private RecipeIngredientsId recipeIngredientsId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("recipeId")
#ToString.Exclude
private Recipe recipe;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE})
#MapsId("ingredientId")
#ToString.Exclude
private Ingredient ingredient;
}
#Entity
#Getter
#Setter
public class Ingredient {
#Id
#Column(name = "id", updatable = false)
private Long id;
#OneToMany(mappedBy = "ingredient", cascade = CascadeType.ALL, orphanRemoval = true)
#ToString.Exclude
private List<RecipeIngredients> recipeIngredients;
public void addRecipeIngredient(RecipeIngredients recipeIngredient) {
if(recipeIngredients == null) {
recipeIngredients = new ArrayList<>();
}
recipeIngredients.add(recipeIngredient);
recipeIngredient.setIngredient(this);
}
}
Before I want to save Ingredient entity I want to check there is exist so I pull it from external service then map It to DTO object. If there is no Ingredient I want to save it and get his object, which will be connected children RecipeIngredients and this is connected to Recipe so when I'm saving recipe it should save me RecipeIngredient with it of Ingredient.
public void refillRecipeIngredientsList(List<RecipeIngredientsDto> recipeIngredientsDtos, Recipe recipe) {
removeOldIngredientsIfExist(recipe);
if (recipeIngredientsDtos != null) {
for (RecipeIngredientsDto recipeIngredientsDto : recipeIngredientsDtos) {
IngredientDto ingredient = pullSavedIngredient(recipeIngredientsDto);
RecipeIngredients recipeIngredient = this.recipeIngredientsDtoToEntityMapper.recipeIngredientsToEntity(recipeIngredientsDto, recipe, ingredientDto);
recipe.addIngredient(recipeIngredient);
ingredient.addRecipeIngredient(recipeIngredient);
}
}
}
But in every way I received org.hibernate.PersistentObjectException: detached entity passed to persist: com.app.todaysdinner.entity.ingredient.Ingredient. Do anyone know the answer how to reattach Ingredient entity?
[EDIT]
I found out that when I want to update through Recipe cascade on RecipeIngredients also cascade onto Ingredients that means it invoke PERSISTANCE method even if allowed is only cascade = {CascadeType.MERGE}. But I can't find answer why in one #Transaction object is being detached.

Related

Hibernate orphanRemoval removes my parent entity

I have a tree structure that I call a graph, that uses the adjacency list. Class Graph has
#OneToMany(mappedBy = "graphEntry", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#OptimisticLock(excluded = true)
public List<GraphNode> getStartNodes() {
return startNodes;
}
public void addStartNode(GraphNode graphNode){
startNodes.add(graphNode);
graphNode.setGraphEntry(this);
}
public void removeStartNode(GraphNode graphNode){
startNodes.remove(graphNode);
graphNode.setGraphEntry(null);
}
and GraphNode has:
#ManyToOne
#JoinColumn(name = "graphEntry")
public Graph getGraphEntry() {
return graphEntry;
}
#ManyToOne(fetch = FetchType.LAZY, targetEntity = GraphNode.class)
#JoinColumn(name = "parent")
#OptimisticLock(excluded = true)
public GraphNode getParentNode() {
return parentNode;
}
#OneToMany(mappedBy = "parentNode", cascade = CascadeType.ALL, orphanRemoval = true)
#OptimisticLock(excluded = true)
public List<GraphNode> getChildNodes() {
return childNodes;
}
When I replace startNode with different node, I get
PersistentObjectException: detached entity passed to persist: ...GraphNode
When I change the order of 2 nodes my data are correctly saved in DB.
I tried to change cascade type to PERSIST and MERGE instead of having ALL but it doesn't help. It looks like hibernate is deleting Graph when the old startNode does not point there any more.
How can I ensure that my Graph is not being removed and node replacement works?
That is the correct behavior when you set orphanRemoval to true; it tells if the child is orphaned. it should also be removed from the database.
Let's understand this with an example, Let's say you have Employee entity and Employee can have multiple accounts.
#Entity
#Table(name = "Employee")
public class Employee
{
#Id
private Integer employeeId;
#OneToMany(orphanRemoval = true, mappedBy = "employee")
private Set<Account> accounts;
}
It essentially means that whenever we remove ‘account from accounts set’(which means I am removing the relationship between that account and Employee); the account entity that is not associated with any other Employee on the database (i.e. orphan) should also be deleted.

Entity Mapping with Cascade does not seem to work

I have the following Situation:
OrganisationEntity.java
#Entity
#Table(name = "organisation")
public class OrganisationEntity {
// ...
private PersonEntity contactPerson;
// ...
#OneToOne
#JoinColumn(name = "contact_person_id", referencedColumnName = "id", nullable = false)
public PersonEntity getContactPerson() {
return contactPerson;
}
public void setContactPerson(PersonEntity contactPerson) {
this.contactPerson = contactPerson;
}
// ...
}
ContactPerson.java
#Entity
#Table(name = "person")
public class PersonEntity {
private int id;
// ...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// ...
}
On the database the table Organisation has a non-nullable foreign key to Person. The entity mapping is uni-directional New when I want the persist a new pair of records (one organisation and one person) with merge on the OrganisationEntity I get the following error:
17:10:19.827 WARN [http-nio-8080-exec-2]
[org.hibernate.action.internal.UnresolvedEntityInsertActions] [144]
HHH000437: Attempting to save one or more entities that have a
non-nullable association with an unsaved transient entity. The unsaved
transient entity must be saved in an operation prior to saving these
dependent entities.
Unsaved transient entity:
([ch.freiwilligenarbeit_sempach.entity.PersonEntity#0])
Dependent entities:
([[ch.freiwilligenarbeit_sempach.entity.OrganisationEntity#]])
Non-nullable association(s):
([ch.freiwilligenarbeit_sempach.entity.OrganisationEntity.contactPerson])
This makes perfect sense to me, since it tries to insert the organisation with no reference to the person whatsoever. So I would usually define a cascade behaviour, so that hibernate inserts the person first, sets the reference ond the organisation and then persists the organisation. I tried the following on the organisation entity:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "contact_person_id", referencedColumnName = "id", nullable = false)
public PersonEntity getContactPerson() {
return contactPerson;
}
and
#OneToOne
#JoinColumn(name = "contact_person_id", referencedColumnName = "id", nullable = false)
#Cascade(org.hibernate.annotations.CascadeType.ALL)
public PersonEntity getContactPerson() {
return contactPerson;
}
But neither of the seem to work. I still get the same error. But I think this should actually work.
Any help is highly appreciated! Thanks in advance.
I don't understand completely, what do you want, but I suppose that you want to save a person entity with a person organization.
If you use different ids, you should add the #OneToOne annotation with attribute mappedBy=contactPerson
In the PersonEntity class
#OneToOne(cascade = CascadeType.ALL, mappedBy = "contactPerson")
private OrganisationEntity orgEntity;
In the OrganisationEntity class
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "contact_person_id", nullable = false)
private PersonEntity contactPerson; // name which is pointed as mappedBy attribute
Then you can save this correct writing something like this
orgEntity.setContactPerson(contactPerson);
orgRepo.save(orgEntity);
p.s. I wrote using field injection, but it's not necessary.
This did the trick.
#Cascade(org.hibernate.annotations.CascadeType.ALL)
Although I believe I stopped tomcat and cleaned redeployed the webapp i did not work yesterday. Today it worked as expected, so that the referenced entities (Person) were inserted before the referencing entity (Organisation).

Hibernate find method sometimes returns proxy

Lately my project encountered a really odd issue with JPA findOne(id) returning a proxy object instead of a full object.
Here is the scenario. Consider the entities and their connections shown below.
#Table(name = "HOUSE")
#Entity
#EqualsAndHashCode
#Setter
#ReadPermission(expression = "user has rights for template snapshots AND has filter")
public class HouseEntity extends VersionedEntity {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "house", fetch = FetchType.LAZY, orphanRemoval = true)
public List<RoomEntity> getRooms() {
return rooms;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "template", fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "ROOMS")
public class Room {
#ManyToOne(fetch = FetchType.LAZY)
public HouseEntity getHouse() {
return house;
}
#OneToMany(mappedBy = "room", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<TableEntity> getTables() {
return tables;
}
}
#Entity
#Table(name = "TABLES")
public class TableEntity{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "HOUSE_ID")
public HouseEntity getHouse() {
return template;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ROOM_ID")
public RoomEntity geRoom() {
return room;
}
As you can see, House has Tables and Rooms, Rooms also has Tables, and each child entity has a connection to its parents.
Add a table to the HouseEntity
Remove the table from the HouseEntity immediately after.
For 1, the houseRepository.findById gets my HouseEntity wrapped in proxy, as if it is lazy loaded.
The weird part is that if I do:
Add a table to a RoomEntity, which is a child of HouseEntity.
Remove the table from RoomEntity.
Then houseRepository.findById returns the HouseEntity without the proxy.
My question here is, why would this happen? Why would the findById method return a proxyed entity in this case? I need to have access to the normal entity without the proxy directly, even if the proxy has the entity populated in the target.

Duplicate entries for #OneToOne entity?

I have a simple parent-child relation.
Problem: in my database there are sometimes multiple child elements that map to the same parent.
#Entity
public class PersonEntity {
#Id
private long id;
//assume every person can only have one car
#OneToOne(mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
private CarEntity car;
private String name;
}
#Entity
public class CarEntity {
#Id
private long id;
#OneToOne
#JoinColumn(name = "fk_person_id", foreignkey = #ForeignKey("name = "fk_person"))
private PersonEntity person;
private String name;
}
Question: how can I happen that there are multiple car elements that map to the same person entity? How is it even possible that hibernate persists those elements? And where could they come from?
As a result, when I fetch those person entities having multiple car mappings, I'm getting of course an exception:
Caused by: org.hibernate.HibernateException: More than one row with the given identifier was found: 123, for class: CarEntity...
Sidenote: I would finally want to delete the duplicate CarEntity and only keep the most recent one. But how can I prevent this to happen in future?
Update:
#Transactional
public PersonEntity createUpdatePerson(PersonDTO dto) {
PersonEntity entity = dao.findByPersonName(dto.getName); //CrudRepository by spring
if (entity == null) {
entity = new Person();
}
mergeDTO(dto, entity); //copy and fill new values
if (person.getId() == null) dao.save(entity);
return entity;
}
private void mergeDTO(PersonDTO dto, PersonEntity entity) {
if (dto.getCar() != null) {
if (entity.getCar() == null) entity.setCar(new Car());
entity.getCar().setName(dto.getCar().getName());
entity.getCar().setPerson(entity);
}
}

JPA not saving foreign key to #OneToMany relation

I'm using Spring with Hibernate as a JPA provider and are trying to get a #OneToMany (a contact having many phonenumbers) to save the foreign key in the phone numbers table. From my form i get a Contact object that have a list of Phone(numbers) in it. The Contact get persisted properly (Hibernate fetches an PK from the specified sequence). The list of Phone(numbers) also gets persisted with a correct PK, but there's no FK to the Contacts table.
public class Contact implements Serializable {
#OneToMany(mappedBy = "contactId", cascade = CascadeType.ALL, fetch=FetchType.EAGER)
private List<Phone> phoneList;
}
public class Phone implements Serializable {
#JoinColumn(name = "contact_id", referencedColumnName = "contact_id")
#ManyToOne
private Contact contactId;
}
#Repository("contactDao")
#Transactional(readOnly = true)
public class ContactDaoImpl implements ContactDao {
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void save(Contact c) {
em.persist(c);
em.flush();
}
}
#Controller
public class ContactController {
#RequestMapping(value = "/contact/new", method = RequestMethod.POST)
public ModelAndView newContact(Contact c) {
ModelAndView mv = new ModelAndView("contactForm");
contactDao.save(c);
mv.addObject("contact", c);
return mv;
}
}
Hopefully I got all of the relevant bits above, otherwise please let me know.
You have to manage the Java relationships yourself. For this kind of thing you need something like:
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy = "contact")
private List<Phone> phoneNumbers;
public void addPhone(PhoneNumber phone) {
if (phone != null) {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
...
}
#Entity
public class Phone {
#Id
private Long id;
#ManyToOne
private Contact contact;
...
}
In reply to Cletus' answer. I would say that it's important to have the #column annotation on the id fields, as well as all the sequence stuff. An alternative to using the mappedBy parameter of the #OneToMany annotation is to use the #JoinColumn annotation.
As a kinda aside your implementation of addPhone needs looking at. It should probably be something like.
public void addPhone(PhoneNumber phone) {
if (phone == null) {
return;
} else {
if (phoneNumbers == null) {
phoneNumbers = new ArrayList<Phone>();
}
phoneNumbers.add(phone);
phone.setContact(this);
}
}
If the Contact-Phone relationship is unidirectional, you can also replace mappedBy in #OneToMany annotation with #JoinColumn(name = "contact_id").
#Entity
public class Contact {
#Id
private Long id;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "contact_id")
private List<Phone> phoneNumbers;
// normal getter/setter
...
}
#Entity
public class PhoneNumber {
#Id
private Long id;
...
}
Similar in JPA #OneToMany -> Parent - Child Reference (Foreign Key)
I don't think the addPhone method is necessary, you only have to set the contact in the phone object:
phone.setContact(contact);
If you want your relationship unidirectional i.e. can navigate from Contact to Phone's only, you need to add
#JoinColumn(name = "contact_id", nullable = false)
Under your #OneToMany on your parent entity.
nullable = false IS VITAL if you want hibernate to populate the fk on the child table
Try this sample:
#Entity
public class Contact {
#Id
private Long id;
#JoinColumn(name = "contactId")
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Phone> phones;
}
#Entity
public class Phone {
#Id
private Long id;
private Long contactId;
}
In JPA this helped me
contact.getPhoneList().forEach(pl -> pl.setContact(contact));
contactRepository.save(contact);

Categories