JPA findAll(spec,Sort) - jpa

I have this code to get all transaction between 2 dates. I would like to get a desc sorted list. What are the possibilities?
#Override
public List<Transaction> searchBySubmitDate(final Date startDate,
final Date endDate) {
return transactionRepository.findAll(new Specification<Transaction>() {
#Override
public Predicate toPredicate(Root<Transaction> transaction,
CriteriaQuery<?> q, CriteriaBuilder cb) {
Predicate between = cb.between(transaction.get(Transaction_.dateSubmit), startDate, endDate);
return between;
}
});

#Override
public List<Transaction> searchBySubmitDate(final Date startDate,
final Date endDate) {
return transactionRepository.findAll(new Specification<Transaction>() {
#Override
public Predicate toPredicate(Root<Transaction> transaction,
CriteriaQuery<?> q, CriteriaBuilder cb) {
Predicate between = cb.between(transaction.get(Transaction_.dateSubmit), startDate, endDate);
return between;
}
},new Sort(Direction.DESC,"dateSubmit"));

The repository has another method taking a Sort as additional argument. Call this method with an appropriate Sort instance.

I thought I would leave a more up-to-date answer, since it's been 7 years. This is how we do it now:
interface TransactionRepository extends Repository<Transaction, Long> {
#Query(value = "SELECT * FROM Transaction AS t " +
"WHERE t.date >= :since AND t.date <= :until", nativeQuery = true)
public List<Transaction> findTransactionBetween(#Param("since" String since,
#Param("until" String until);
}
You can read more in the Spring JPA documentation

Related

Add specification from a loop

In a spring boot 3 application I try to use specification
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
...
return pre;
};
return findAll(specification, page);
}
I need for every value in splittedValues to add a global specification
for (String splittedName: splittedValues) {
specification.or(splittedName);
}
and pass it to findAll
I don't understand how to do it
Edit
you solution seem to work but
that generate
where
1=1
or like e1_0.name "%bob%"
or like e1_0.name "%jame%"
It's there a way to get
where
1=1
and (
like e1_0.name "%bob%"
or like e1_0.name "%jame%"
)
Using or operator , you combine multiple conditions, checking whether the name of User contains one of the splittedValues. Predicate is then returned from specification.
I hope that was what you wanted, in case I didn't understand, please correct me. Here is an example if so, adapt to your own code.
Edit: To get 1=1 and (like e1_0.name "%bob%" or like e1_0.name "%jame%"), you can use cb.or method to combine the conditions, resulting nameConditions is then combined with pre using cb.and.
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.conjunction();
Predicate nameConditions = null;
for (String splittedName: splittedValues) {
if (nameConditions == null) {
nameConditions = cb.like(root.get("name"), "%" + splittedName + "%");
} else {
nameConditions = cb.or(nameConditions, cb.like(root.get("name"), "%" + splittedName + "%"));
}
}
if (nameConditions != null) {
pre = cb.and(pre, nameConditions);
}
return pre;
};
return findAll(hasPersonWithName, page);
}

CriteriaBuilder, specification and subquery

I use spring boot 3
School have a one to many relation with Teacher
I created 2 specfication
public Page<School> advanceSearch(SchoolSearch search, Pageable page) {
Specification<School> hasCityName = (Root<School> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
...
Predicate predicate..
return predicate;
};
Specification<Teacher> hasTeacherName = (Root<Teacher> mainRoot, CriteriaQuery<?> mainCq, CriteriaBuilder mainCb) -> {
...
Predicate predicate..
return predicate;
};
return findAll(hasCityName, page);
}
I search to do
select s from School s where s.city like city and
s.schoolId in
(select t.school.schooId from Teacher t where t.name like teacherNom)
how to do a in condition with specfication?
I tried with no success
Specification<Teacher> fromRepondant = (Root<Teacher> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.and(cb.equal(root.get("name"), search.name()));
Path<School> schooPath = root.get("school");
return cq.select(schooPath.get("schoolId")).from(School.class).in(pre);
};
Specification<School> hasIdIn = (Root<School> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
return cb.and(root.get("schooId").in(hasTeacherName));
};

JPA Native Query across multiple tables

I have the following defined as a native query in a repository (dispenseRepository) :
#Query(
value = "SELECT p.*, c.*, s.*, d.* from patient p, consult c ,script s,dispense d "
+ " where p.patient_id=c.patient_id "
+ " and c.consult_id = d.consult_id "
+ " and c.fk_script_id =s.script_id"
+ " and c.consult_id=?1 ",
nativeQuery = true
)
List<Dispense> findInvoiceByConsultId(Long consultId);
The Rest Controller has :
#RequestMapping(value = "/api/invoice/{consultId}",method = {RequestMethod.GET})
public List<Dispense> invoice(#PathVariable(value="consultId")Long consultId){
return dispenseRepository.findInvoiceByConsultId(consultId);
}
When I hit the api I only get dispense details:
[
{
"dispenseId": 1,
"icd10": "J.10",
"tariffCode": "10010",
"dispenseItem": "Lenses",
"price": 400.0
},
{
"dispenseId": 3,
"icd10": "J.10",
"tariffCode": "111000",
"dispenseItem": "Other",
"price": 1500.0
},
{
"dispenseId": 4,
"icd10": "K.100",
"tariffCode": "10010",
"dispenseItem": "Eye Test",
"price": 550.0
}
]
I'd like all the data as per query which will be used for Jasper report
patient-consult 1-M
consult-script 1-1
consult-dispense 1-M
Since in your query you return all fields from all tables: SELECT p.*, c.*, s.*, d.* from patient p, consult c ,script s,dispense d creating projections/DTOs for so many objects and fields is very cumbersome. There are 2 ways to proceed. Either specify exactly the fields you want from each table in your query and create a DTO to hold those fields.
e.g.
Approach 1:
I chose only one field from each table to make it as example. Please not that you have to convert your query from native to jpa one!
#Query("SELECT new com.example.demo.ResultDTO(p.patientName, c.reservationNumber, s.addition, d.dispenseItem) from Patient p, Consult c, Script s, Dispense d ...")
List<ResultDTO> findInvoiceByConsultId(Long consultId);
and ResultDTO class can be:
package com.example.demo;
public class ResultDTO {
private String patientName;
private String reservationNumber;
private String addition;
private String dispenseItem;
public ResultDTO(String patientName, String reservationNumber, String addition, String dispenseItem) {
this.patientName = patientName;
this.reservationNumber = reservationNumber;
this.addition = addition;
this.dispenseItem = dispenseItem;
}
public String getPatientName() {
return patientName;
}
public void setPatientName(String patientName) {
this.patientName = patientName;
}
public String getReservationNumber() {
return reservationNumber;
}
public void setReservationNumber(String reservationNumber) {
this.reservationNumber = reservationNumber;
}
public String getAddition() {
return addition;
}
public void setAddition(String addition) {
this.addition = addition;
}
public String getDispenseItem() {
return dispenseItem;
}
public void setDispenseItem(String dispenseItem) {
this.dispenseItem = dispenseItem;
}
}
UPDATE
Approach 1 won't work with a nativeQuery, you have to convert it to jpa one so unless you convert your query to jpql, the above code wont work.
OR the much easier but bulkier, keep the query as is and place the result on a List of Maps.
Approach 2:
#Query(
value = "SELECT p.*, c.*, s.*, d.* from patient p, consult c ,script s,dispense d "
+ " where p.patient_id=c.patient_id "
+ " and c.consult_id = d.consult_id "
+ " and c.fk_script_id =s.script_id"
+ " and c.consult_id=?1 ",
nativeQuery = true
)
List<Map<String, Object>> findInvoiceByConsultId(Long consultId);

Replacement for "GROUP BY" in ContentResolver query in Android Q ( Android 10, API 29 changes)

I'm upgrading some legacy to target Android Q, and of course this code stop working:
String[] PROJECTION_BUCKET = {MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DATA,
"COUNT(" + MediaStore.Images.ImageColumns._ID + ") AS COUNT",
MediaStore.Files.FileColumns.MEDIA_TYPE,
MediaStore.MediaColumns._ID};
String BUCKET_GROUP_BY = " 1) and " + BUCKET_WHERE.toString() + " GROUP BY 1,(2";
cur = context.getContentResolver().query(images, PROJECTION_BUCKET,
BUCKET_GROUP_BY, null, BUCKET_ORDER_BY);
android.database.sqlite.SQLiteException: near "GROUP": syntax error (code 1 SQLITE_ERROR[1])
Here it supposed to obtain list of images with album name, date, count of pictures - one image for each album, so we can create album picker screen without querying all pictures and loop through it to create albums.
Is it possible to group query results with contentResolver since SQL queries stoped work?
(I know that ImageColumns.DATA and "COUNT() AS COUNT" are deprecated too, but this is a question about GROUP BY)
(There is a way to query albums and separately query photo, to obtain photo uri for album cover, but i want to avoid overheads)
Unfortunately Group By is no longer supported in Android 10 and above, neither any aggregated functions such as COUNT. This is by design and there is no workaround.
The solution is what you are actually trying to avoid, which is to query, iterate, and get metrics.
To get you started you can use the next snipped, which will resolve the buckets (albums), and the amount of records in each one.
I haven't added code to resolve the thumbnails, but is easy. You must perform a query for each bucket Id from all the Album instances, and use the image from the first record.
public final class AlbumQuery
{
#NonNull
public static HashMap<String, AlbumQuery.Album> get(#NonNull final Context context)
{
final HashMap<String, AlbumQuery.Album> output = new HashMap<>();
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
final String[] projection = {MediaStore.Images.Media.BUCKET_DISPLAY_NAME, MediaStore.Images.Media.BUCKET_ID};
try (final Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null))
{
if ((cursor != null) && (cursor.moveToFirst() == true))
{
final int columnBucketName = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
final int columnBucketId = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.BUCKET_ID);
do
{
final String bucketId = cursor.getString(columnBucketId);
final String bucketName = cursor.getString(columnBucketName);
if (output.containsKey(bucketId) == false)
{
final int count = AlbumQuery.getCount(context, contentUri, bucketId);
final AlbumQuery.Album album = new AlbumQuery.Album(bucketId, bucketName, count);
output.put(bucketId, album);
}
} while (cursor.moveToNext());
}
}
return output;
}
private static int getCount(#NonNull final Context context, #NonNull final Uri contentUri, #NonNull final String bucketId)
{
try (final Cursor cursor = context.getContentResolver().query(contentUri,
null, MediaStore.Images.Media.BUCKET_ID + "=?", new String[]{bucketId}, null))
{
return ((cursor == null) || (cursor.moveToFirst() == false)) ? 0 : cursor.getCount();
}
}
public static final class Album
{
#NonNull
public final String buckedId;
#NonNull
public final String bucketName;
public final int count;
Album(#NonNull final String bucketId, #NonNull final String bucketName, final int count)
{
this.buckedId = bucketId;
this.bucketName = bucketName;
this.count = count;
}
}
}
This is a more efficient(not perfect) way to do that.
I am doing it for videos, but doing so is the same for images to. just change MediaStore.Video.Media.X to MediaStore.Images.Media.X
public class QUtils {
/*created by Nasib June 6, 2020*/
#RequiresApi(api = Build.VERSION_CODES.Q)
public static ArrayList<FolderHolder> loadListOfFolders(Context context) {
ArrayList<FolderHolder> allFolders = new ArrayList<>();//list that we need
HashMap<Long, String> folders = new HashMap<>(); //hashmap to track(no duplicates) folders by using their ids
String[] projection = {MediaStore.Video.Media._ID,
MediaStore.Video.Media.BUCKET_ID,
MediaStore.Video.Media.BUCKET_DISPLAY_NAME,
MediaStore.Video.Media.DATE_ADDED};
ContentResolver CR = context.getContentResolver();
Uri root = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
Cursor c = CR.query(root, projection, null, null, MediaStore.Video.Media.DATE_ADDED + " desc");
if (c != null && c.moveToFirst()) {
int folderIdIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_ID);
int folderNameIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.BUCKET_DISPLAY_NAME);
int thumbIdIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media._ID);
int dateAddedIndex = c.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_ADDED);
do {
Long folderId = c.getLong(folderIdIndex);
if (folders.containsKey(folderId) == false) { //proceed only if the folder data has not been inserted already :)
long thumbId = c.getLong(thumbIdIndex);
String folderName = c.getString(folderNameIndex);
String dateAdded = c.getString(dateAddedIndex);
Uri thumbPath = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, thumbId);
folders.put(folderId, folderName);
allFolders.add(new FolderHolder(String.valueOf(thumbPath), folderName, dateAdded));
}
} while (c.moveToNext());
c.close(); //close cursor
folders.clear(); //clear the hashmap becuase it's no more useful
}
return allFolders;
}
}
FolderHolder model class
public class FolderHolder {
private String folderName;
public long dateAdded;
private String thumbnailPath;
public long folderId;
public void setPath(String thumbnailPath) {
this.thumbnailPath = thumbnailPath;
}
public String getthumbnailPath() {
return thumbnailPath;
}
public FolderHolder(long folderId, String thumbnailPath, String folderName, long dateAdded) {
this.folderId = folderId;
this.folderName = folderName;
this.thumbnailPath = thumbnailPath;
this.dateAdded = dateAdded;
}
public String getFolderName() {
return folderName;
}
}
GROUP_BY supporting in case of using Bundle:
val bundle = Bundle().apply {
putString(
ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
"${MediaStore.MediaColumns.DATE_MODIFIED} DESC"
)
putString(
ContentResolver.QUERY_ARG_SQL_GROUP_BY,
MediaStore.Images.ImageColumns.BUCKET_ID
)
}
contentResolver.query(
uri,
arrayOf(
MediaStore.Images.ImageColumns.BUCKET_ID,
MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME,
MediaStore.Images.ImageColumns.DATE_TAKEN,
MediaStore.Images.ImageColumns.DATA
),
bundle,
null
)

How to select shard for search query?

I'm recently implemented ShardIdentifierProvider. It is working fine. But how to ensure it is using only one shared for query?
public class SiteIdAsShardIdProvider extends ShardIdentifierProviderTemplate {
#Override
protected Set<String> loadInitialShardNames(Properties properties, BuildContext buildContext) {
ServiceManager serviceManager = buildContext.getServiceManager();
SessionFactory sessionFactory = serviceManager.requestService(HibernateSessionFactoryServiceProvider.class, buildContext);
Session session = sessionFactory.openSession();
try {
#SuppressWarnings("unchecked")
List<String> ids = session.createSQLQuery("select cast(id as CHAR(3)) from website").list();
return new HashSet<>(ids);
} finally {
session.close();
}
}
#Override
public String getShardIdentifier(Class<?> entityType, Serializable id, String idAsString, Document document) {
return document.getFieldable("siteId").stringValue();
}
}
Creating your own custom filter and overriding getShardIdentifiersForQuery should do the trick. Here is something that does approximately the same as what's in the documentation, but with a ShardIdentifierProviderTemplate:
#Override
public Set<String> getShardIdentifiersForQuery(FullTextFilterImplementor[] filters) {
FullTextFilter filter = getFilterByName( filters, "customer" );
if ( filter == null ) {
return getAllShardIdentifiers();
}
else {
Set<String> result = new HashSet<>();
result.add( filter.getParameter( "customerID" ) );
return result;
}
}
private FullTextFilter getFilterByName(FullTextFilterImplementor[] filters, String name) {
for ( FullTextFilterImplementor filter: filters ) {
if ( filter.getName().equals( name ) ) {
return filter;
}
}
return null;
}
I created a ticket to update the documentation: https://hibernate.atlassian.net/browse/HSEARCH-2513
The shard selection at query time is controlled by using a custom Filter.
See "5.3.1. Using filters in a sharded environment" for details and examples.