Spring JPA repository method to get sorted distinct and non-null values - spring-boot

To get distinct data based on multiple columns and exclude NULL values on a column and sort the result in SQL, I would write query like:
SELECT DISTINCT CAR_NUMBER, CAR_NAME
FROM CAR
WHERE CAR_NUMBER IS NOT NULL
ORDER BY CAR_NUMBER
This would return me rows with distinct values for CAR_NUMBER and CAR_NAME and it would exclude any rows having CAR_NUMBER = NULL and finally, it would sort the result by CAR_NUMBER.
However, In Spring JPA, I gather you can use either methods named based on your entity fields or using #Query annotation.
I am trying to do this:
List<Car> findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort);
, and to call this method like:
myRepo.findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort.by("carNumber"));
but this is failing on Maven > Install with error like "findDistinctByCarNumberAndCarNameAndCarNumberIsNotNull(Sort sort) expects at least 1 arguments but only found 0".
Similarly, I tried using #Query like below but with same effect:
#Query(SELECT DISTINCT c.carNumber, c.carName FROM carEntity c WHERE c.carNumber IS NOT NULL ORDER BY c.carNumber)
List<Car> findAllCars();

I figured out the problem. Following is how I solved it:
In my repository:
#Query("select distinct c.carNumber, c.carName from CarEntity c where c.carNumber is not null")
List<Object> findAllDistinctRegions(Sort sort);
Important here to realize is that #Query returns List<Object>, not List<Car>.
Next, in my service, call this method:
List<Object> carData = carRepository.findAllDistinctCars(Sort.by("carNumber"));
That worked finally fine; however, I run into another problem where I had to do necessary conversion from List to List.
// This is bit tricky as the returned List<Object> is actually
// List<Object[]>. Basically, each field returned by the #Query
// is placed into an array element.
//To solve it, I had to do following:
List<Car> cars = new ArrayList<Car>();
for(Object data: carsData) {
Object[] obj = (Object[]) data;
cars.add(new CarDto((Short) obj[0], ((String) obj[1]));
}

I just remembered there is a better way to solve this than that helper function that you described in your answer and thought I would share it.
Projection in JPQL would be a cleaner way to create your DTO:
#Query("SELECT DISTINCT new com.yourdomain.example.models.MyDto(c.carNumber, c.carName)
FROM CarEntity c WHERE c.carNumber is not null")
List<CarDto> findAllDistinctRegions(Sort sort);

Related

JPQL, Can I Create DTO using Constructor which Takes Entities by Selecting All Columns In Once?

I'm using Spring Data JPA and Hibernate. I made repository having a method returning List<AWithBDto> like
#Query(
"""
select new com.test.example.AWithBDto(a.a1, a.a2, ..., b.b1, b.b2, ...)
from A a join B b where ~
order by ~
"""
)
Because I thought listing all columns of table a and b is tedious, I added a constructor in AWithBDto as follows. (in Kotlin)
data class AWithBDto(
...
) {
constructor(a: A, b: B) : this(
a1 = a.a1,
a2 = a.a2,
...,
b1 = b.b1,
b2 = b.b2,
...,
)
}
So I could shorten my repository codes as follows.
#Query(
"""
select new com.test.example.AWithBDto(a, b)
from A a join B b where ~
order by ~
"""
)
However, the query made kind of N+1 problems because it selected only id columns from both tables.
select
a0_.id as col_0_0_,
b1_.id as col_1_0_
from
a a0_
inner join
b b1_
on a0_.id=b1_.id
where
~
order by
~
and multiple queries selecting all columns for each single row from each table were executed.
select
a0_.id as id1_3_0_,
...,
...,
from
a a0_
where
a0_.id=?
...multiple times
select
b0_.id as id1_4_0_,
...,
...,
from
b b0_
where
b0_.id=?
...multiple times
In JPQL, Can I create DTO using a constructor which takes entities as params by selecting all columns in once? I want Hibernate to execute a single query for this.
Or if I can't, could you suggest good alternatives for me?
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(A.class)
public interface AWithBDto {
#IdMapping
Long getId();
String getA1();
String getA2();
// Either map properties through nested mapping path expressions
#Mapping("b.b1")
String getB1();
#Mapping("b.b2")
String getB2();
// Or through a subview
BDto getB();
#EntityView(B.class)
interface BDto {
#IdMapping
Long getId();
String getB1();
String getB2();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AWithBDto a = entityViewManager.find(entityManager, AWithBDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<AWithBDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Spring And Kotlin Query

How can I achieve this query
select *
from table t
where name like '%Ami%'
order by (name = 'Ami') desc, length(col);
(just the sort part)
Using springframework Sort..
What I tried is
Sort.by(Sort.Direction.DESC, "name") // But I need to sort by name = 'Ami'
Sort.by(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
JpaSort.unsafe(Sort.Direction.DESC, "name" = 'Ami'") // throws an error
Looks like the documentation has an example almost identical to your question:
https://docs.spring.io/spring-data/jpa/docs/2.4.5/reference/html/#jpa.query-methods.sorting
However, using Sort together with #Query lets you sneak in
non-path-checked Order instances containing functions within the ORDER
BY clause. This is possible because the Order is appended to the given
query string. By default, Spring Data JPA rejects any Order instance
containing function calls, but you can use JpaSort.unsafe to add
potentially unsafe ordering.

Spring data JPA expression using deleteBy

I am using spring-data-jpa to perform delete operation.
Currently, I have a query like:
#Modifying
#Transactional
#Query("Delete from student s where(:studentName is null Or s.studentName =:studentName) and s.studentId IN(:studentIds)")
int deleteByStudentIdAndRollNo(#Param("studentName") String studentName, #Param("studentIds") List<Integer> studentIds)
The above query is working fine and is returning a count of successfully deleted rows from the table.
But the problem is, now I have to return deleted List<Students> instead of the number of deleted records.
I am aware of the fact that #Query will always return the count of successfully deleted records.
I tried to use deleteBy like below:
List<Student> deleteByStudentIdStudentNameIsNullOrStudentNameIsAndStudentIdIN
, But that's not working.
Is there any way to get list of deleted students?
This article show how to return deleted records: Spring Data JPA – Derived Delete Methods
And try maybe this code:
List<Student> deleteAllByStudentNameIsNullOrStudentNameAndStudentIdIn(String studentName, List<Integer> studentIds);
But can't be sure that will be work because I don't know your Student's model structure.

spring data jpa query select all rows if given parameter collection is null

I want to write a query that selects all rows by given type collection for my fileRepository(There are 3 types as "1", "2" and "3"), if that parameter is not given from frontend, then it should return all rows.
I am using a Repository with #RepositoryRestResource annotation to achieve this.
It works fine for single type query(not Collection)
#Query("SELECT t FROM #{#entityName} t WHERE :fileType IS NULL OR t.fileType = :fileType")
Page<MyFile> findByFileType(#Param("fileType") String fileType, #Param("page") Pageable pageable);
it returns all entities when I don't give any arguments i.e : "http://localhost:8080/api/myFiles/search/findByFileType"
and returns all Files which have type "1" when I search with 1 parameter:
"http://localhost:8080/api/myFiles/search/findByFileType?fileType=1"
But problem occurs when I try to write a query from collection of types and I tried this
#Query("SELECT t FROM #{#entityName} t WHERE :fileTypes IS NULL OR t.fileType IN :fileTypes")
Page<MyFile> findByFileType(#Param("fileTypes") Collection<String> fileTypes, #Param("page") Pageable pageable);
It still works for the example localhost links that I gave above but multiple parameter throws "java.sql.SQLSyntaxErrorException: ORA-00920: invalid relational operator" error with this link:
"http://localhost:8080/api/myFiles/search/findByFileType?fileType=1,2"
Edit: I am using Oracle11g as database.

SimpleJpaRepository Count Query

I've modified an existing RESTful/JDBC application i have to work with new features in Spring 4... specifically the JpaRepository. It will:
1) Retrieve a list of transactions for a specified date. This works fine
2) Retrieve a count of transactions by type for a specified date. This is not working as expected.
The queries are setup similarly, but the actual return types are very different.
I have POJOs for each query
My transactions JPA respository looks like:
public interface MyTransactionsRepository extends JpaRepository<MyTransactions, Long>
//My query works like a charm.
#Query( value = "SELECT * from ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1", nativeQuery = true )
List< MyTransactions > findAllBy_ToChar_LastAction( String lastActionDateString );
This returns a list of MyTransactions objects as expected. Debugging, i see the returned object as ArrayList. Looking inside the elementData, I see that each object is, as expected, a MyTransactions object.
My second repository/query is where i'm having troubles.
public interface MyCountsRepository extends JpaRepository<MyCounts, Long>
#Query( value = "SELECT send_method, COUNT(*) AS counter FROM ACTIVITI_TMP.BATCH_TABLE WHERE TO_CHAR(last_action, 'YYYY-MM-DD') = ?1 GROUP BY send_method ORDER BY send_method", nativeQuery = true )
List<MyCounts> countBy_ToChar_LastAction( String lastActionDateString );
This DOES NOT return List as expected.
The object that holds the returned data was originally defined as List, but when I inspect this object in Eclipse, I see instead that it is holding an ArrayList. Drilling down to the elementData, each object is actually an Object[2]... NOT a MyCounts object.
I've modified the MyCountsRepository query as follows
ArrayList<Object[]> countBy_ToChar_LastAction( String lastActionDateString );
Then, inside my controller class, I create a MyCounts object for each element in List and then return List
This works, but... I don't understand why i have to go thru all this?
I can query a view as easily as a table.
Why doesn't JPA/Hibernate treat this as a simple 2 column table? send_method varchar(x) and count (int or long)
I know there are issues or nuances for how JPA treats queries with counts in them, but i've not seen anything like this referenced.
Many thanks for any help you can provide in clarifying this issue.
Anthony
That is the expected behaviour when you're doing a "group by". It will not map to a specific entity. Only way this might work is if you had a view in your database that summarized the data by send_method and you could map an entity to it.

Resources