Reputation: 127
I am developing an application using Spring rest and Hibernate and I want to get nested records from database like I'm getting Profession
for a User
, now I want to fetch Users
associated to the Profession
I fetched previously.
Here is my Dao class
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Profession> getProfessionById(long id) throws Exception {
session = sessionFactory.openSession();
Criteria cr = session.createCriteria(Profession.class);
cr.add(Restrictions.eq("uid", id));
List results = cr.list();
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return results;
}
Upvotes: 8
Views: 7101
Reputation: 3466
Fetching Strategies
There are four fetching strategies
For detail explanation, you can check on the Hibernate documentation.
FetchType.LAZY
is on demand
FetchType.EAGER
is immediate
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<User> getProfessionById(long id) throws Exception {
session = sessionFactory.openSession();
Criteria cr = session.createCriteria(Profession.class, "pro")
.setFetchMode("user", FetchMode.JOIN);
cr.add( Restrictions.eq("uid", id));
Profession pro = cr.uniqueResult();
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return pro.getUsers();
}
Upvotes: 2
Reputation: 9575
I think you are doing something wrong in your DAO class because you are open a session and a transaction inside your method and probably inside each one of the DAO's methods.
Doing this has a cost in performance and is bad conceptually, a transaction has to group a set of operations that will entirely success or fail. In most cases a database transaction will be related to an entire business operation, in this case a REST request. Something like
POST /user
@RestController.createUser
open transaction
UserDAO.saveUser
commit transaction
response
Also, if you look at your code you are opening a transaction and then closening.
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
In this case you are querying the database so a transaction is not needed at all.
Transactions are a cross-cutting concern in your application and given that you are already using Spring you should look at @Transactional annotation (or his xml equivalent) for achieve transactionality with AOP (Spring creates an aroud aspect). This would do your code much more readable and maintainable.
The answer of @ManojP has a good and a bad thing. I think you should avoid bidirectional relationships whenever you can because it makes harder the design. My advice is this: start always with unidirectional relationships and if you found a case where you can't avoid it, use it. The good thing is that is showing to you the use of lazyness when he does:
List<User> users = profession.getUsers();
This line of code should be outside of the DAO. What happens is that you have a list of users marked as lazy (is the default), then when you fetch the professions with the criteria query, a select over the table professions is triggered and the each one of the Profession objects is constructed with a proxy collection instead of the true collection. When you call profession.getUsers() a new select is trigerred over the table of users where profession_id = profession.getId(). So:
But beware! if you have a collection of professions (like I think you have because you're returning a List) and iterate over each profession and ask the list of users you'd be doing:
This will have a bad performance.
The answer of @farvilain is good to. But in this case you will always retrieving a Profession with it collection of Users because inside your DAO you are always using FetchMode.JOIN, and then you are loosing the benefit of lazyness. So, if you always want the list of users when you are querying Professions, use lazy=true for the collection of Users, but be aware of the costs that may have (if User have a non-lazy collection A to when you query Professions you will also retrieve the list of Users plus the collection A). If you don't want this, maybe you could have this signature:
public List<Profession> getProfessionById(Long id, FetchMode fetchMode) throws Exception
This is not very nice but shows that the DAO client could choose the fecth mode.
Upvotes: 2
Reputation: 1516
Use ManyToMany mapping to get the Professions, So that the assosoated Users will also come.check this link.
Upvotes: 0
Reputation: 2562
Starting from your code
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Profession> getProfessionById(long id) throws Exception {
session = sessionFactory.openSession();
Criteria cr = session.createCriteria(Profession.class);
cr.add(Restrictions.eq("uid", id));
List results = cr.list();
tx = session.getTransaction();
session.beginTransaction();
tx.commit();
return results;
}
I first will propose you to use Transactionnal annotation if you're using Spring. It makes the code clearer. Then stop using SuppressWarnings, it's uggly and useless, you can configure your IDE to hide them.
@Transactionnal(readonly = true)
public List<Profession> getProfessionById(long id) throws Exception {
Criteria cr = session.createCriteria(Profession.class);
cr.add(Restrictions.eq("uid", id));
List results = cr.list();
return results;
}
No use the fact that criteria are fluent API and do not create local variable, it's now uneeded.
@Transactionnal(readonly = true)
public List<Profession> getProfessionById(long id) throws Exception {
return session
.createCriteria(Profession.class)
.add(Restrictions.eq("uid", id))
.list();
}
Let's now solve your problem
@Transactionnal(readonly = true)
public List<Profession> getProfessionById(long id) throws Exception {
return session
.createCriteria(Profession.class)
.add(Restrictions.eq("uid", id))
.setFetchMode("users", FetchMode.JOIN)
.list();
}
It requires of course this line in profession;
public class Profession {
[...]
@OneToMany(mappedBy = "profession")
private Set<Users> users = new HashSet<>();
[...]
}
Warning
You don't need getter/setter, don't call reverse mapping when uneeded.
Be sure not to use List, Hibernate doesn't really like List without order declaration.
Do not declare users as fetched Eagerly in the entity, you will have horrific problem everytime you load a profession owned by lot of users, even if you're not good to fetch them by yourself.
Do not use FetchMode.EAGER, it's deprecated !
Upvotes: 1
Reputation: 6248
First you need to add below mapping in Profession and User entity class
In Profession class
//bi-directional many-to-one association to Users
@OneToMany(mappedBy="profession", cascade=CascadeType.ALL)
private List<User> users;
public List<User> getUsers() {
return this.users;
}
public void setUsers(List<User> users) {
this.users = users;
}
In User entity class
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="profession_id")
private Profession profession;
Then you can fetch profession object by id using your existing DAO class code.
@SuppressWarnings({ "unchecked", "rawtypes" })
public List<Profession> getProfessionById(long id) throws Exception {
session = sessionFactory.openSession();
Profession profession = (Profession) session.get(Profession.class, id);
// here you can get list of users for this profession
// List<User> users = profession.getUsers();
return profession;
}
Upvotes: 2
Reputation: 153780
According to you query, the Profession table has a uid
column which is probably a FK to the Users
table and I think the Users
table should have a FK to the Profession instead.
So the Users
table will have a many-to-one
association to a Profession
:
@ManyToOne
private Profession profession;
and the Profession
can associate all Users having that particular profession, so in the Profession
entity you have the inverse side of this association:
@OneToMany(mappedBy = "profession")
private List<Users> users = new ArrayList<>();
Now to get all users having a profession you can run a simple query like this:
List<Users> users = (ist<Users>) session.createQuery(
"select u " +
"from Profession p " +
"join fetch p.users u " +
"where p.id = :id")
.setParameter("id", proffesionId)
.list();
Upvotes: 2
Reputation: 4643
You might be able to use the following:
Criteria cr = session.createCriteria(Profession.class);
cr.setFetchMode("user", FetchMode.EAGER);
cr.add(Restrictions.eq("uid", id));
List results = cr.list();
I haven't used Hibernate criteria, but a quick google came up with something similar.
Upvotes: 1