Sorting in Java | How to get value from other field of entity if the current field is null while sorting entity list in java? - spring-boot

Context :
Consider following entity in spring boot project,
#Entity
public class Transaction{
#Id
private Integer tId;
private Integer amount;
private LocalDate invoiceDate;
private LocalDate tDate;
}
A method which creates object of transaction, doesn't set tDate.
There is an independent method which sets tDate.
So, in the database some entries don't have 'tDate'.
Problem :
Now, I want to show all the entries in database sorted using tDate but, for those entries which does not contain tDate it should consider invoiceDate.
Sample Database entries
t_id
amount
invoice_date
t_date
1
1200
2/3/2022
4/3/2022
2
2434
5/3/2022
6/3/2022
3
234
3/3/2022
NULL
Sample expected Output
[[tId = 3, amount = 234, invoiceDate = 3/3/2022, tDate = NULL]
[tId = 1, amount = 1200, invoiceDate = 2/3/2022, tDate = 4/3/2022]
[tId = 2, amount = 2434, invoiceDate = 5/3/2022, tDate = 6/3/2022]]
Note : Highlighted dates above are the dates considered for sorting.
What I tried
I tried to use the combination of nullsLast(Comparator.naturalOrder()) and thenComparing() methods of Comparator.comparing() but it doesn't give the expected output. It sorts the entries withtDate and entries without tDate separately.
Thank you in advance for any help!!
Also, I'm using MongoRepository in repository layer, so I can use the Sort object as well.
Thank you!

Related

how to extract the today's date out of the date column using SQL query?

I assume I would need to change query in order to sort the data with today's date.
Please tell me how to change it though...
SQL QUERY in ToDoDao
#Query("SELECT * FROM todo_table WHERE date(date) = date('now')")
fun getTodayList(): Flow<List<ToDoTask>>
DATABASE
#Entity(tableName = DATABASE_TABLE)
data class ToDoTask(
#PrimaryKey(autoGenerate = true) val id: Int = 0,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String,
#ColumnInfo(name = "priority") val priority: Priority,
#ColumnInfo(name = "date") val date: String,
#ColumnInfo(name = "favorite") var favorite: Boolean)
date val in ViewModel class
val date : MutableState<String> = mutableStateOf("")
datas inserted
enter image description here
I have tried the code below and I was able to activate the function as the query as I intented, so I think the query is the issue here.
#Query("SELECT * FROM todo_table WHERE date = '2023-2-14'")
fun getTodayList(): Flow<List<ToDoTask>>
The Issue
The issue is that the SQLite date function expects the date to be in an accepted format.
YYYY-M-DD is not such a format and will result in null rather than a date. YYYY-MM-DD is an accepted format (see https://www.sqlite.org/lang_datefunc.html#time_values). That is leading zeros are used to expand single digit numbers to 2 digit numbers for the month and day of month values.
The Fix (not recommended)
To fix the issue you have shown, you could use (see the However below):-
#Query("SELECT * FROM todo_table WHERE date(substr(date,1,5)||CASE WHEN substr(date,7,1) = '-' THEN '0' ELSE '' END ||substr(date,6)) = date('now');")
If the month was 2 numerics i.e. MM (e.g. 02 for February) then the above would not be necessary.
The CASE WHEN THEN ELSE END construct is similar to IF THEN ELSE END. see https://www.sqlite.org/lang_expr.html#the_case_expression. This is used to add the additional leading 0, when omitted, to the string used by the date function.
However, the above would not cater for days that have the leading 0 omitted for the first 9 days of the month. This due to the 4 permutations of the format (YYYY-MM-DD, YYYY-MM-D, YYYY-M-D and YYYY-M-DD) would be more complex e.g.
#Query("SELECT * FROM todo_table WHERE date(CASE WHEN length(date) = 8 THEN substr(date,1,5)||'0'||substr(date,6,1)||'-0'||substr(date,8) WHEN length(date) = 9 AND substr(date,7,1) = '-' THEN substr(date,1,5)||'0'||substr(date,6) WHEN length(date) = 9 AND substr(date,8,1) = '-' THEN substr(date,1,8)||'0'||substr(date,9) ELSE date END) = date('now');")
Recommended Fix
The recommended fix is to store values using one of the accepted formats rather than try to manipulate values to be an accepted date to then be worked upon using the date and time functions.

crudrepository save takes too long to update an entity

I've got a controller that first selects all data with status = 'CREATED' transferType = 'SOME_TYPE' and DATE_TIME between x and y, and then put all the data in the List<TransferEntity>
then i am going through each element in the list and updating status to 'CHECKED'
if (listOfTransfers.isNotEmpty()){
for(element in listOfTransfers){
element.status = "CHECKED"
repos.transfers.save(element)
}
}
entity itself is pretty straight forward with no relations to other tables
#Entity
#Table( name = "TRANSFERS")
class TransferEntity(
#Id
#Column(name = "Identifier", nullable = false)
var Identifier: String? = null,
#Column(name = "TRANS_DATE_TIME")
var transDateTime: LocalDateTime? = null,
#Column(name = "TRANS_TYPE", nullable = true, length = 255)
var transType: String? = null,
#Column(name = "STATUS")
var status: String = ""
)
i tried to experiment with indexes (oracle)
`CREATE INDEX TRANS_INDEX_1 ON TRANSFERS(STATUS)`
`CREATE INDEX TRANS_INDEX_2 ON TRANSFERS(TRANS_DATE_TIME)`
`CREATE INDEX TRANS_INDEX_3 ON TRANSFERS(TRANS_TYPE)`
or created them as one index
CREATE INDEX TRANS_INDEX_4 ON TRANSFERS(STATUS,TRANS_DATE_TIME,TRANS_TYPE)
but it wasnt a big difference
UPDATE
witn TRANS_INDEX_1 2 and 3 - 3192 elements were updateind in 5 minutes 30 sec
with TRANS_INDEX_4 - 3192 elements were updated in 5 minutes 30 sec
maybe there are different approaches to mass update elements inside the list or perhaps indexes are completely wrong and i dont understand them as much as i want it to.
UPDATE 2
technically saveAll() method works much faster but still I think there should be a room for improvement
saveAll() - 3192 elements were saved under 3minutes 21seconds
save() 3192 elements were save under 5minutes 30 seconds
You call save() each time you update an element. 1000 elements will create 1000 query calls to the database, you repeat too many calls to your DB and that's why your function is slow.
Instead, you could use saveAll() after you updated all the elements
as suggested below, we also have to config the batch_size properly to really do the trick
Indexes won't help in this situation since they benefit the select operation more than update or insert
Since you set the same value to all the elements of your list, you can make a batch update query :
Query q = entityManager.createQuery("update TransferEntity t set t.status = :value where t in (:list)");
q.setParameter("value", "CHECKED");
q.setParamter("list", listOfTransfers);
q.execute();
If you use ORACLE as backend be aware that in clause is limited to 1000 elements. Therefore you might have to split your list in buckets of 1000 elements and loop on this query for each bucket.

JPA find first by created in given list

I have a model class which has these fields:
class Student {
Long roleNo;
Long version;
Date createdAt;
Date updateAt;
}
Now I'm trying to write a JPA query in which I can pass a list of role numbers and for every role number I can get a latest record by version.
I tried to do this but I'm only getting one record:
findFirstByRoleNoIn(List<Long> roleNo);
One way is to make use of #Query annotation to write simple query. Sample query for your case:
#Query(nativeQuery = true, value = """
SELECT s.* FROM student s
INNER JOIN (SELECT
role_no,
MAX(version) AS latest
FROM student GROUP BY role_no) t_version
ON s.role_no = t_version.role_no
AND s.version = t_version.latest
WHERE s.role_no IN (:roleNo)
""")
findLatestVersion(#Param("roleNo") List<Long> roleNo);

Hibernate Envers revision number order

I used Envers to audit my entities. The DB used is Oracle 11g. For the revision number of the revision entity, I used a oracle sequence. But since the oracle sequence does not guarantee monotonic increasing of each call to nextval, In my revision table, I got something like this,
We can see that the rev number is not monotonically increasing. In the audit table of my entity, I have this:
By chaining the records together with the rev number to form a timeline, I got :
24 --> 1302 --> 1303 --> 1355 --> 1304 --> 1356 --> 1357 --> 1305 -->1358 --> null
In fact, I have a cluster of 2 servers. They both can persist data into the DB. I suspect that this has relation to the problem of order in revnumber. Because of this problem. Query like MyEntity anEntity = auditReader.find(MyEntity.class, id, revNum) doesn't work because of
org.hibernate.envers.query.impl.AbstractAuditQuery.getSingleResult(AbstractAuditQuery.java:117) . I checked the SQL generated by Hibernate, in the where clause
where
myentity.rev<=?
and myentity.revtype<>?
and myentity.id=?
and (
myentity.revend>?
or myentity.revend is null
)
so for the rev number 1356, several audit records are retrieved, for example
1356 --> 1357 and 1305 -->1358 because rev num 1356 is just between the two ends.
How can I solve this problem? I mean to make the rev number monotonically increasing one transaction after another.
UPDATE
My revision entity
#Entity
#RevisionEntity(CustomRevisionListener.class)
#Table(name = "REV_TABLE")
#SequenceGenerator(name = "GENERIC_GENERATOR", sequenceName = "SQ_REVISION_ID")
public class Revision {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator ="GENERIC_GENERATOR")
#Column(nullable = false, name = REV)
#RevisionNumber
private long rev;
}
You probably want to take a look at how the revision number sequence is defined inside the SequenceIdRevisionEntity class. We essentially define it as follows:
#Id
#GeneratedValue(generator = "RevisionNumberSequenceGenerator")
#GenericGenerator(
name = "RevisionNumberSequenceGenerator",
strategy = "org.hibernate.envers.enhanced.OrderedSequenceGenerator",
parameters = {
#Parameter(name = "table_name", value = "REVISION_GENERATOR"),
#Parameter(name = "sequence_name", value = "REVISION_GENERATOR"),
#Parameter(name = "initial_value", value = "1"),
#Parameter(name = "increment_size", value = "1")
}
)
#RevisionNumber
private int id;
The key is specifying that the initial_value and increment_size are defined as 1 to avoid the hi-lo gaps you're noticing with your existing sequence definition.

Hibernate 4.3.1 - Native query on multiple table returns just one record

I am using Hibernate 4.3.1 to fetch records from four different unrelated tables by using columns which have matching data. For this I have written a query which is as follows
SELECT ca.csc_issuer_id as cssIssuerId,
ca.csc_serial_number as cssSerialNumber,
ca.csc_type as cssType,
ca.csc_lifecycle_count as cssLifecycleCount,
aa.app_provider_id as ...,
aa.provider_app_serial_number as ...,
aa.card_app_serial_number as ..,
aa.app_type as ..,
pa.csc_issuer_id as ..,
pa.csc_serial_number as ..,
pa.prodType
FROM AATable AA, CATable CA, PATable PA
WHERE AA.CSC_SERIAL_NUMBER = CA.CSC_SERIAL_NUMBER
AND AA.CSC_SERIAL_NUMBER = PA.CSC_SERIAL_NUMBER
AND AA.CSC_SERIAL_NUMBER = 123456
AND AA.csc_issuer_id = 26
AND AA.csc_type = 1
AND AA.csc_lifecycle_count = 0
AND PA.app_provider_id = 26
AND PA.provider_app_serial_number = 123456
AND PA.card_app_serial_number = 0
This query return 3 rows when I run it directly on Oracle database(All of them obviously match all the where condition but each row has a different value of PATable.prodType column) but when I use this from within my java code , Hibernate only returns one row which is the first row returned by SQL.Any idea whats going on ? I have tried joining tables but the same result.
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Builder
public class MyPOJO {
private Long cssIssuerId;
private Long cssSerialNumber
other members....
}

Resources