Spring Data Sort in `#Query` annotation on unmapped Property - spring

Is it possible to sort a query on a repository method by specifying an unmapped property? In my particular case I want to use a aggregation result as sort parameter but do not want to map it into the projected class.
interface FooRepository {
#Query("""
select f from Foo f
group by t.id
order by count(t.id) desc
""")
List<Foo> findFooSorted();
}

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 JPA repository method to get sorted distinct and non-null values

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);

CrudRepository: findBy... but without returning the values of a column

I have a table with columns like this:
ID | A | B | C
I have a class with the attributes ID, A and B but without the attribute C.
I want to implement a CrudRepository find method (for example findAllByA) that takes every row that matches the query but without returning C so the result can be bound to my class.
I know I could have a secondary class with all four arguments and then convert the collection of results to a collection of results of the class I actually want, but I'd rather avoid this.
Simply return an instance of your class from your method.
For example :
public interface YourRepository extends CrudRepository<YourModel, Long> {
#Query("SELECT new com.example.YourClass(e.ID, e.A, e.B) FROM YourEntity e WHERE e.A = ?1")
List<YourClass> findByA(String filter);
}
So the result is bound to your class directly.
I think, mapping is the best choice in this situation. Because YourClass is DTO object and if you change your mind and change the internal structure YourClass in the project, you can only change the mapper code. Yann39's solution is acceptable but if you change the structure of YourClass class, you will need to change every Query related to it.

how to use SpringData findAll() between select when using an object as major condition?

So there's a method in SpringData findAll(Pageable pageable,Condition condition) ;,
usually I use it like findAll(pageable,myobject) .
The question is when it comes to select some records between some certain field range ,like select out objects whose createDate are between A and B , how to use findAll()?
I tried findAllByCreateDateAfterAndCreateDateBefore(Pageable pageable,Date a,Date b);
But here I can't put myObj as a condtion into the method , and it caused a lot trouble when some fields in the myObj are not sure if it would be used as a condition.
You can simply use JPA Specification to do this, then :
fun findAll(spec: Specification<YourObject>, pageable: Pageable): Page<YourObject>
Here simple example how it should be use :
JPA Specification example

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