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.
Related
The Problem
I have a 1:n relation, but the n side shouldnt rely on constraints. So i actually wanna insert a EntityPojo via its future id, when its not saved yet ( Lets ignore that its a bad practice ). This looks kinda like this.
var relation = new RelationshipPojo();
.
.
.
relation.targets.add(session.getReference(futureID, EntityPojo.class));
session.save(relation);
// A few frames later
session.save(theEntityPojoWithTheSpecificId);
Cascading is not possible here, i only have its future ID, not a reference to the object i wanna save. Only its id it will have in the future.
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
.
.
.
#ManyToMany(cascade = {}, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
Question
How do we tell hibernate that it should ignore the constraints for this 1:n "target" relation ? It should just insert the given ID into the database, ignoring if that EntityPojo really exists yet.
Glad for any help on this topic, thanks !
For a much simpler solution, see the EDIT below
If the goal is to insert rows into the join table, without affecting the ENTITY_POJO table, you could model the many-to-many association as an entity itself:
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#OneToMany(cascade = PERSIST, fetch = EAGER, mappedBy = "relationship")
public Set<RelationShipEntityPojo> targets = new LinkedHashSet<>();
}
#Entity
public class RelationShipEntityPojo {
#Column(name = "entity_id")
private Long entityId;
#ManyToOne
private RelationshipPojo relationship;
#ManyToOne
#NotFound(action = IGNORE)
#JoinColumn(insertable = false, updatable = false)
private EntityPojo entity;
}
This way, you'll be able to set a value to the entityId property to a non-existent id, and if an EntityPojo by that id is later inserted, Hibernate will know how to populate relationship properly. The caveat is a more complicated domain model, and the fact that you will need to control the association between RelationshipEntityPojo and EntityPojo using the entityId property, not entity.
EDIT Actually, disregard the above answer, it's overly complicated. Turing85 is right in that you should simply remove the constraint. You can prevent Hibernate from generating it in the first place using:
#ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinTable(inverseJoinColumns = #JoinColumn(name = "target_id", foreignKey = #ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT)))
public Set<EntityPojo> targets = new LinkedHashSet<>();
The only caveat is that when you try to load RelationshipPojo.targets before inserting the missing EntityPojo, Hibernate will complain about the missing entity, as apparently #NotFound is ignored for #ManyToMany.
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;
I have a Person object. Person has a manager property which is again of type Person.
#OneToOne(optional = true, cascade = { CascadeType.ALL })
private Person manager;
Say John is the manager and Bob is the employee. When I am trying to delete John, it fails since Bob becomes orphan (without Manager). That should be allowed in my use case. But marking this relationship "optional" doesn't help. And Cascade doesn't seem to have any significance here.
I presume this is possible with JPA. Any help?
#Entity
public class Person {
#Id
private String id;
private String name;
private Integer age;
private Address address;
#JoinColumn(name = "manager_id", nullable = true, insertable = true, updatable = true)
#OneToOne(optional = true, cascade = { CascadeType.ALL })
private Person manager;
#OneToMany(mappedBy = "manager", cascade = { CascadeType.ALL })
public Collection<Person> reportees;
You should set null as Bob's manager, before trying to delete John. Also, #Andrei is right, you should map this as bidirectional #ManyToOne relation (although your code will work if you know all the persons that have John as their manager).
I doubt the relationship is #OneToOne, as there are many employees that have the same manager.
If you insist that it is #OneToOne, then make the relationship bidirectional, by adding the property in the Person Java class:
#OneToOne(mappedBy="manager", cascade = { CascadeType.ALL })
private Person employee;
I deleted the parent entity before deleting children. Not the ideal solution but works for me.
I also tried the bidirectional relationship (by adding #ManyToOne reportees property of type Person) and marked the relationship "optional" using #JoinColumn also marked nullable=true. But nothing worked.
This is more of a general 'understanding' question rather than a specific senario question.
I have been lookiing at the ways in which JPA maps tables together and found two examples here that seem to work in different ways.
One has a Set of Phone objects using #JoinTable to join STUDENT_PHONE to STUDENT by STUDENT_ID
The other has a Set of StockDailyRecord but seems to just use mappedby stock and in the stock_detail table object have the #PrimaryKeyJoinColumn annotation.
Simply trying to get an understanding of which method would be the prefered way and why?
Method 1:
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_PHONE", joinColumns = { #JoinColumn(name = "STUDENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "PHONE_ID") })
public Set<Phone> getStudentPhoneNumbers() {
return this.studentPhoneNumbers;
}
Method 2:
#Table(name = "stock", catalog = "mkyongdb", uniqueConstraints = {
#UniqueConstraint(columnNames = "STOCK_NAME"),
#UniqueConstraint(columnNames = "STOCK_CODE") })
#OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
public Set<StockDailyRecord> getStockDailyRecords() {
return this.stockDailyRecords;
}
#Table(name = "stock_detail", catalog = "mkyongdb")
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
public Stock getStock() {
return this.stock;
}
Method #2:
It uses an extra column to build the OneToMany relation. This column is a Foreign key column of the other table. Before building the relation if these data needs to be added to the database then this foreign key column needs to be defined as nullable. This breaks the efficiency and cannot provide a normalized schema.
Method #1:
It uses a third table and is the efficient way to store data in a relational database and provides a normalized schema. So where possible its better to use this approach, if the data needs to be existed before building the relation.
I have two entities with a onetoone relationship, A and B. B entity is optional, can be updated and removed on it's own, but must always be linked to an instance of A.
So i have two JPA entities, A and B with a bi-directional relationship. THis is the one from A to B.
#OneToOne(mappedBy = "a", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
I can create a A and B, remove the A and both get deleted. good.
But because of the cascade from A to B, if i em.remove(b) the delete doesn't get persisted. Even if i do a.setB(null) first.
The only way to delete the optional entity, while keeping the cascade, seems to be to use a new JPA2 feature, orphanRemoval=true. Call a.setB(null), then persist A.
This means i can't do operations directly on B, it implies too strong of a composition relationship, all actions on B must be done via A.
But B is not an embedded object, it's a full blown Entity in it's own right, how can i delete it independently of A?
The best way seems to be to remove the cascade, and force users to make sure they delete any related objects separately before they delete the A? Enforced by a FK constraint in the B table.
This is such a straight forward case. two related entities, the relationship is optional on one end, and mandatory on the other.
Oh, this is with hibernate 4.2.3-Final
Your current object design implicitly defined that one is more important than another. That is, one will have the foreign key to another.
To make them equal, you just define the JoinTable between them. Set cascade on both sides, and then everything will work as expected.
Example:
Document class
#Entity
public class Document extends ABaseEntity {
private Medicine medicine;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinTable(
name = "Document_Medicine",
joinColumns =
#JoinColumn(name = "DOC_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "MED_ID", referencedColumnName = "ID"))
public Medicine getMedicine() {
return medicine;
}
public void setMedicine(Medicine medicine) {
this.medicine = medicine;
}
}
Medicine class
#Entity
public class Medicine extends ABaseEntity{
private Document document;
#OneToOne(mappedBy = "medicine", cascade = CascadeType.REMOVE)
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}