Getting right to the point, I'm trying to build the following logic to hibernate relationships.
A Resource has many read groups.
A Resource has many write groups.
Both groups are Groups class.
What I did until now:
ResourcePage.class
public class ResourcePage {
/*
useless code
*/
private Set read;
private Set write;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
targetEntity = Groups.class)
#JoinTable(name = "resourcepage_read_permissions")
public Set getRead() {
return read;
}
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
targetEntity = Groups.class)
#JoinTable(name = "resourcepage_write_permissions")
public Set getWrite() {
return write;
}
/*
useless code
*/
}
The tables is created as expected.
However, hibernate is generating an unique constraint to id of group and this is giving me a big problem because sometimes two different resources can be same group as read group.
How do you guys deal with it?
How can I make hibernate not generate this unique constraint?
Thanks a lot.
You need to use #ManyToMany instead of #OneToMany.
Related
Consider the following code:
#Entity
public class User {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private List<UserRole> roles;
}
According to the code above User.roles will be loaded lazily. However, we can change this behavior using fetchgraph, something like this:
EntityGraph<User> graph = entityManager.createEntityGraph(User.class);
graph.addSubgraph("roles");
typedQuery.setHint("javax.persistence.fetchgraph", graph);
List<User> entities = typedQuery.getResultList();//roles will be eagerly loaded
Is is possible to make JPA provider/Hibernate create and update User with roles field when #OneToMany.cascade = null? By other words, I want to add cascade = CascadeType.ALL dynamically to have a full and dynamic control over entity tree for all create/update/delete operations.
I'm currently implementing a doc with a like button like this:
The like button is associated with certain user account. When you press a like, it will stay liked for that user (similar to youtube video).
My entities and DTOs are below:
Doc.java:
#Entity(name = "Doc")
#Table(name = "doc")
#Data
public class Doc {
//Unrelated code reacted for clarity
#ManyToMany(cascade = {
CascadeType.MERGE,
CascadeType.PERSIST
})
#JoinTable(
name = "doc_user_dislike",
joinColumns = #JoinColumn(name = "doc_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<UserWebsite> dislikedUsers;
#ManyToMany(cascade = {
CascadeType.MERGE,
CascadeType.PERSIST
})
#JoinTable(
name = "doc_user_like",
joinColumns = #JoinColumn(name = "doc_id"),
inverseJoinColumns = #JoinColumn(name = "user_id")
)
private Set<UserWebsite> likedUsers;
}
User.java:
#Entity
#Table(name = "user_website")
#Data
public class UserWebsite {
//Unrelated code reacted for clarity
#ManyToMany(mappedBy = "likedUsers")
private Set<Doc> likedDocs;
#ManyToMany(mappedBy = "dislikedUsers")
private Set<Doc> dislikedDocs;
}
DocDetailsDTO.java (This will be sent to client).
#Data
public class DocDetailsDTO {
private Long id;
private Boolean isDisliked;
private Boolean isLiked;
}
I'm having some solutions:
Add a field called isLiked to Doc.java with #Formular combine with
#Transient and perform queries to DB.
Have another API which accept from Client a list of DocID, and a
UserID, then return a list of DocID that UserID liked.
Check if UserID exist in likedUsers list (not very efficient,
sometimes not feasible since I have to initialize that big
lazy-loaded list).
The question is: What is the most efficient way to retrieve liked/disliked status for many post at once (>10 doc but max 100 doc per request) for about thousand users (1000 CCU) at once ? Are above solutions already optimal ?
Any help is appreciated. Thanks for your time reading through the question.
If I understand the problem correctly, this approach is not correct. You want to determine if a given user likes specified documents, so the formula would need a user id parameter, which you have no way to pass to the formula. Even if somehow #Formula could be used, it leads to N+1 problem (extra query per each document). Plus, you use managed entities which means extra dirty checking at the end.
This one is optimal in my opinion - one query, capable of using projection (no managed entities).
As you notice, this will kill your application and database. Plus, again you use managed entities which means extra dirty checking at the end. Definitely don't use this one.
Currently we have an issue (a well known one) with Spring Data JPA + Spring Data REST (Hibernate as JPA implementation) when trying to update the collection (relation) which is a not the owning side.
The mapping is the following:
#Entity(name = Product.NAME)
public class Product {
...
#OneToMany(mappedBy = "baseProduct", fetch = FetchType.LAZY, targetEntity = Variant.class)
List<Variant> getVariants() {
...
and on the other variant side:
#Entity(name = Variant.NAME)
public class Variant extends Product {
...
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Product.class)
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
Product getBaseProduct() {
...
}
all is good on the Java side if you use Spring Data JPA only, however if you want to update the "product" by updating its collection of variants and send PATCH request to https://localhost:8112/storefront/rest/product/21394435410197232 containing the payload of the new collection only (having 2 out of the 3 items):
{"variants":["22801810293768080","22801810293768096"]}
I get no exceptions or anything but since the owning side is the other side nothing is persisted and I got the old 3 items again.
I know that I can fix this by setting
#JoinColumn(name = "baseproduct_id", referencedColumnName = "id")
on both sides and not use mappedBy anywhere, however I have heard there is a performance implication which I am not sure how big it is (we got 100+ entities having #OneToMany) and I wonder is there better workaround via #PreUpdate listener or something ?
You have to synchronize both sides of the bidirectional association, and also add on orphanRemoval and Cascade.
So, your mapping becomes:
#OneToMany(
mappedBy = "baseProduct",
fetch = FetchType.LAZY,
targetEntity = Variant.class
cascade = CascadeType.ALL,
orphanRemoval = true)
List<Variant> getVariants() {
And the two add/remove methods:
public void addVariant(Variant variant) {
getVariants().add(variant);
variant.setBaseProuct(this);
}
public void removeVariant(Variant variant) {
variant.setBaseProuct(null);
this.getVariants().remove(variant);
}
You need to implement equals and hashCode methods in the Variant child entity for the add and remove methods to work effectively.
i am developing an sample application using hibernate. Its going quite smooth, but i have one small query regarding one to many relation.
I have seen there are 2 different ways of specifying the relation
#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;
}
the other way is
#OneToMany(fetch=FetchType.EAGER)
#JoinColumn(name="PERSON_ID", nullable=false)
public Set<Address> getAddresses() {
return addresses;
}
which is more efficient and when to use which method.
The second one is probably a bit more efficient, because it needs one join less than the first one.
But it couples the many side (address) to the one side (person) by requiring a foreign key in the address table. That is in contradiction with the fact that the association is unidirectional (address doesn't know about its person in the object model).
This is why the second one is the default for unidirectional one to many associations.
I have the following (simplified) Hibernate entities:
#Entity
#Table(name = "package")
public class Package {
protected Content content;
#OneToOne(cascade = {javax.persistence.CascadeType.ALL})
#JoinColumn(name = "content_id")
#Fetch(value = FetchMode.JOIN)
public Content getContent() {
return content;
}
public void setContent(Content content) {
this.content = content;
}
}
#Entity
#Table(name = "content")
public class Content {
private Set<Content> subContents = new HashSet<Content>();
private ArchivalInformationPackage parentPackage;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "subcontents", joinColumns = {#JoinColumn(name = "content_id")}, inverseJoinColumns = {#JoinColumn(name = "elt")})
#Cascade(value = {org.hibernate.annotations.CascadeType.DELETE, org.hibernate.annotations.CascadeType.REPLICATE})
#Fetch(value = FetchMode.SUBSELECT)
public Set<Content> getSubContents() {
return subContents;
}
public void setSubContents(Set<Content> subContents) {
this.subContents = subContents;
}
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "parent_package_id")
public Package getParentPackage() {
return parentPackage;
}
public void setParentPackage(Package parentPackage) {
this.parentPackage = parentPackage;
}
}
So there is one Package, which has one "top" Content. The top Content links back to the Package, with cascade set to ALL. The top Content may have many "sub" Contents, and each sub-Content may have many sub-Contents of its own. Each sub-Content has a parent Package, which may or may not be the same Package as the top Content (ie a many-to-one relationship for Content to Package).
The relationships are required to be ManyToOne (Package to Content) and ManyToMany (Content to sub-Contents) but for the case I am currently testing each sub-Content only relates to one Package or Content.
The problem is that when I delete a Package and flush the session, I get a Hibernate error stating that I'm violating a foreign key constraint on table subcontents, with a particular content_id still referenced from table subcontents.
I've tried specifically (recursively) deleting the Contents before deleting the Package but I get the same error.
Is there a reason why this entity tree is not being deleted properly?
EDIT: After reading answers/comments I realised that a Content cannot have multiple Packages, and a sub-Content cannot have multiple parent-Contents, so I have modified the annotations from ManyToOne and ManyToMany to OneToOne and OneToMany. Unfortunately that did not fix the problem.
I have also added the bi-directional link from Content back to the parent Package which I left out of the simplified code.
If I understand correctly, based on the ManyToOne mapping, one Content has many Packages, and I assume you removed the "packages" collection field from your Content class in your simplified code above?
So, for your "packages" collection field, do you have a cascade delete on it (just like what you have on your subcontents)? If you do, then I think it should work. When you delete the root Content, it should perform cascade delete on each subcontent, and each content will then perform cascade delete on the package.
Does that work?
The problem turned out to be caused by the fact that I was flushing and clearing the session after deleting each Package, and due to the circular dependencies in the model not everything was being deleted. The flush and clear are required because very large data sets are involved. In the end I changed it so that a set of all entities dependent on the current Package is constructed (which may include other Packages) and then all deleted before calling flush and clear.