Any way to automate deleting non-owner of a bidirectional relationship? - java

Hello Hibernate/JPA gurus!
I am new to Hibernate, and have a question.
Say I have a bidirection relationship between Books and Tags (any book can be tagged with any tag)
Tables:
book (bookid, bookname)
tag (tagid, tagname)
booktaglink (booktaglinkid, bookid, tagid)
public class Book() {
#ManyToMany(fetch = FetchType.LAZY)
#Cascade({CascadeType.MERGE, CascadeType.PERSIST, CascadeType.EVICT})
#JoinTable(name = "booktaglink",
joinColumns = {#JoinColumn(name = "bookid") },
inverseJoinColumns = {#JoinColumn(name = "tagid") })
private List<Tag> tags = new ArrayList()
}
public class Tag() {
#ManyToMany(fetch = FetchType.LAZY, mappedBy="tags")
#Cascade({CascadeType.MERGE, CascadeType.PERSIST, CascadeType.EVICT})
private List<Book> books = new ArrayList()
}
So, above... I have Books and Tags bidirectional relationship, so I can retrieve tags that are associated with the book, and books that are associated with the tags. However, Books owns the relationship.
Say I want to delete a tag. Is this correct?
Tag tagToDelete;
List<Book> books = tag.getBooks()
for (Book book: books) {
book.getTags().remove(tagToDelete);
dataSource.save(book)
}
dataSource.delete(tagToDelete);
Why do I have to open the owner of association (in this case Book class) and remove the tag I am trying to delete? Can I simply cascade the delete and remove all associations with books? This sucks because if I simply do dataSource.delete(tagToDelete), it will leave all the associations in the link table, and cause errors. Is there any way to automate the delete process instead of looping as I did in the example above.
Is there a general rule about who should own the relationship?
Why would anyone create a uni-directional relationship? If this was unidirectional, and I am trying to delete a tag, I will never be able to delete the associations except if I loop through all the books in the database and remove the tag I am deleting. Seems inefficient.
Thanks so much!!
PA

Deleting an entity in a bidirectional ManyToMany relation is not that intuitive. From what i recall, when you delete an entity from the owning side (Book in your case), all joins are deleted as well without deleting any Tags. Vize versa this won't work. To solve this, just declare both sides as owning sides. Plus I advise you to use Sets to prevent duplicate data.
In your class Tag get rid of mappedBy="tags" and add a #JoinTable:
#JoinTable(name = "booktaglink",
joinColumns = {#JoinColumn(name = "tagid") },
inverseJoinColumns = {#JoinColumn(name = "bookid") })
private Set<Book> books;

Related

JPA many-to-many duplicate

I have tables Tags, Blog and BlogTags.
With using many-to-many I would like a blog to have many tags (as objects with id, name).
In front end, when I add tags, they are created with name only and id of null so when I save the blog, new tag gets created every time as it auto increments Tag, what I would like is for the Tag to be merged with blog if said tag already exists. The idea is so that I can click on any give tag and get the all the blogs associated with it.
So object sent to backend would be:
{id: null,
name: 'name'
,.....
tags: [{id: null, name: 'name},{id: null, name: 'name}]}
Any help is appreciated.
#Data
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
private List<BlogPost> blogPosts;
}
public class BlogPost {
//other data
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinTable(
name = "blog_post_tag",
joinColumns = {#JoinColumn(name = "blog_post_id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id")})
private List<Tag> tags;
}
I went with #ElementCollection without many-to-many annotations and it works.
Not having the TAG ID is the normal behavior to register.
You can try, which for me is the best option, to load a master table from which you get the id if it already exists and with a find (depending on the technology used) at the moment the user inserts the check if tag. exists to load the id, get the DB entity and set the relationship of the tag entity to the post
Or before saving the entity, post iterate over all the tags making a query by tag to see if it exists and assign it.
In essence it is the same but with the first option we only make a query to DB

JPA manyToMany relationship middle table is not updated

I have CATEGORY, AD and CATEGORY_AD table, typical many to many relationship. Somehow nothing is inserted into CATEGORY_AD table. What am I missing?
In Category.java:
#ManyToMany
#JoinTable(name = "CATEGORY_AD", joinColumns = {
#JoinColumn(name = "CATEGORY_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
#JoinColumn(name = "AD_ID", referencedColumnName = "ID") })
private List<Ad> ads;
In Ad.java:
#ManyToMany(mappedBy = "ads")
private List<Category> categories;
In my Service class:
Category laCat = new Category();
laCat.setId(categoryId);
laCat.getAds().add(ad);
ad.getCategories().add(laCat);
ad = adRepository.saveAndFlush(ad);
You are saving the 'owned' side instead of the 'owning' side.
Every ManyToMany relationship must have an owner, which in your case is Category. You can see that because in Category you have the definition of the #JoinTable in the ads List, and in Ad you refer to that list by #ManyToMany(mappedBy = "ads").
Whenever you save the owning side of the relationship, this will trigger a save on the join table too, but not the other way around. So saving the owned side (Ad) will do nothing on the CATEGORY_ADtable.
You should do something like this:
Category laCat = new Category();
laCat.setId(categoryId);
laCat.getAds().add(ad);
// Next line will insert on CATEGORY and CATEGORY_AD tables
laCat = categoryRepository.saveAndFlush(category);
// We add the category to the ad object to keep both sides in sync
ad.getCategories().add(laCat);
You can see that even if a save on Category triggers a save on the join table, it's still our responsibility manually add the category to the categories Listin the ad object so both sides are in sync, having the same elements. And there's no need to save the ad object already.
Try to save Category object also (before you invoke ad.getCategories().add(laCat);)
Please use hibernate #Cascade annotations on List<Category> categories;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
#ManyToMany(mappedBy = "ads")
#Cascade(value = CascadeType.SAVE_UPDATE)
private List<Category> categories;
If you prefer using JPA annotations then you can try this:
#ManyToMany(mappedBy = "ads", cascade = CascadeType.PERSIST)
#Cascade(value = CascadeType.SAVE_UPDATE)
private List<Category> categories;
Please check this thread: what is cascading in hibernate

JPA ManyToMany mappedBy clarification

I'm learning to use JPA and there's one thing that confuses me. I'm trying to implement a many to many relationship between two classes that I've got. My database schema is simple enough. There's one table called stations (PK: station_id), one called buses (PK: bus_id) and one to connect them up together called station_bus (FKs: station_id, bus_id).
The relevant code:
// Station Class
#Entity
#Table(name = "stations")
public class Station {
private List<Bus> buses;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "station_bus",
joinColumns = {#JoinColumn(name = "bus_id")},
inverseJoinColumns = {#JoinColumn(name = "station_id")}
)
public List<Bus> getBuses() { return buses; }
}
// Bus Class
#Entity
#Table(name = "buses")
public class Bus {
private List<Station> stations;
#ManyToMany(
fetch = FetchType.LAZY,
mappedBy = "buses"
)
public List<Station> getStations() { return stations; }
}
There is one thing that confuses me. I understand that in a many to many relationship, one needs to be the owner, Station in this case, and one the ownee. The difference being the owner needs to specify the #JoinTable annotation and the ownee needing to specify mappedBy. The thing I don't understand is what the mappedBy value needs to be set to.
From what I gather from the various examples I've looked over is that it needs to be the name of the field in the owner class, so in this example, since Station contains a buses field, that's what the mapped by needs to be set to.
If anybody could confirm or correct me, it would be helpful, as I haven't been able to find an answer.
Some notes:
The absence of the mappedBy element in the mapping annotation implies
ownership of the relationship, while the presence of the mappedBy
element means the entity is on the inverse side of the relationship.
The value of mappedBy is the name of the attribute in the owning
entity that points back to the inverse entity.
Your sample use of mappedBy is correct.

Can't quite understand how to delete fields properly with Hibernate

I have many-to-many relation in my database (entities are Participant and Event)
//part of participant model
#ManyToMany(fetch = FetchType.LAZY , cascade = { CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "participant_event",
joinColumns = {#JoinColumn(name = "participant_id")},
inverseJoinColumns = {#JoinColumn(name = "event_id")})
//part of event model
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "events", cascade = CascadeType.ALL)
Right at this moment, deleting an Event causes deleting all the Participants who are to visit this event. Removing CascadeType.ALL in event model causes no result after deleting Event (by deleting I mean .remove). What is the proper way to delete Event only and keep all the participants?
Code is here
In a many-to-many association, CascadeType.REMOVE is not desirable, since it deletes the actual entities (not just their associated link table entries).
The proper way to delete associations is to dissociate the entities:
public void removeEvent(Event event) {
events.remove(event);
event.setParticipant(null);
}
To delete an Event, you'd have to remove from all the Participants. For that you have to do something like this:
Event deletableEvent = ...;
for(Iterator<Participant> participantIterator = event.getParticipnts(); participantIterator.hasNext();) {
Participant participant = participantIterator.next();
participant.getEvents().remove(deletableEvent);
participantIterator.remove();
}
entityManager.flush();
entityManager.remove(deletableEvent);

JPA/Hibernate: ManyToMany delete relation

I have two classes, say Group and Person with a ManyToMany-Relation that is mapped in a JoinTable.
If I delete a Person that has a relation to a Group, I want to delete the entry from the join table (not delete the group itself!).
How do I have to define the cascade-Annotations? I didn't found a really helpful documentation but several unsolved board discussions...
public class Group {
#ManyToMany(
cascade = { javax.persistence.CascadeType.? },
fetch = FetchType.EAGER)
#Cascade({CascadeType.?})
#JoinTable(name = "PERSON_GROUP",
joinColumns = { #JoinColumn(name = "GROUP_ID") },
inverseJoinColumns = { #JoinColumn(name = "PERSON_ID") })
private List<Person> persons;
}
public class Person {
#ManyToMany(
cascade = { javax.persistence.CascadeType.? },
fetch = FetchType.EAGER,
mappedBy = "persons",
targetEntity = Group.class)
#Cascade({CascadeType.?})
private List<Group> group;
}
Cascade will not clean up the leftover references to the deleted Person that remain on the Group object in memory. You have to do that manually. It seems like cascade should do this, but sadly that's not the way it works.
Based on the info provided in your question, I don't think you need any cascade options set on your Person or Group entities. It doesn't sound like they share a parent/child relationship where the existence of one depends upon the other. That's the kind of relationship where I would expect to see some cascade options.
I believe what you want is:
cascade = CascadeType.ALL
To remove the DB relation, remove the association from each group. Remove the person from the Group.persons collection and remove the Group from the Person.group collection, then persist your person object.
You can probably do it on a database specifically (depends on your database and it capabilities). By adding "on delete cascade" to the foreign key of the relationship table.

Categories

Resources