Hibernate Envers revision number order - oracle

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.

Related

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.

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

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!

Named Query with map values in Spring Data JPA

i have a hashmap like which has key value pair such as :
testMap = new HashMap(){{
put(iD, sID);
put(lEVEL, sLevel);
put(tYPE, sType);
put(vALUE, sValue);
Now i have my entity Class which has fields like
#Column(name = "ID")
private String id;
#Column(name = "LEVEL")
private String level;
#Column(name = "PROGRAM")
private String program;
#Column(name = "TYPE")
private String type;
#Column(name = "VALUE")
private String value;
#Column(name = "STARTDATE")
private Date start;
Now i need to form a named query where for each column i have to set values by extracting them from map key value pair (something like this :tesMap.get(iD) and setting in ID column) and also comparing the start date with the current date and setting the largest date in startdate column .
Tried the following using Named query but didn't work .
#Query(value = "SELECT * FROM OVERRIDES s " +
"WHERE s.ID =?1 AND s.LEVEL=?2 AND s.TYPE=?3 AND s.VALUE=?4", nativeQuery = true)
Also ,unable to decide how to compare dates in this select query .
Comparing the dates can be easily done with Date.after() method.
E.g.
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2022-07-01");
Date date2 = sdf.parse("2022-08-31");
if(date1.after(date2)) {
//set largest date in date column
}
As for the rest question, please provide the whole Entity, or maybe more infos about the query
In SQL, you can compare dates/timestamps with relational operators >, <, >=, <= i.e.
#Query(value = "SELECT * FROM OVERRIDES s " +
"WHERE s.ID =?1 AND s.LEVEL=?2 AND s.TYPE=?3 AND s.VALUE>=?4", nativeQuery = true)

Java 8 GroupBy and transform result to a list

Hello I have to group by multiple fields and make a summary by one of these files, thereafter I have to work with this result and make some more transformations my problem is that I'm getting a complex structure after the grouping and it's not easy to work with this items. This is my code:
Map<String, Map<String, Map<LocalDateTime, Map<String, Double>>>> processed = null;
processed = itemsToProcess
.stream()
.collect(groupingBy(entity::getId,
groupingBy(entity::getType,
groupingBy(entity::getCreateDate,
groupingBy(entity::getCode,
summingDouble(entity::getPay))))));
The objective of this grouping is the summary of the pays but thereafter I I have to do some transformations with this processed structure, my doubt if is there is a way to transform this in a simple list in order to make more easy this task?
My input is basically a list of:
List<Person> itemsToProcess= new ArrayList<>();
#JsonInclude
public class Person extends Entity<Person > {
private static final long serialVersionUID = 1L;
/* File line content */
private String id;
private String type;
private LocalDateTime createDate;
private String code;
private double pay;
private String city
private String company
}
The output that I'm looking for is the summary of the pay field grouped by {id,type,createDate,pay}
example if I have the next values
Id type createdDAte Code pay more fields....
1 0 today BC 500
1 0 today BC 600
2 0 today BC 600
2 0 today BC 300
3 0 today BC 300
The result must be:
Id type createdDAte Code pay more fields....
1 0 today BC 1100
2 0 today BC 900
3 0 today BC 300
You can use Collectors.toMap to Map by that four properties and merging the same group person objects and creating a new one using the constructor.
List<Person> processed =
new ArrayList<>(
itemsToProcess
.stream()
.collect(Collectors.toMap(
i -> Arrays.asList(i.getId(), i.getType(), i.getCreateDate(), i.getCode()),
i -> i,
(a, b) -> new Person(a.getId(), a.getType(), a.getCreateDate(), a.getCode(),
a.getPay() + b.getPay())))
.values());
Output:
Person [id=2, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=900.0]
Person [id=3, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=300.0]
Person [id=1, type=0, createDate=2020-08-18T12:26:15.616034800, code=BC, pay=1100.0]
Demo here
Quick way is to group by a map of the key fields:
groupingBy(e -> Map.<String, Object>of(
"id", e.getId(),
"type", e.getType(),
"createDate", e.getCreateDate(),
"code", e.getCode(),
"pay", e.getPay()
), identity())
Map's equals() method works as you would hope it does.

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