Entity Mapping with Cascade does not seem to work - java

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).

Related

Hibernate Multiple #OneToMany bound to same entity type

I have yet another #OneToMany question. In this case, I'm trying to model a person having a list of excluded people they shouldn't be able to send items to. This is a Spring Boot app using JPA.
In the code below, the exclusions list populates properly but the excludedBy List does not. Because of this, I believe that is causing the deletion of a Person that is excluded by another person to fail because the Exclusion in excludedBy is not mapped on the object properly.
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
...
#OneToMany(mappedBy = "sender", cascade = { CascadeType.ALL })
List<Exclusion> exclusions = new ArrayList<>();
//This is not getting populated
#JsonIgnore
#OneToMany(mappedBy = "receiver", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
...
}
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#GeneratedValue
#Column(nullable = false)
Long id;
#ManyToOne
#JsonIgnore
Person sender;
#ManyToOne
Person receiver;
...
}
I would expect that this would have mapped the bidirectional relationship properly and as such the excludedBy List would be populated as well.
Any wisdom on this matter would be great!
1 - An #Id is by default not nullable, not required:
#Column(nullable = false)
2 - There is no need for an #Id in this class. Both sides of the exclusion are together unique. Not needed:
#Id
#GeneratedValue
Long id;
3 - An "Exclusion" requires both an excludedBy and an excluded, give them names that match and they are your #Id. It is a 2 way ManyToMany relationship.
#Entity
#Table(name = "exclusions")
public class Exclusion {
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excludedBy;
#Id
#ManyToMany // An ID so not optional, so no need for (optional = false)
Person excluded;
}
Entity Exclusion always knows both sides of the story.
#ManyToMany(mappedBy = "excludedBy", cascade = { CascadeType.ALL })
List<Exclusion> excluded = new ArrayList<>();
#ManyToMany(mappedBy = "excluded", cascade = { CascadeType.ALL })
List<Exclusion> excludedBy = new ArrayList<>();
Tip: JSON DTOs shouldn't be defined in your JPA DTOs, otherwise you can't change your internal data model independently of your external API model.
I had this problem in the past. Your key problem ist that your ORM Mapper hibernate does not know which of your database entries need to be assinged to exclusions and which are assiged to excludedBy. You need a discriminator and add the constraint in your select. I would propose a solution that looks something like this:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 0")
private Set<Exclusion> exclusions;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PRIMARY_KEX_IN_EXCLUSION_TABLE", referencedColumnName = "id")
#Where(clause = "is_excluded_by = 1")
private Set<Exclusion> excludedBy;
the value isExcludedBy needs to be a database column, part of your Entity and set in your code manually.
I think you also need to use Set instead of List when having multiple collections in one Entity. https://vladmihalcea.com/spring-data-jpa-multiplebagfetchexception/

The DELETE statement conflicted with the REFERENCE constraint in dbo.interests

I am running into a problem deleting related entities from my database. I have a trading application where users can post trades and express their interests in other people's trades.
When a user deletes their account, all trades posted and interests expressed by this user should be removed from the database. However, the latter doesn't seem to work (I am also not sure if the first one works as I don't know in what order they get executed). I get the error:
The DELETE statement conflicted with the REFERENCE constraint "FKq9kr60l7n7h3yf82s44rkoe4g". The conflict occurred in database "dbi438161_i438161", table "dbo.interests", column 'user_id'.
Note: I get the same when I try to delete a trade but then the column is 'trade_id'
I do the same for the trades and roles of a user so I think it has to do with what is in my interest entity. I am using CascadeType.ALL annotation to let Hibernate remove related entities
Lists of related entities in user:
#ManyToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(name="user_roles",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Interest> interests = new ArrayList<>();
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="user")
private List<Trade> trades = new ArrayList<>();
Interest entity:
#Entity
#Table(name = "interests")
public class Interest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int interestId;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne
#JoinColumn(name = "trade_id", nullable = false)
private Trade trade;
private String comment;
public Interest(User user, Trade trade, String comment) {
this.user = user;
this.trade = trade;
this.comment = comment;
}
public Interest(){
}
}
For comparison, the trade entity:
#Entity
#Table(name = "trades")
public class Trade {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="trade_id")
private int tradeId;
#Column(name="wants")
private String wants;
#Column(name="offers")
private String offers;
#Column(name="date_last_modified")
private LocalDateTime lastModified;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Transient
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy="trade")
private List<Interest> interests = new ArrayList<>();
public Trade(String wants, String offers, User user){
this.wants = wants;
this.offers = offers;
this.user = user;
}
public Trade() {
}
}
Does anybody have an idea on what I am doing wrong here? Thanks in advance
Try to set orphanRemoval to true for the following associations:
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Interest> interests = new ArrayList<>();
#OneToMany(cascade=CascadeType.ALL, mappedBy="user", orphanRemoval = true)
private List<Trade> trades = new ArrayList<>();
As it is stated in the documentation:
If the child entity lifecycle is bound to its owning parent so that the child cannot exist without its parent, then we can annotate the association with the orphanRemoval attribute and dissociating the child will trigger a delete statement on the actual child table row as well.
Please also note that you should not use cascade=CascadeType.ALL for the #ManyToMany association as it explained in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.

Is there an easy way using JPA/Hibernate to remove a child entity when parent is deleted without having to define a bi-directional relationship?

As the title says. Suppose I have the following entities:
#Entity
#Table
public class User {
#Id
private UUID id;
#Column(nullable = false)
private String name;
}
#Entity
#Table
public class Phone {
#Id
private UUID id;
#Column(nullable = false)
private String number;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
And I don't want to define a bi-directional association - that is, I don't want to define the relation on the owner side of the relation (in the User entity). Is there an easy way to mark a Phone entity for removal if I delete its parent User?
Looking for something like CascadeType.REMOVE but on the many side of the relation. Is there such a setting available?
Use cascade = CascadeType.DELETE and orphanRemoval = true in the owning side of your association.
#Entity
#Table
public class User {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) // ALL includes DELETE
private List<Phone> phones;
...
}

Auto generated id not cascading on EmbeddedId in child entity in Hibernate

When trying to insert an Entity - Awith a set Another Entity B, B should get the Auto generated Id from A but its null.
Tried and failed:
#MapsId("taskPKId.storyId.id") - Same error.
#Embeddable
class StoryId {
#ManyToOne(fetch = FetchType.Lazy)
#JoinColumn(name = "STORY_ID")
Long id;
} //Incomprehensible Null pointer exception
mappedBy("story") - same error
Tried with mappedBy('story') but getting an error with repeated column and so had to map it with insertable=false and updatable=false [Hibernate doesn't recognize insertable=false for #EmbeddedId]
I am getting STORY_ID = null and therefore saveAll fails on storyRepository.saveAll(stories) where storyRepository is a Spring Data repository
#Table(name = "STORY")
#EqualsAndHashCode
class Story {
#Id
#GeneratedValue(stratergy=GenerationType.Auto)
#Column(name="STORY_ID")
Long id;
#Column(name="STORY_NAME")
String name;
//#OneToMany(cascade=ALL, mappedBy="taskPKId.storyId.id", fetch = FetchType.Lazy) // tried this as well
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task;
}
#Table(name = "TASK_XREF")
#EqualsAndHashCode
Class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID", nullable = false, insertable = false, updatable = false)
Story story;
}
#Embeddable
#EqualsAndHashCode
Class TaskPKId implements Serializable {
TaskId taskId;
TaskTypeId taskTypeId;
StoryId storyId;
}
#Embeddable
#EqualsAndHashCode
class StoryId implements Serializable {
#Column(name = "STORY_ID")
Long id;
}
Tables:
STORY [STORY_ID, STORY_NAME]
TASK_XREF [(TASK_ID(FK), TASK_TYPE_ID(FK), STORY_ID(FK)) PK,TASK_NAME]
Story gets inserted (before commit ofcourse), but fails because STORY_ID is sent as null to TASK_XREF for the next inserts
I'm not quite sure why your configuration does not work. I have a similar configuration in one of my projects that works just fine. I was able to find a solution however, by adding a #MapsId annotation to the ManyToOne in the Task class. (see can someone please explain me #MapsId in hibernate? for an explanation about MapsId) I also removed insertable=false and updatable=false. See below for the code.
I didn't get MapsId to work with the StoryId class, so i changed the type of TaskPKID.storyId from StoryId to long. The StoryId class doesn't seem to add much, so hopefully this isn't to much of a problem. If you find a solution please let me know in the comments though!
By the way, your code has a lot of problems. There's a bunch of typo's, and there is a OneToMany mapping on a property that is not a Collection (which isn't allowed) This made it more difficult for me to debug the problem. Please make sure to post better quality code in your questions next time.
Here is the Task class the way I implemented it:
#Entity
#Table(name = "TASK_XREF")
class Task {
#EmbeddedId
TaskPKId taskPKId;
#Column(name = "TASK_NAME")
String name;
#MapsId("storyId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "STORY_ID")
Story story;
//getters, setters
}
And here is the TaskPKID class:
#Embeddable
class TaskPKId implements Serializable {
long taskId;
long taskTypeId;
#Column(name="STORY_ID")
long storyId;
public long getTaskId() {
return taskId;
}
public void setTaskId(long taskId) {
this.taskId = taskId;
}
public void setTaskTypeId(long taskTypeId) {
this.taskTypeId = taskTypeId;
}
}
I'm not sure what you want to achieve, but it looks like you have combined #OneToMany annotation with #OneToOne-like implementation (which can lead to unexpected behavior like this one).
Possible solutions:
If one story owns multiple tasks
// Story.java
#OneToMany(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Set<Task> task; // basically Set, List or any other collection type
// Task.java
#ManyToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
If one story owns only one task
// Story.java
#OneToOne(cascade=ALL, mappedBy="story", fetch = FetchType.Lazy)
Task task;
// Task.java
#OneToOne (fetch = FetchType.Lazy, optional = false)
#JoinColumn(name = "STORY_ID", referencedColumnName = "STORY_ID")
Story story;
Further reading:
#OneToOne
#OneToMany

Hibernate, JPA cant delete one-to-many relation

I have one-to-many relation:
#Entity
#Table(name = "Users")
public class User {
#Id
#Column(name = "user_id", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "login", nullable = false)
private String login;
#Column(name = "password", nullable = false)
private String password;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", nullable = false)
private Role role;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = javax.persistence.CascadeType.ALL)
private Set<Contacts> contacts = new HashSet<Contacts>();
And I'm trying to delete User object with all Contacts; I tried to use:
cascade = javax.persistence.CascadeType.ALL
cascade =
javax.persistence.CascadeType.REMOVE
#Cascade(CascadeType.DELETE) from org.hibernate.annotations
#Cascade(CascadeType.DELETE_ORPHAN) from org.hibernate.annotations
but nothing helped. I always get exception:
org.hibernate.util.JDBCExceptionReporter - Cannot delete or update a
parent row: a foreign key constraint fails
(contactmanager.contact, CONSTRAINT contact_ibfk_1 FOREIGN KEY
(user_id) REFERENCES
UPD
Code that deletes a User is as follows:
#Transactional
public void removeUser(User user) {
sessionFactory.getCurrentSession().delete(user);
}
I'll appreciate any help! Thanks.
My recommendation here would be to do the relationship management yourself. Cascading removes can be tricky (especially in a situation like yours where the owner of your bi-directional relationship is not the one declaring the cascade) and often times quite dangerous so I usually prefer to avoid them. Especially if you are running a version of JPA pre-2.0 then you don't have too much of a choice. I would just change the removal method to something like:
#Transactional
public void removeUser(User user) {
Set<Contacts> contacts = user.getContacts();
for (Contact contact : contacts) {
sessionFactory.getCurrentSession().delete(contact);
}
contacts.clear();
sessionFactory.getCurrentSession().delete(user);
}

Categories