Mapping multiple collections in one entity in Hibernate - java

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?

Related

Hibernate good practice, lazy/eager loading and saving/deleting children (help me Hibernate sensei)

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.

Hibernate JPA uniqueness

Let's say that this is a class that has unique constrained field.
#Entity
public class Thing {
#Column(name = "name", unique = true)
private String name;
#ManyToOne
private Owner owner;
}
Example works just fine if new Things are created with unique names. But when different owners want to create things with the same name this approach fails.
Is it possible to set unique constraint to differ records of Things in the database based on the Owners using Hibernate/JPA functionalities (I could not find any) or should I write my own logic and dump the unique from #Column.
Perhaps it could be done with Hibernate Validator? Reading the docs I haven't found much about unique constraints.
You're looking for #UniqueConstraint
http://docs.oracle.com/javaee/5/api/javax/persistence/UniqueConstraint.html

Data inserted into jointable entity always coming up as null - playframework

Been struggling with this one for a couple of days. I've got 2 models course and student. Each model is used as a form to register courses and students. I've created a separate entity called timetable which has a course object and a student object, instead of having a manytomany relationship. Here are the releavant sections of the code
Objects/lists from the class model
#Id
#GeneratedValue
public Long id;
#OneToMany(mappedBy = "course", cascade=CascadeType.ALL)
public List<Timtable> timetable;
Objects/lists from the student model
#Required
#Email
#Id
public String email;
#OneToMany(mappedBy = "student", cascade = CascadeType.ALL)
private List<Timetable> timetable;
Objects from the timetable model
#ManyToOne
#JoinColumn(name="email")
public Student student;
#ManyToOne
#JoinColumn(name="id")
public Course course;
Now, I "add" a course to the timetable through a form. The form has 2 hidden inputs with the email and the id. The mySQL table updates, but with NULL values instead of the values that are populated in the form. If I change the timetable variables from objects to primitive types it updates correctly, but when I change back the objects, and the manytoone/onetomany relationship, it just has NULL again. Any ideas on why this is happening?
I'm new to RDMS and ORMs. I've trawled a fair few resources to get the above code together. This is a good one: http://uaihebert.com/jpa-mini-book-first-steps-and-detailed-concepts/22/ (where I got the new class called timetable idea from) and this: http://lazylightening-tech.blogspot.co.uk/2013/07/manytomany-ebean-example.html the Beisar dude has also posted some really good stuff on it. I've been over too many of his posts on here and the google group to get to link, but most people have error messages. I'm not getting an error, it just isn't registering what I'm submitting. Can anyone help?
try writting up manual getters and setters..even i encountered this problem..if you are using eclipse then you can do..
right click + source + generate getters and setters
moreover go through this-
http://www.playframework.com/documentation/2.1.0/JavaEbean
PS - Because Ebean class enhancement occurs after compilation, do not expect Ebean-generated getter/setters to be available at compilation time. If you’d prefer to code with them directly, either add the getter/setters explicitly yourself, or ensure that your model classes are compiled before the remainder of your project, eg. by putting them in a separate subproject.

Inserting a Hibernate entity with relationship

Say I have these classes:
public class Loan {
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "lender_id")
private User lender;
}
public class User {
#Id
private Long id;
#Column
private String userName;
#OneToMany
private List<Loan> loans;
}
Now, let's say I have the user (lender) id and in the DAO layer, I want to create a Loan based on the id of the lender?
I realize that I can do the following:
User u = userDao.getUserById(1234L);
loanDao.createLoan(u, "someLoan");
But I'm wondering if it's possible to do it without pre-loading the User record?
There isn't a good way to do that, in part because it would fundamentally lead to incorrect ORM code. You the programmer are responsible for managing the in memory state of the Entities and keeping them correct. If you create a new Loan and say it belongs to a User, and a User has a collection of Loans, it is your responsibility to add that Loan to the User! (This has real consequences as soon as the caches get involved.)
You're using ORM, you need to think in terms of the objects and not in terms of the database. Adding a number in a foreign key column isn't what's important, setting up the correct in-memory representation of the object Model is what's important for you. The database is hibernate's problem.

Hibernate: Mapping User-Friends relation in Social Networks

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.

Categories

Resources