Custom hibernate criteria (one-to-many relationship) - java

I need to create custom Hibernate query.
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> user = criteria.from(User.class);
List<Predicate> restrictions = new ArrayList<>();
restrictions.add(builder.equal(user.get("firstname"), user.getFirstName()));
List<User> users = (List<User>) entityManager.createQuery(criteria).getResultList();
I need to add to restrictions additional criteria. I have additional model Photo, which connected to model User with foreign key user_id.
I need to find users, which has any photos.
User model contains:
private List<Photo> photos;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "photo", cascade = CascadeType.ALL)
#Fetch (FetchMode.SELECT)
#JsonIgnore
public List<Photo> getPhotos() {
return photos;
}
Photo model contains:
private User user;
#ManyToOne
#JoinColumn(name="user_id")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
UPDATED
I need something like this:
restrictions.add(builder.isNotNull(user.get("photos")));
but it's not working.
Problem is not with how to combine 2 restrictions, problem with how to add restriction isNotNull... User know nothing about photos. Photos know about user as they has user_id.
UPDATED2:
org.postgresql.util.PSQLException: ERROR: syntax error at or near "."
SQL:
select * from User user0_ cross join Photo photos1_ where user0_.id=photos1_.user and (. is not null)
I need follow SQL:
SELECT DISTINCT user.* FROM user
LEFT JOIN photo
ON user.id = photo.user_id;

Try this:
restrictions.add(Restrictions.and(Restrictions.eq(user.get("firstname"), user.getFirstName()),Restrictions.isNotNull(user.get("photos")));
UPDATE: The bidirectional relationship you are looking for is something like this:
Photo class:
#ManyToOne
#JoinColumn(name="user")
private User user;
User class:
#OneToMany(mappedBy="user")
private Set<Photo> photos;
In this one you have it mapped both ways so you can access photos from the user and user from photos.

I found solution myself. Full code:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> user = criteria.from(User.class);
Root<Photo> photo = criteria.from(Photo.class);
List<Predicate> restrictions = new ArrayList<>();
restrictions.add(builder.equal(user.get("firstname"), user.getFirstName()));
restrictions.add(builder.equal(user.get("id"), photo.get("user")));
List<User> users = (List<User>) entityManager.createQuery(criteria).getResultList();

Related

Using JPA Entity Graph in DAO

I have defined the JPA Entity Graph on my Entity class, and it looks like follows.
UserTable.java
#Entity
#Table(name = "USER")
#NamedEntityGraph(
name = "user-entity-graph-with-photos",
attributeNodes = {
#NamedAttributeNode(value = "photos"),
})
public class UserTable {
#Id
#Column(name = "USER_ID", nullable = false)
private Long userId;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<PhotoTable> photos = new HashSet<>();
The requirement is that sometimes I want to fetch the users along with the photos, and in some other cases I only want the users to be loaded from the database, but not the photos.
Now, I have created a DAO class for the User - UserDAO.java. In there, I have two methods, each for one case.
public Optional<UserTable> findByEmail(String email) {
final TypedQuery<UserTable> query = entityManager.createQuery(
"SELECT e FROM UserTable e WHERE e.email = :email", UserTable.class);
return Optional.ofNullable(query.setParameter("email", email).getSingleResult());
}
public Optional<UserTable> findByEmailWithPhotos(String email) {
final TypedQuery<UserTable> query = entityManager.createQuery(
"SELECT e FROM UserTable e WHERE e.email = :email", UserTable.class);
return Optional.ofNullable(query
.setParameter("email", email)
.setHint("javax.persistence.loadgraph", entityManager.getEntityGraph("user-entity-graph-with-photos"))
.getSingleResult());
}
I am a bit worried about the API in the DAO layer, since it now contains 2 methods like findByEmail and findByEmailWithPhotos which also loads the photos eagerly. Is this the correct approach? Should we really use one DAO method for each defined entity graph? Would some kind of a builder pattern be more effective here? Any advice is appreciated.
UPDATE
To explain further what I feel is bad about this design is the following. Let's suppose we have 3 entity graphs on the user
user-graph-with-photos
user-graph-with-messages
user-graph-with-followers
Then in the DAO would need to have the following methods:
findUsers
findUsersWithPhotos
findUsersWithMessages
findUsersWithFollowers
findUsersWithPhotosAndMessages
findUsersWithPhotosAndFollowers
findUsersWithMessagesAndFollowers
findUsersWithPhotosAndMessagesAndFollowers

How to use hibernate lazy loading with column that can be null

This is my entity:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "id_city")
private City city;
//...
}
In my Repository I have:
public interface UserRepository extends JpaRepository<User, Long>{
#Query("SELECT u FROM User u JOIN FETCH u.city")
public List<User> findAllUserForApi();
}
If there are any cities in table, findAllUserForApi() shows me full information about user:
[{"id":1,"name":"John","surname":"Pillman","city":{"id":1,"name":"New York"}]
If there are no cities, I want to get at least [{"id":1,"name":"John","surname":"Pillman","city":null]
But I've got nothing: []
Help me please.
Given that you are already using a custom query, the simplest solution would be a LEFT JOIN FETCH:
#Query("SELECT u FROM User u LEFT JOIN FETCH u.city")
This way all users will be loaded regardless of whether they have a city or not; and for those who have a city, it'll be available by user.getCity().
Why you write custom query here. You dont need.
Firstly you have to follow general convention:
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "CITY_ID")
private City city;
...
And here JPA shows all information related with User.
public interface UserRepository extends JpaRepository<User, Long>{
public List<User> findAll();
}
It looks like you are trying to use Lazy Loading with a predefined Query, I don't think this is going to work.
See, the JOIN FETCH in your query state the following:
Get all the Users which has u.City
So if you don't have a u.City for a user, the return would be empty.
More info on Join and Fetch
What you really want is the following:
public User findUserByID(Long userId)
{
Session session = sessionFactory.getCurrentSession();
User user = (Users) session.createCriteria(User.class).add(Restrictions.idEq(userId)).uniqueResult();
// this will force SQL to execute the query that will join with the user's city and populate
// the appropriate information into the user object.
Hibernate.initialize(user.geCity());
return user;
}
If the u.City is NULL, it will return a NULL. while the User object contains data.
Or in your case Find all users :
public List<User> findUserByID(Long userId)
{
Session session = sessionFactory.getCurrentSession();
List<User> users = (List<User>) session.createCriteria(User.class);
// this will force SQL to execute the query that will join with the user's city and populate
// the appropriate information into the user object.
for (User user : users)
Hibernate.initialize(user.geCity());
return user;
}
Note:
I did not test the code, this is pseudo so you may want to change some of it.
source

Java Hibernate Criteria. Select by the subclass property from subclass list

I have three Java hibernate entities. And I want using hibernate criteria get all Users who has pick with specific id in their picks list.
Users entity:
#Entity
#Table(name="users")
public class User {
...
#ManyToMany
private List<UserPick> picks = new ArrayList<UserPick>(0);
...
UserPick entity:
#Entity
#Table(name="usersPicks")
public class UserPick {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
#ManyToOne
private MatchPick pick;
...
I want get all of the users that have pick with specific pick.id, like:
// return all users if exist pick with pickId in picks list
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class);
criteria... //
return criteria.list();
}
// Goal is get target but using Hibernate criteries
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class);
List<User> users = criteria.list();
List<User> target = new List<>();
for(User u:users)
for(UserPick p:u.getPicks())
if(p.getId == pickId)target.add(u);
return target;
}
Work for me.
public List<User> getUsersByPick(int pickId) {
Criteria criteria = session.createCriteria(User.class)
.createCriteria("userPicks", "picks")
.createCriteria("pick", "pick")
.add( Restrictions.eq("pick.id",pickId) );
return criteria.list();
}

Hibernate like query on Many-to-Many collection

I am having Userentity which has mapped to user_roles. I want to filter these roles based on User.idand roles.name
Same like as this SQL query
SELECT ur
FROM user
JOIN user_roles ur
ON ur.user_id = user.id
WHERE user.id = 1
AND ur.name like '%admin%';
How to achieve this SQL query in hibernate?
How to pass parameter to role name?
User.java
#Entity
class User {
#ManyToMany
#JoinTable(name="user_roles",
joinColumns=#JoinColumn(
name="user_id", referencedColumnName="id"),
inverseJoinColumns=#JoinColumn(
name="role_id", referencedColumnName="id")
)
public Set<Role> getRoles() {
return roles;
}
}
Hope it will help
select "your_req" from User us join usr.role usr where usr.name like '%admin%' and us.id=1
HQL supports exactly what you want, you can create an HQL query and then pass parameters to it. The following code block may be a reference:
Long userId = 1L;
String roleNamePattern = "%admin%";
Query query = session.createQuery("SELECT role FROM User user JOIN user.roles role WHERE user.id = :userid AND role.name LIKE :rolename");
query.setLong("userid", userId);
query.setString("rolename", roleNamePattern);
List<Role> roles = query.list();

JPA Join on multiple columns

I have entities User, Organization and GrantedRole. Their relation is that a GrantedRole defines the role a User has in an Organization. All three entities inherit a field 'id' (annotated with #Id) from their superclass. Organization has no references to GrantedRole or User, those relations are unidirectional.
#Entity
#Table(name = "role_assignments", uniqueConstraints = #UniqueConstraint(columnNames = {
"user_id", "organization_id" }))
public class GrantedRole extends DomainEntity implements GrantedAuthority {
public static enum Role {
ROLE_MODM_USER, ROLE_MODM_ORGADMIN, ROLE_MODM_ADMIN
}
private User user;
private Role role;
private Organization organization;
public static enum Role {
ROLE_MODM_USER, ROLE_MODM_ORGADMIN, ROLE_MODM_ADMIN
}
#ManyToOne
#NotNull
public User getUser() {
return user;
}
#NotNull
#Enumerated(EnumType.STRING)
public Role getRole() {
return role;
}
#ManyToOne
public Organization getOrganization() {
return organization;
}
// Some setters
}
#Entity
#Table(name = "modmUsers")
public class User extends DomainEntity implements UserDetails {
// Some other fields
private Set<GrantedRole> roles = new HashSet<GrantedRole>();
private Set<Organization> organizations = new HashSet<Organization>();
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
public Set<GrantedRole> getRoles() {
return roles;
}
#ManyToMany(fetch = FetchType.EAGER)
public Set<Organization> getOrganizations() {
return organizations;
}
// Some setters
}
Now I want to use JPA Criteria to find the Organizations in which a User has the role ROLE_MODM_ORGADMIN. But the generated query joins on a single column, and that leads to repetition of the rows from GrantedRole, because User.id is not unique within GrantedRole, nor is Organization.id. The combination of both is unique.
My code for finding the Organizations:
public List<Organization> findOrganizationsByOrgAdminUser(String
CriteriaBuilder cb = entityManager.
CriteriaQuery<Organization> query = cb.createQuery(Organization.
Root<User> root = query.from(User.class);
SetJoin<User, Organization> joinOrg = root.joinSet("organizations");
SetJoin<User, GrantedRole> joinRoles = root.joinSet("roles");
Predicate p1 = cb.equal(root.get("id"), userId);
Predicate p2 = cb.equal(joinRoles.get("role"), Role.ROLE_MODM_ORGADMIN);
query.select(joinOrg);
query.where(cb.and(p1, p2));
return entityManager.createQuery(query).getResultList();
}
The generated query is:
SELECT
orgs.*
FROM
modmUsers users
INNER JOIN
modmUsers_organizations u_o
ON users.id=u_o.modmUsers_id
INNER JOIN
organizations orgs
ON u_o.organization_id=orgs.id
INNER JOIN
role_assignments roles
ON users.id=roles.user_id
WHERE
users.id=?
and roles.role=?
The query I would want is:
SELECT
orgs.*
FROM
modmUsers users
INNER JOIN
modmUsers_organizations u_o
ON users.id=u_o.modmUsers_id
INNER JOIN
organizations orgs
ON u_o.organization_id=orgs.id
INNER JOIN
role_assignments roles
ON users.id=roles.user_id
AND orgs.id=roles.organization_id //how to do this???
WHERE
users.id=?
and roles.role=?
Ok, so my colleague helped me out and led me to this query:
SELECT
orgs.*
FROM
modmUsers users
INNER JOIN
modmUsers_organizations u_o
ON users.id=u_o.modmUsers_id
INNER JOIN
organizations orgs
ON u_o.organization_id=orgs.id
INNER JOIN
role_assignments roles
ON users.id=roles.user_id
WHERE
users.id=?
AND roles.role=?
AND orgs.id=roles.organization_id
Which is a simple extension of my code:
public List<Organization> findOrganizationsByOrgAdminUser(String
CriteriaBuilder cb = entityManager.
CriteriaQuery<Organization> query = cb.createQuery(Organization.
Root<User> root = query.from(User.class);
SetJoin<User, Organization> joinOrg = root.joinSet("organizations");
SetJoin<User, GrantedRole> joinRoles = root.joinSet("roles");
Predicate p1 = cb.equal(root.get("id"), userId);
Predicate p2 = cb.equal(joinRoles.get("role"), Role.ROLE_MODM_ORGADMIN);
Predicate p3 = cb.equal(joinOrg.get("id"), joinRoles.get("organization"));
query.select(joinOrg);
query.where(cb.and(p1, p2, p3));
return entityManager.createQuery(query).getResultList();
}

Categories