How to get Distinct record from JPA - spring

I have implemented a method which gives me specification, but what I want is the query should be as below:
Select Distinct *
From (another select query)
I generate query dynamically.
How do I perform the same using specification in Spring Boot?

Try something like this
Specification<T> spec = getSpecification();
Specification<T> distinctSpec = (root, query, cb) -> {
query.distinct(true);
return spec.toPredicate(root, query, cb);
};

if you want to get distinct records, you have to write a query like this in the repository.
The below query gives the distinct author from the post table.
#Query("select distinct author from Post")
List<String> findUniqueAuthor();

Write this in the repository
#Query(value = "Select Distinct * From (another select query)", nativeQuery = true)
List<Object> findUniqueData();

Related

delete in list via Panache in Quarkus

Here’s what I want to do
delete from table where id in list_of_ids
I know Hibernate HQL can do that
Long[] ids = {1L, 2L, 3L};
Query query = session.createQuery("delete from SysMenu where id in (:id)");
query.setParameterList("id", ids);
int i = query.executeUpdate();
But what can I do if I want to use Panache-ORM?
with panache you can always use a simplified query, something like
SysMenu.delete("delete from SysMenu where id in ?", ids);
should work (handwritten, not tested).
Here you can see the method definition
It works with Panache
Long[] ids = {1414151951951728640L, 1414152114971742208L};
List<Long> list = Arrays.asList(ids);
long rows = SysMenu.delete("id in (?1)", list);

Why can JPQLs modifying queries only return void or int?

When i want to modify the database via JPQL i have to mark the query as Transactional and Modiyfing. If i do so, the return type of the method representing the query has to be either void or int(representing the number of edited rows i think). Why are only the two return types allowed? If i do a HTTP-PUT request and update the object with an own JPQL query, i would like to return the updated object again. Whats the best way to do it if the return type of the query has to be void or int? Do i have to do a seperate query/request again which selects the object after it was updated?
EDIT:
Thats how i call the query:
if (inactivityListDTO.getProjectIds().size() > 0) {
projectRepository.updateProjectsIsArchivedByProjectIds(inactivityListDTO.getProjectIds(), inactivityListDTO.getIsArchived());
}
Thats the query:
#Transactional
#Modifying
#Query("UPDATE Project project SET project.isArchived = :isArchived,
project.archivedDate = current_date " +
"WHERE project.id IN :ids")
void updateProjectsIsArchivedByProjectIds(#Param("ids") List<Long> ids, #Param("isArchived") boolean isArchived);
Because it finally boils down to execute a standard UPDATE SQL in the DB , and the UPDATE in standard SQL only returns the number of records being updated and does not return a result set.
And yes , if you need get a record 's value after update , you have to query it again. Alternatively , you should consider using a JPA way to update a record , which first query the object , then update it by changing its state . Something like below (Assume you are using spring #Transactional to manage the transactional boundary):
#Transactional
public void changeEmployeeSalary(Integer employeeId , Integer salary){
Employee employee = entityManager.find(Employee.class , employeeId);
employee.setSalary(salary);
}
In this way , you do not need to query the record again after it is updated and you also do not need to manually write a UPDATE SQL.

Spring data querying with complex object marked by #Param

I have an entity with aggregation information that I am going to receive from database:
class BookStats {
String author
String title
Integer count
}
My question is could I use some complex object in Repository to filter statistic information. Something like that:
#Query(value = "SELECT new com.test.book.BookStats(b.author, b.title, count(b)) from Book b where b.title = :filter.title and b.author= :filter.author")
List<BookStats> calculateBookStats (#Param("filter") Filter filter)
Spring Data JPA allows to use SpEL :
#Query(value = "SELECT new com.test.book.BookStats(b.author, b.title, count(b)) from Book b where b.title = :#{#filter.title} and b.author= :#{#filter.author}")
List<BookStats> calculateBookStats (#Param("filter") Filter filter)
More info here.

Using distinct in Spring data over multiple columns

My domain model is like this:
CollectedData {
String name;
String description;
int count;
int xAxis,
int yAxis
}
Using Spring data repository query, I would like to retrieve all the unique rows (unique with name, xAxis, yAxis)
I am trying something like this
#Query("select distinct a.name, a.xAxis, a.yAxis from CollectedData a")
List<CollectedData> findAllDistinctData();
So, when I do
List<CollectedData> records= findAllDistinctData();
for (CollectedData record : records) { //Exception on this line
}
Exception
[Ljava.lang.Object; cannot be cast to CollectedData.
Is there any other way to write query for this ?
#Query return ArrayList of Object(s) instead of specific type of object. so you have to define some thing like
#Query("select distinct a.name, a.xAxis, a.yAxis from CollectedData a")
List<Object> findAllDistinctData();
then cast according to your requirement,
List<Object> cdataList=findAllDistinctData();
for (Object cdata:cdataList) {
Object[] obj= (Object[]) cdata;
String name = (String)obj[0];
String description = (String)obj[1];;
...
}
Instead of returning an object you can use JPA's constructor expression feature to return a more specific object holding only the columns you're interested in. See also following answer:
JPQL Constructor Expression - org.hibernate.hql.ast.QuerySyntaxException:Table is not mapped
According to your example you could create a new Object with only the columns you are interested in:
SELECT DISTINCT new com.mypackage.MyInterestingCollectedData(a.name, a.xAxis, a.yAxis) from CollectedData a
If you want to select complete object based on distinct values of multiple columns,
In that case the native query would be the option.
e.g.
#Query(
value = "select distinct on (column1, column2, column3) * From my_table where someId=: order by column1 asc,column2 desc,column3 desc,column4 desc",
nativeQuery = true
)
fun finalAllDistinctBy(containerId: String): List<MyTable>

Spring pageable with specification add more LEFT JOIN on order by joined column

Can anybody explain to me why Spring adds LEFT JOIN checking on pageable Order using its specification feature?
Here is the case:
I want to use Spring specification to generate dynamic query for filtering data and pageable to limit the data. The query is between UserEntity and UserGroupEntity, and I want to always fetch the UserGroupEntity on every query, therefore I add a checking block for COUNT and data query. If the query is a query COUNT then I use the Join object, otherwise I use the Fetch object.
public static Specification<UserEntity> filtered(final String searchTerm) {
return new Specification<UserEntity>() {
#SuppressWarnings("unchecked")
#Override
public Predicate toPredicate(Root<UserEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Join<UserEntity, UserGroupEntity> joinUserGroup = null;
if (QueryHelper.isQueryCount(query)) {
joinUserGroup = root.join("userGroup");
} else {
Fetch<UserEntity, UserGroupEntity> fetchUserGroup = root.fetch("userGroup");
joinUserGroup = (Join<UserEntity, UserGroupEntity>) fetchUserGroup;
}
if (searchTerm != null) {
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(cb.like(root.get("lowerUsername").as(String.class), "%" + searchTerm.toLowerCase() + "%"));
predicates.add(cb.like(root.get("lowerEmail").as(String.class), "%" + searchTerm.toLowerCase() + "%"));
predicates.add(cb.like(joinUserGroup.get("lowerName").as(String.class), "%" + searchTerm.toLowerCase() + "%"));
cb.or(predicates.toArray(new Predicate[predicates.size()]));
}
return null;
}
};
}
This is how I call the specification on UserService:
Order order = new Order(Direction.ASC, "lowerEmail");
Page<UserEntity> pages = userRepository.findAll(filtered(searchTerm), new PageRequest(page, size, new Sort(order)));
The query is fine when I add an ordering using UserEntity column. Here is the generated HQL:
select generatedAlias0 from com.example.entity.UserEntity as generatedAlias0
inner join fetch generatedAlias0.userGroup as generatedAlias1
order by generatedAlias0.lowerEmail asc
But the query becomes strange (because I don't know why) when I use the the column from joined entity for ordering which is userGroup.lowerName. By using that column, Spring adds more LEFT JOIN in my query and makes it like this:
select generatedAlias0 from com.example.entity.UserEntity as generatedAlias0
left join generatedAlias0.userGroup as generatedAlias1
inner join fetch generatedAlias0.userGroup as generatedAlias2
order by generatedAlias1.lowerName asc
As I come across the spring-data-JPA code on Github, I found that the translation from specification to criteria is done in method getQuery(). This method calls other method toOrders() from class QueryUtils to apply the sort order. Method toOrders() finally calls method getOrCreateJoin() or isAlreadyFetched() which will check the the previous join attribute (the one I made in the specification) which is userGroup, but because the join type is not LEFT JOIN then Spring adds more join using LEFT JOIN in my query.
private static Join<?, ?> getOrCreateJoin(From<?, ?> from, String attribute) {
for (Join<?, ?> join : from.getJoins()) {
boolean sameName = join.getAttribute().getName().equals(attribute);
if (sameName && join.getJoinType().equals(JoinType.LEFT)) {
return join;
}
}
return from.join(attribute, JoinType.LEFT);
}
That's what I got from my search on the spring-data-jpa code, CMIIW. What is the purpose of the JoinType.LEFT actually I still don't understand. Your explanation will be very helpful for me (and us).
Now I think that I will use custom repository to generate dynamic query using JPQL until I understand the reason for that generated query with additional LEFT JOIN using specification and pageable.
I had the same issue, and found the solution and answer in https://stackoverflow.com/a/43965633/7280115. You may check if it also works for you

Resources