It's quite some time that I'm trying to figure out this problem and from googling around many people have similar problems.
I'm trying to model a User in a Social Network, using Hibernate, and what is more basic to a social network than to map a friendship relation?
Every user in the system should have a list of it's friends and I thought that this might be an incredibly easy task (just use a ManyToMany relation, right?). So I went on to try the following:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null
}
The problem now is that it tells me I use ManyToMany wrongly by having no clear distinction between friend and befriended. So far so good, I get the error, but how can I do what I want?
Any idea? I've reached the end of my wisdom.
The fact about Many to Many is that it needs a little more configuration, because its imperative that Hibernate generates a relation table.
Try this:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
#JoinTable(name = "user_friends",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "friend_id"))
protected List friends = null;
#ManyToMany(mappedBy = "friends")
protected List befriended = null;
}
Hope it works =)
EDIT: Also, be very careful with fetch types... you can enter an user fetching out-of-control loop and get all the DB.
The ManyToMany annotation has a mappedBy parameter. I guess something like
#ManyToMany(mappedBy = "friends")
might work. In any case, see the docs.
Edit:
It seems as if a many to many relationship needs to be accessible from both ends. Currently yours is only accessible from one end. Maybe you should add the other relation as well:
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null;
#ManyToMany(mappedBy = "friends")
protected List<User> befriended = null;
}
If that doesn't work you can always introduce a separate class representing the relation between two users, and let every user have a collection of instances of this class.
Good point, and in fact I tried that. Problem is that I then get a complaint about the mappedBy attribute being set on both sides of the relationship, which is true, but invalid.
I was wondering wether a simple #ManyToMany with some clever custom query to fetch the friends might be a solution:
The ManyToMany would generate a join table with user_id and friend_id, but the query would match either of the fields, returning all users where that match either the friend_id or the user_id that is.
Any ideas?
That of course would be a good fix, yet I'm not yet completely sold. Basically to get the friends I'd have to merge the two collections, which is quite unsatisfactory.
Is there absolutely no way to create an idempotent, reflexive relation in hibernate?
I had the same problem today as well.
#Entity
#Table(name="users")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="userid")
protected Long id = null;
#ManyToMany
protected List<User> friends = null
}
Should be ok, I used a similar structure. However I got lazy loading exceptions and when setting the fetchType to EAGER, I got complaints about recursive initialisation of bags.
How I fixed my problem: In the query you use to fetch the 'user', do something like:
from User as u left join fetch u.friends
This will initialise the list of friends for that entity. Be warned though that it doesn't initialise any friends from those friends. That is a good thing actually.
Related
I have these 2 entity with many to many relationship.
#Entity
public class User {
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<User> users = new ArrayList<User>();
}
#Entity
public class Language {
#ManyToMany(mappedBy = "languages")
private List<User> users = new ArrayList<User>();
}
I already have 20 languages saved in my language table. Now, I want to create a user and give relate that user with first language in the language table. So I did something like this
Language selectedLanguage = languageService.findById(1);
stammdaten.getLanguages().add(selectedLanguage);
stammdatenService.save(stammdaten);
But this gives me error org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.outgoing.Entity.Language. So how can I save this many to many relation. One thing to note here: I don't want to add new language. I want to add new user with already created languages.
Replace CascadeType.ALL with CascadeType.MERGE.
Also, add setters on both entities.
I define many to many relation through #ManyToMany annotation in JPA.
I have written a code example to insert.
I think this image will help you understand
So, I have found myself in quite a pickle regarding Hibernate. When I started developing my web application, I used "eager" loading everywhere so I could easily access children, parents etc.
After a while, I ran into my first problem - re-saving of deleted objects. Multiple stackoverflow threads suggested that I should remove the object from all the collections that it's in. Reading those suggestions made my "spidey sense" tickle as my relations weren't really simple and I had to iterate multiple objects which made my code look kind of ugly and made me wonder if this was the best approach.
For example, when deleting Employee (that belongs to User in a sense that User can act as multiple different Employees). Let's say Employee can leave Feedback to Party, so Employee can have multiple Feedback and Party can have multiple Feedback. Additionally, both Employee and Party belong to some kind of a parent object, let's say an Organization. Basically, we have:
class User {
// Has many
Set<Employee> employees;
// Has many
Set<Organization> organizations;
// Has many through employees
Set<Organization> associatedOrganizations;
}
class Employee {
// Belongs to
User user;
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Organization {
// Belongs to
User user;
// Has many
Set<Employee> employees;
// Has many
Set<Party> parties;
}
class Party {
// Belongs to
Organization organization;
// Has many
Set<Feedback> feedbacks;
}
class Feedback {
// Belongs to
Party party;
// Belongs to
Employee employee;
}
Here's what I ended up with when deleting an employee:
// First remove feedbacks related to employee
Iterator<Feedback> iter = employee.getFeedbacks().iterator();
while (iter.hasNext()) {
Feedback feedback = iter.next();
iter.remove();
feedback.getParty().getFeedbacks().remove(feedback);
session.delete(feedback);
}
session.update(employee);
// Now remove employee from organization
Organization organization = employee.getOrganization();
organization.getEmployees().remove(employee);
session.update(organization);
This is, by my definition, ugly. I would've assumed that by using
#Cascade({CascadeType.ALL})
then Hibernate would magically remove Employee from all associations by simply doing:
session.delete(employee);
instead I get:
Error during managed flush [deleted object would be re-saved by cascade (remove deleted object from associations)
So, in order to try to get my code a bit cleaner and maybe even optimized (sometimes lazy fetch is enough, sometimes I need eager), I tried lazy fetching almost everything and hoping that if I do, for example:
employee.getFeedbacks()
then the feedbacks are nicely fetched without any problem but nope, everything breaks:
failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session
The next thing I thought about was removing the possibility for objects to insert/delete their related children objects but that would probably be a bad idea performance-wise - inserting every object separately with
child.parent=parent
instead of in a bulk with
parent.children().add(children).
Finally, I saw that multiple people recommended creating my own custom queries and stuff but at that point, why should I even bother with Hibernate? Is there really no good way to handle my problem relatively clean or am I missing something or am I an idiot?
If I understood the question correctly it's all about cascading through simple 1:N relations. In that case Hibernate can do the job rather well:
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "post", orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
}
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private Post post;
}
Code:
Post post = newPost();
doInTransaction(session -> {
session.delete(post);
});
Generates:
delete from Comment where id = 1
delete from Comment where id = 2
delete from Post where id = 1
But if you have some other (synthetic) collections, Hibernate has no chance to know which ones, so you have to handle them yourself.
As for Hibernate and custom queries, Hibernate provides HQL which is more compact then traditional SQL, but still is less transparent then annotations.
I am using hibernate with JPA annotations for relationship mapping.
I have three entities in my code User Group & User_Group
User & Group are in a ManyToMany relationship.
User_Group is a kinda bridge table but with some additional fields. So here is the modified mapping code.
User
#Entity
#Table(name = "USERS")
public class User {
#OneToMany(mappedBy = "user")
private Set<UserGroup> userGroups
}
Group
#Entity
#Table(name = "GROUPS")
public class Group {
#OneToMany(mappedBy = "group")
private Set<UserGroup> userGroups
}
UserGroup
#Entity
#Table(name = "USERS_GROUPS")
public class UserGroup {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "GROUP_ID")
private Group group;
}
When I set the user & group object to the usergroup & save it.
User user = new User("tommy", "ymmot", "tommy#gmail.com");
Group group = new Group("Coders");
UserGroup userGroup = new UserGroup();
userGroup.setGroup(group);
userGroup.setUser(user);
userGroup.setActivated(true);
userGroup.setRegisteredDate(new Date());
session.save(userGroup);
Things work fine. With CascadeType.ALL the group object & user object are updated too. But when I delete the userGroup object. The child object are deleted too.
Deletion of child objects is a strict no no.
There is no CascadeType.SAVE-UPDATE in JPA, which just does save or update but no delete. How do I achieve this.
If I remove the CascadeType.ALL from the mapping the child objects don't get updated & I need them to be updated.
SAVE_UPDATE is for save(), update(), and saveOrUpdate(), which are 3 Hibernate-proprietary methods. JPA only has persist() and merge(). So, if you want to use cascading on Hibernate-proprietary methods, you'll need to use Hibernate-proprietary annotations. In this case, Cascade.
Or you could stop using the Hibernate Session, and use the standard JPA API instead.
CascadeType.ALL includes CascadeType.REMOVE too.
The solution is to use all CascadeType.* you need except CascadeType.REMOVE, like so:
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}))
in your UserGroup definitions.
It's almost always a code smell when propagating from child to parent entity, it should be the other way round.
From Cascading best practices:
Cascading only makes sense only for Parent – Child associations (the
Parent entity state transition being cascaded to its Child entities).
Cascading from Child to Parent is not very useful and usually, it’s a
mapping code smell.
From Hibernate best practices:
Avoid cascade remove for huge relationships
Most developers (myself included) get a little nervous when they see a
CascadeType.REMOVE definition for a relationship. It tells Hibernate
to also delete the related entities when it deletes this one. There is
always the fear that the related entity also uses cascade remove for
some of its relationships and that Hibernate might delete more
database records than intended. During all the years I’ve worked with
Hibernate, this has never happened to me, and I don’t think it’s a
real issue. But cascade remove makes it incredibly hard to understand
what exactly happens if you delete an entity. And that’s something you
should always avoid. If you have a closer look at how Hibernate
deletes the related entities, you will find another reason to avoid
it. Hibernate performs 2 SQL statements for each related entity: 1
SELECT statement to fetch the entity from the database and 1 DELETE
statement to remove it. This might be OK, if there are only 1 or 2
related entities but creates performance issues if there are large
numbers of them.
I am quite new to hibernate and programming with databases in general to be honest...
I've tried to save some graph-like structure to database.
Suppose I have Java class like this:
public class User {
#OneToMany(cascade=CascadeType.ALL)
private Collection<User> followers = new ArrayList<>();
#OneToMany(cascade=CascadeType.ALL)
private Collection<User> friends = new ArrayList<>();
#Id
private String name;
.....
}
The problem is I want to save it to PostgeSQL database using Hibernate. However I found it quite nontrivial. The one problem for example is:
Suppose I do:
User user1 = new User("user1");
User user2 = new User("user2");
user1.getFollowers().add(user2);
user1.getFriends().add(user2);
Now if I do merge on user1 object there will be issue with key uniqueness constraint. I wonder if this is the issue because I misconfigured Hibernate annotations to save my structure or it is entirely wrong approach to represent graph in such a way using Hibernate ? Any help much appreciated.
Personally I would go for a generated ID column, and would not use the user's name for that. Put a constraint on that name column in the database rather.
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column
private long id;
Otherwise I cannot see anything wrong with your approach. Do you get any exceptions while running your code?
I use EclipseLink for 9 months and so far no problem. Since I have the
need to query an entity with a OneToMany attribute, it's all the contrary.
It gives me a strange result.
I have simplified my entities until the maximum but the problem remains.
I will explain my need which is ultra simple : I have two entities :
Person which has a bidirectional relation with Address.
Person has potentially several Addresses but an Address belongs to one and
only Person.
In Classes, it gives that :
#Entity
public class Person implements Serializable {
#Id
private Long id;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
// Getter and setter
...
}
#Entity
public class Address implements Serializable {
#Id
private String idAddress;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "idPerson", referencedColumnName = "idPerson")
private Person person;
// Getter and setter
...
}
I want to query personne with their adresses. All that with some conditions
on personne and adresse.
My simplified query :
select pers FROM Person pers join pers.addresses address
where pers.matricule=:matricule
and address.date=:dateContract
When I execute it, I retrieve the right person but with all addresses
linked (with foreign key) with this person. Even the addresses which don't
match with the dateContract condition.
It seems that it's a problem related to the use of filtering on a oneToMany
attribute in my query. The problem is solved if i do several requests but
it will give low performances as I have several requests like this.
I have tried with the oneToMany in eager initialization and with a
fetch-join query hint but i have got the same result.
Thank you for having read me :)
PS : I have written the code manually, so a little typo is not impossible
David
Your query only returns persons. Once you get the persons, you're calling getAddresses(), which lazily loads the addresses of the person - all of them. In short, the query limits the set of returned persons, but since it only returns persons, the addresses are lazy-loaded using another query when accessing the set of addresses.
What you want to do is return the persons with some of their addresses in a single query. To do that, you need to use the fetch keyword:
select distinct pers FROM Person pers
join fetch pers.addresses address
where pers.matricule = :matricule
and address.date = :dateContract
Be very careful, though: this query returns an incorrect view of the person entity. You should make sure not to modify the collection of addresses of the returned persons (although since the addresses association is mapped by the Address.person association and there is no cascade, you should not have problems in this particular case).