JPA join by id not entity - jpa

I have created an query with criteria api that retrieves an entity by another linked entity:
public List<Booking> getBookingsByUser(User user) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Booking> createQuery = cb.createQuery(Booking.class);
Root<Booking> booking = createQuery.from(Booking.class);
Join<Booking, UsersProjects> join = booking.join(Booking_.userProject, JoinType.INNER);
createQuery.where(cb.equal(join.get(UsersProjects_.user), user));
createQuery.select(booking);
return em.createQuery(createQuery).getResultList();
}
This is working fine. But how to rewrite this to find entities by userId (Long)?
Metamodel of User has User_.id (SingularAttribute).
User is also an Entity. And a "UsersProject" hast exactly one User and one Project.

Add one more join clause between UserProjects and User:
Join<Booking, UsersProjects> userProjectsJoin = booking.join(Booking_.userProject, JoinType.INNER);
Join<UsersProjects, User> userJoin = userProjectsJoin.join(UserProjects_.user);
createQuery.where(cb.equal(userJoin.get(User_.id), userId));

Related

Bulk update over spring data jpa with join

I'm having troubles doing a bulk update (JPA 2.1) with spring-data-jpa.
According to docs:
5.3.8. Modifying queries
All the sections above describe how to declare queries to access a given entity or collection of entities. Of course you can add custom modifying behaviour by using facilities described in Custom implementations for Spring Data repositories. As this approach is feasible for comprehensive custom functionality, you can achieve the execution of modifying queries that actually only need parameter binding by annotating the query method with #Modifying:
#Modifying
#Query("update User u set u.firstname = ?1 where u.lastname = ?2")
int setFixedFirstnameFor(String firstname, String lastname);
But the entity I need update has a #ManyToOne relationship with another entity.
#Entity
#Table(name = "usuarios", indexes = {
#Index(name = "idx_usuarios_rut", columnList = "rut")
,#Index(name = "idx_usuarios_email", columnList = "email")})
public class User extends BaseEntity implements UserDetails {
...
#NotNull(message = "Debe seleccionar un nivel")
#ManyToOne(fetch = FetchType.LAZY)
public Role getRole() {
return role;
}
}
So my UPDATE is:
#Modifying
#Transactional
#Query("update User u set u.password = ?2 where u.company = ?1 and u.role.name not in ('ROLE_ADMIN', 'ROLE_ROOT')")
int updatePasswordForAll(Company company, String password);
The resulting native query is update usuarios cross join set password=? where company_id=? and (nombre not in ('ROLE_ADMIN' , 'ROLE_ROOT')) so I get com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual ...
What am I doing wrong ?
I tried with
#Query("update User u set u.password = ?2 join u.role r where u.company = ?1 and r.name not in ('ROLE_ADMIN', 'ROLE_ROOT')")
But this one is a bad formed update sentence org.hibernate.QueryException: node to traverse cannot be null! [update cl.arvisoft.mantenimiento.jpa.User u set u.password = ?2 join u.role r where u.company = ?1 and r.name not in ('ROLE_ADMIN', 'ROLE_ROOT')].
In JPQL you cannot join entities when doing update operation (see reference).
To workaround this situation try to use a sub-select, something like this (not tested):
update
User u
set
u.password = ?2
where
u.company = ?1 and
u.role in (select r from Role r where r.name not in ('ROLE_ADMIN', 'ROLE_ROOT'))

How to delete or update using inner join in Entity Framework?

I need to delete some records using inner join in Entity Framework.
For example, I have User, Role and UserRoleMapping tables:
User => Id, Name
Role => Id, Name
UserRoleMapping => Id, UserId, RoleId
Now I need to delete the users who belong to role with Id = 2.
I need to fire the query as shown below
Delete user
from User
inner join UserRoleMapping on User.Id = UserRoleMapping.UserId
where UserRoleMapping.RoleId = 2
Is this is possible in Entity Framework?
In EF you need first load entities, select items and then DeleteObject . You need do it like:
using (var context = new YourContext())
{
var item = (from user in context.User
join userRoleMapping in context.UserRoleMapping on user.Id equals userRoleMapping.UserId
where userRoleMapping.RoleId == 2
select user).ToList().ForEach(context.User.DeleteObject);
context.SaveChanges();
}
Note:
ObjectContext.DeleteObject(entity) marks the entity as Deleted in the context. (It's EntityState is Deleted after that.) If you call SaveChanges afterwards EF sends a SQL DELETE statement to the database. If no referential constraints in the database are violated the entity will be deleted, otherwise an exception is thrown
or
using (var context = new YourContext())
{
var items = (from user in context.User
join userRoleMapping in context.UserRoleMapping on user.Id equals userRoleMapping.UserId
where userRoleMapping.RoleId == 2
select user).ToList();
foreach (var item in items)
{
context.Entry(item).State = EntityState.Deleted;
}
context.SaveChanges();
}
or using ExecuteStoreCommand, here you find more
using (var context = new YourContext())
{
context.ExecuteStoreCommand("DELETE FROM USER INNER JOIN USERROLEMAPPING ON USER.ID = USERROLEMAPPING.USERID WHERE USERROLEMAPPING .ROLEID = {0}", customId);
}

QueryDsl - OR statement not working

I have the following QueryDSL query:
QCustomer customer = QCustomer.customer;
BooleanBuilder builder = new BooleanBuilder();
builder.or(customer.person.name.containsIgnoreCase(query));
builder.or(customer.company.name.containsIgnoreCase(query));
return builder;
And I expect to get results from Persons that contains the name = query and/or Companies that contains the query parameter. But I get nothing.
This is my Customer class mapping:
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Company company;
#OneToOne(orphanRemoval = false, optional = true, cascade = CascadeType.ALL)
private Person person;
Did someone knows what I'm missing here?
I expect to get a query like this:
select o
from Customer
where o.person.name like '%:name%' or o.company.name like '%:name%'
This is the generated query:
select
count(customer0_.uid) as col_0_0_
from
Customer customer0_
cross join
Person person1_
cross join
Company company2_
where
customer0_.person_uid=person1_.uid
and customer0_.company_uid = company2_.uid
and (lower(person1_.name) like ? escape '!' or lower(company2_.name) like ? escape '!') limit ?
It uses a count because it's the first query that Spring Data use to paginate the result.
The query looks ok. Most probably you get wrong results because the implicit property based joins make the joins inner joins.
Using left joins you might get the results you need.
QPerson person = QPerson.person;
QCompany company = QCompany.company;
BooleanBuilder builder = new BooleanBuilder();
builder.or(person.name.containsIgnoreCase(str));
builder.or(company.name.containsIgnoreCase(str));
query.from(customer)
.leftJoin(customer.person, person)
.leftJoin(customer.company, company)
.where(builder);

Troubles with JPA criteria API and multiple subqueries

I am struggling with the JPA Criteria API for formulating a query for my data structure. Ok, my entities are as follows. I have users and groups (both share a common base class OrgEntity). Logically, users can be members in multiple groups of course. Finally, I have an entity representing a task, which has a list of potential owners (that can be either single users or whole groups). The domain model is summarized below and is given, so I cannot change it.
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
abstract public class OrgEntity {
#Id
public String name;
...
}
#Entity
public class User extends OrgEntity {
public String displayName;
#ManyToMany(mappedBy="members")
public List<Group> groups;
...
}
#Entity
public class Group extends OrgEntity {
#ManyToMany
public List<User> members;
...
}
#Entity
public class Task {
#Id
public String uuid;
#ManyToMany
public List<OrgEntity> potentialOwners;
...
}
The starting point for my query is a single instance of User. I want to know all the tasks where the user is a potential owner (regardless if the user is directly contained in the potentialOwners collection or member of a group that is contained in potentialOwners).
My first attempt using a named query was as follows
SELECT DISTINCT t FROM Task AS t JOIN t.potentialOwners po
WHERE (po IN (SELECT g FROM User u JOIN u.groups g WHERE u = :user)
OR po IN (SELECT u FROM User u WHERE u = :user))
It works, but I don't know if this is the most efficient way to do this. Any suggestions?
However, I have no idea how to implement this using the criteria API. Can somebody please help me with that.
Thanks
Ok, I finally figured out how to do it. If you are interested in my solution, here it is. u is the User object, basically the query parameter and em is the EntityManager instance.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
// specifies the result value of the query
CriteriaQuery<Task> cq = criteriaBuilder.createQuery(Task.class);
// start with the navigation at the task entity
Root<Task> from = cq.from(Task.class);
// join the potential owner organizational entities
Join<Task,OrgEntity> potentialOwners = from.join("potentialOwners");
// select the tasks but remove duplicates
CriteriaQuery<Task> select = cq.select(from).distinct(true);
// definition for subquery1: fetch the user instance
Subquery<User> subquery1 = cq.subquery(User.class);
// start at the User entities
Root<User> from1 = subquery1.from(User.class);
// select the whole user
subquery1.select(from1);
// based on the specified user
subquery1.where(criteriaBuilder.equal(from1, u));
// definition for subquery2: fetch all groups for given user
Subquery<Group> subquery2 = cq.subquery(Group.class);
// we start at the User entity
Root<User> from2 = subquery2.from(User.class);
// join to Group entities via the groups collection
Join<User, Group> groups = from2.join("groups");
// select the group entities only
subquery2.select(groups).distinct(true);
// and finally restrict to all groups of the specified user
subquery2.where(criteriaBuilder.equal(from2, u));
// order in descending order based on the unique task id
select.orderBy(criteriaBuilder.desc(from.get("uuid")));
// here we restrict to those tasks that have the potential
// owners either in the result set of subquery2 or subquery1
// additionally I've tried to filter for another restriction
// in the task (based on a like statement of the uuid)
select.where(criteriaBuilder.and(
criteriaBuilder.or(
criteriaBuilder.in(potentialOwners).value(subquery2),
criteriaBuilder.in(potentialOwners).value(subquery1)),
criteriaBuilder.like(from.<String>get("uuid"), "1%")));
TypedQuery<Task> typedQuery = em.createQuery(select);
List<Task> resultList = typedQuery.getResultList();

Join with JPA 2 criteria API

I have a basic structure of 2 domain entities:
User
UserDetails
Where a User holds (has a) UserDetails, and UseDetails has String userName.
Using JPA criteria API I would like to commit a simple query which loads a User by a given user-name.
In code, I would like it to look more or less like this:
public User findByUsername(String userName) {
CriteriaBuilder qb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> c = qb.createQuery(User.class);
Root<User> user = c.from(User.class);
Predicate condition = qb.equal(user.get(User_.userDetails.getuserName()), userName);
c.where(condition);
TypedQuery<User> q = entityManager.createQuery(c);
List<User> result = q.getResultList();
if (result.isEmpty()) {
return null;
}
return result.get(0);
}
But this doesn't work since getuserName() cannot be found under User_.userDetails.
I guess this is not the way to do that, maybe I need to implement a Join between those tables (User and UserDetails)?
How should I do it?
I can't try it now, but I think you must do something like:
Predicate condition = qb.equal(user.get(User_.userDetails).get(UserDetails_.userName), userName);