Spring Data query: how to express 'at least one of the element of this list is member of...' - spring-data-jpa

For now, I have a Repository class, that has a Department and a list of Users.
For a connected user, I want to retrieve all repositories from a dedicated department or repositories where the user is listed.
Here is how it is managed using Spring Data query:
#Query(value = "select distinct repository " +
"from Repository repository " +
"left join repository.users " +
"where analysis.department =:department " +
"or :connectedUser member of repository.users ",
countQuery = "select count(distinct repository) from Repository repository")
Page<Analysis> findAllByUser(Pageable pageable,
#Param("department") Department department,
#Param("connectedUser") User connectedUser);
It is working well.
I have now to integrate a new feature.
The repository can be linked to a list of UserGroup.
I would like to update my method with something like this:
#Query(value = "select distinct repository " +
"from Repository repository " +
"left join repository.users " +
"left join repository.userGroups " +
"where analysis.department =:department " +
"or :connectedUser member of repository.users " +
"or (at least one element of :userGroupList) member of repository.userGroups ",
countQuery = "select count(distinct repository) from Repository repository")
Page<Analysis> findAllByUser(Pageable pageable,
#Param("department") Department department,
#Param("connectedUser") User connectedUser,
#Param("userGroupList") Set<UserGroup> userGroupList;
Is there a way to do that? If so, what should I use instead of (at least one element of userGroupList)?
Maybe, member of is not the best approach... If so, could you advise another one? My main objective is to be able to manage that in a single query to get page results.
Thanks in advance for all help and advice you could provide! :)
If it can help, here are entities used (high level, not in detail):
public class Repository {
private String name;
private Department department;
#ManyToMany
private Set<User> users = new HashSet<>();
#ManyToMany
private Set<UserGroup> userGroups = new HashSet<>();
public class UserGroup {
private String name;
private Department department;
private Set<User> users = new HashSet<>();

Related

JPA Query Left Join with A non-declared relation

I need to query an Entity with a left join with another entity. But I can only declare the ManyToOne relation on the left table:
First table:
#Entity
public class ShipyardModel {
private Long id
....etc
}
Second table (LEFT one):
#Entity
public class Boat{
private Long id;
...
#ManyToOne
#JoinColumn(name = "shipyard_model_id")
private ShipyardModel shipyardModel;
}
Because of serialization reasons I can't declare the #OneToMany relation in the first entity. But they are related, because in the Boat table, i have the shipyard_mode_id. So I created a DTO to query this guy for me and created a JPA query:
Query(" SELECT new br.com.easymarine.dto.ShipyardModelDTO" +
"(sm.id, sm.name, sm.commercialLength, sm.length, sm.width, sm.height, sm.beam, sm.weightWithoutMotor, sm.weightWithMotor, sm.modelYear, sm.shipType,s.id, s.name,count(b.id)) " +
"from ShipyardModel sm " +
"join sm.shipyard s " +
"left join Boat b on b.shipyardModel = sm GROUP BY sm.id")
List<ShipyardModelDTO> findAllShipyardModelDTO();
The Spring is giving me
Path expected for join!
error
Is it possible to execute this query the way it is?

How to solve duplicated rows in the JPQL query with many-to-one?

My query almost works but it returns duplicate rows, because the team name is duplicated. How can I solve this problem?
I have : "team glass", "team foot", and "team swim".
TeamUserCompany has a many-to-one relationship to Team.
When I query for team names containing "team" I get duplicated rows because it contains "team glass", "team foot", and "team swim"!
In the front end I need to show companies!
#Query("SELECT distinct teamUserComp FROM TeamUserCompany teamUserComp WHERE teamUserComp.team.name like %?1% ");
Entity
public class TeamUserCompany extends AbstractEntity {
#ManyToOne(optional = false)
private Company company;
#ManyToOne
private User user;
#ManyToOne
private Team team;
}
If you want distinct companies you have to select distinct companies and not distinct TeamUserCompany:
SELECT distinct c
FROM TeamUserCompany tuc
JOIN tuc.company c
WHERE tuc.team.name like %?1%
I used a Group by it solved the problem.
#Query("SELECT distinct teamUserComp FROM TeamUserCompany teamUserComp WHERE teamUserComp.team.name like %?1% group by (teamUserInter.company)");

JPA - Find items from a list that don't exist in a table

Given a list of emails, I need to find which ones don't exist in a table. Using SQL, I can do the following:
SELECT e.email
FROM
(
VALUES('email1'),('email2'),('email3'),('email4')
) AS e(email)
EXCEPT
SELECT username FROM dbo.UsersTbl;
How can I write equivalent JPQL? In the application, values email1, email2... need be dynamically built (not hardcoded) based on passed in list. Using a Spring Data JPA native query, I can do the following:
#Query( value =
"SELECT e.email " +
" FROM " +
"( " +
" VALUES('email1'),('email2'),('email3'),('email4') " +
" ) AS e(email) " +
" EXCEPT " +
" SELECT username FROM dbo.UsersTbl ",
nativeQuery=true)
List<String> findMissingEmails(List<String> emails);
But how can I pass in the list of emails to the query?
For fixed number of email arguments, this could work:
#Query( value =
"SELECT e.email " +
" FROM " +
"( " +
" VALUES(:email1),(:email2),(:email3),(:email4) " +
" ) AS e(email) " +
" EXCEPT " +
" SELECT username FROM dbo.UsersTbl ",
nativeQuery=true)
List<String> findMissingEmails(String email1, String email2, String email3, String email4);
For high and/or dynamic number of emails, a better approach could be to use NativeQuery constructed at runtime.
Old answer - more or less exactly the opposite of what was asked for, but I'll keep it here as reference.
Using of named parameter:
#Query("SELECT u.email FROM User AS u WHERE u.email NOT IN (:emails)")
List<String> findMissingEmails(#Param("emails")Collection<String> emails);
Alternatively, you could use a JPA query method:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findAllByEmailNotIn(Collection<String> emails);
}
Unfortunately that method would fetch and return a list of Users instead of list of their emails.
To fetch just emails you could use a JPA projection.
Assuming that User entity has a field of type String named email, the following projection could be used:
public interface UserEmail {
String getEmail();
}
And this is the repository method:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
List<UserEmail> findAllByEmailNotIn(Collection<String> emails);
}

JPQL LIKE expression with attributes of target entity in a null valued relationship

I have an entity named Item with relationship to another entity Category which can be null. The two entities are as follows:
Item
#Entity
public class Item {
#Id
private String id;
private String name;
private String code;
#ManyToOne
private Category category;
}
Category
#Entity
public class Category {
#Id
private String id;
private String name;
}
Now, I have to select items having name, code or category name similar to a search term, for which I tried the following query:
"SELECT item FROM Item item "
+ "WHERE item.code LIKE :searchTerm OR item.name LIKE :searchTerm "
+ "OR item.category.name LIKE :searchTerm"
searchTerm is set using
query.setParameter("searchTerm", "%" + searchTerm + "%");
But it doesn't result will be empty if category is null (otherwise works). I tried the following query also.
"SELECT item FROM Item item "
+ "WHERE item.code LIKE :searchTerm OR item.name LIKE :searchTerm "
+ "OR (item.category IS NOT NULL AND item.category.name LIKE :searchTerm)"
This also didn't work. How can I make it to check for category name only if category is not null?
Try to use a left join:
"SELECT item FROM Item item LEFT JOIN item.category cat"
+ "WHERE item.code LIKE :searchTerm OR item.name LIKE :searchTerm "
+ "OR (cat.categoryID IS NOT NULL AND cat.name LIKE :searchTerm)"
JPQL documentation
Simply check null for the above parameterized value. The jpa does not return null pointer exception because we give the null only within the double quotes. So like java, it take null as a string and tries the value of '%null%' from your db. So that, it returns empty list.

Limit records on onetomany relationship in Play! Framework

I am trying to determine the best way to page/limit the rows returned when querying the children of a OneToMany relationship while using JPA in Play! Framework.
#Entity
public class User extends Model {
#OneToMany(mappedBy="user", cascade=CascadeType.ALL)
public List<CaseFolder> caseFolders;
}
public class CaseFolder extends Model {
#Required
#ManyToOne
public User user;
#Required
public String number;
public String description;
}
I realize I can set the relationship the lazy fetching. However, that still doesn't seem to stop me from retrieving the entire list of CaseFolders when I finally access user.caseFolders.
Ideally, I would like to be able to do something like:
user.getCaseFolders().start(100).limit(10)
but I can't find anything about doing that "out of the box".
Does everyone really bring the entire related data set into memory every time they need a few of the "children" of a 1-m relationship?
I'm implementing a UI that has paging (using jqgrid) and a user can potentially have thousands of records.
After further research, here's the answer I came up with:
The "many" side of the relationship - the collection of all caseFolders - is a property of the User. By definition, that property's value is the entire collection.
To obtain a subset of those elements, I added the following method to my User model:
public List<CaseFolder> getCaseFolders(String sidx, String sord, int start, int limit) {
String orderBy = "c." + sidx + " " + ((sord.toUpperCase().equals("ASC")) ? "ASC" : "DESC");
String jpql = "SELECT c " +
"FROM CaseFolder c " +
"WHERE c.user = :user " +
"ORDER BY " + orderBy;
Query query = JPA.em().createQuery(jpql)
.setParameter("user", this)
.setFirstResult(start)
.setMaxResults(limit);
List<CaseFolder> result = query.getResultList();
return result;
}