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.
Related
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.
In hibernate we can map entity relationships with one to one, one to many, etc. I am little bit skeptical about using the relationship annotations and I prefer to use individual find methods to retrieve child records. Example,
Lets consider I have two tables, User and Roles. Each user can have one role. So the entities are,
class User {
#Column
private String name;
#OneToOne(mappedBy="role_id")
private Role role;
.... getter/setter....
}
class Role {
...
}
Either we have to make eager fetch or it will lead to lazy initialisation exception if the role is accessed outside of the current session.
Instead of this, shall we have the mapping like this?
class User {
#Column
private String name;
#Column
private Long roleId;
....
}
This way, whenever we need the role details, we can get the role_id from the User object and query the role table? Is this a right approach? Yes, I know the benefit of loading object graph, but I think this approach will avoid the unnecessary eager fetches and will run seamlessly if we do database partitions.
(I always consider databases as just datastore and use use individual queries to retrieve data instead of using joins to avoid load on the DB).
Please let me know your thoughts.
I suggest using
class User {
#Column
private String name;
#OneToOne(mappedBy="role_id")
private Role role;
.... getter/setter....
}
class Role {
...
}
Don't see point in calling database select twice. Database can handel joins good and do select very fast so I don't see point to manually do it. Also when using this approach you can easy save/update/delete objects.
To avoid lazy initialisation exception you can use Hibernate.initialize(Object obj) as explained here How Hibernate.initialize() works.
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.
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 have two objects Customer and Transaction. They look something like this:
#Entity
class Transaction {
#Id
long id;
#Column("CUSTOMER_ID")
long customerId;
#ManyToOne
#JoinColumn(name="CUSTOMER_ID", insertable=false, updatable=false, nullable=false)
Customer customer
...
}
#Entity
class Customer {
#ID
long id;
...
}
The problem is we use fake customer numbers to identify some types of transactions. It's a legacy practice that I can do nothing to change and various other processes outside my control rely on it.
What I'd like to be able to do is get the customer number back for the dummy customer transactions and just have a null customer on my transaction object. For transactions that aren't to dummy customers I'd like to have the customer object set. Ideally I'd rather not have to test for the dummy customer numbers and issue separate queries.
How do I write a query and map my object to do that? Right now everything I try, I end up with an EntityNotFound exception for the dummy policy IDs.
using UNION you could select the proper customer record, unioned with a default 'NO SUCH CUSTOMER' record, make sure the default value comes second by ordering apropriatly and fetching only the first result.
In JPA if the ManyToOne does not reference anything you should just get null. Not sure how you are getting EntityNotFound. It may be a specific issue to your JPA provider.