I'm using Spring JPA to persist data in my application. I also use Hibernate Envers to create a history for every record I enter into my core table. I would like to get the revision immediately after the write transaction, and show the user what revision was created for the change(s) s/he just made.
In other words:
Step 1: entity -- persisted --> entity table -- envers --> audit table
Step 2: return me the audit version just created
I have taken the approach of persisting the data first, and then retrieve the latest rev info from the audit table in a separate call. This will eventually be inconsistent as the number of users increases.
MyEntity mySavedEntity = myEntityRepository.save(myEntity);
AuditReader reader = AuditReaderFactory.get(entityManager);
List<Number> revisions = reader.getRevisions(MyEntity .class, mySavedEntity.getId());
// ... get the latest revision and pass it back to the user ...
How do I attack this problem? - Thank you
You can make Envers log the version column of your entities by setting org.hibernate.envers.do_not_audit_optimistic_locking_field to false
Then use that column and the value of the version attribute of the entity after the transaction for retrieving the revision.
Maybe you can use the ValidityAuditStrategy, you can check it here, this will let you know your last valid entry and in this case get the last revision.
Related
My system has a patient entity that contains email(string), type(integer) fields, both non primary, not unique and not null fields along with other fields and of course a primary key id.
After I save a new patient entity, when I search the entity in the database by jpa query findById it works perfectly fine and it fetches that new entity that was just saved few ms ago.
But when I search the newly saved entity by email and type by jpa query findByEmailAndTypeAndEmailIsNotNull it returns nothing but if I run the very same findByEmailAndTypeAndEmailIsNotNull query after 1 second of saving that new entity then it returns that newly saved entity.
Can some one diagnose the problem, is it even related to JPA? or with Database itself?
Edit:
#Transactional
public synchronized Patient addPatient(PatientProfileDto patientProfileDto, Integer facilityId)
throws ResourceAlreadyExistsException, EntityNotFoundException, ClientException {
// some code
performPatientCreationValidations(ownerDto, ownerDemographicDto.getNationality().getId(), Boolean.FALSE, Boolean.FALSE);
// patient creation
patientRepository.saveAndFlush(patient);
// some code to link patient to other entities
}
private void performPatientCreationValidations(...params)
throws ResourceAlreadyExistsException, ClientException {
if (patientRepository.findByEmailAndTypeAndEmailIsNotNull(patientDto.getEmail(), PatientType.OWNER.getId()).isPresent()) {
// throw error
}
}
If I hit the API 5 times in a row with neglible delay then 2 duplicate patient would get created, however on the last 3 apis hits it will throw error as it should. It should have also thrown the error upon 2nd api hit it receives. Also note the adding of patient is synchronized function so when one api hit is completed saving the patient then another api hit acquires lock on function n go on.
After I save a new patient entity, when I search the entity in the database by jpa query findById it works perfectly fine
JpaRepository.findById() works fine as it takes the entity from Hibernate's 1st level cache, not from the database, because inserting into the database is usually deferred until necessary, e.g. until your session is flushed. An entity can be fetched from the 1st level cache only by its id.
So you have to either flush the Session manually with JpaRepository.flush(), or use JpaRepository.saveAndFlush() instead of JpaRepository.save() or execute your operations within one transaction. In this case your requests share one session and Hibernate is going to flush its cache as soon as it gets a new query for the same entity.
I have a spring web application with Spring Boot(v2.3.3) and Spring Data. I have a table assessment that contains the following columns:
Id
Name
Address
Version
My use case is any changes of name and address in the table assessment a new row should be created and the version incremented with the same id.
So basically only when either the address or name are updated a new record should be inserted in the assessment table an example is shown below:
Existing record:
Id - 23
Name - John
Address - Southend
Version - 1
For example, the name has been updated to Ryan and there should be two rows as follows:
Id - 23
Name - John
Address - Southend
Version - 1
Id - 23
Name - Ryan
Address - Southend
Version - 2
So basically any changes in the name and address should be audited in assessment table and the version should be incremented.
I have seen hibernate envers for auditing but a new audit table needs to be created and therefore I cannot insert it in the assessment table and also I don't know how to generate the version number.
I can write the code programmatically to achieve this but is there any other available auditing tool that I can achieve it please?
Thanks in advance
I am not sure what yours expectations.
Why you want to use some tool ? Like envers.
The problem is connected with automation and DB so first think is trigger.
Mabye the simplest and best way is write just PostUpdateEventListener.
public class VersionUpdateEventListener
implements PostUpdateEventListener {
#Override
public void onPostUpdate(
PostUpdateEvent event) {
/*
1. getOldState() and getState()
2. check updated values
3. update version +1 or not
*/
}
...
Or use hibernate #PostUpdate annotation.
I'm sure you'll figure it out an find details in docs/google.
Your main entity should have columns Id, Name, Address (not version). And you should put #Audit over entity. After that, you should generate database table scripts (see, for example, method main in the org.hibernate.tool.hbm2ddl.SchemaExport).
You will see in script the main and archive table (with the _aud suffix). It will contain the Id,Name, Address, rev. the rev field is your version. When you change the name to Ryan in the main entity and commit, envers automatically adds a new entry to the archive table.
If trigger is supported, try trigger, as it will cover cases updated directly from DB.
The entity I'm trying to save is a parent and child. When I save the entity (i.e. the parent and children saved at the same time), however with normal execution (in debug mode every time) I get a HibernateOptimisticLockingFailureException thrown during session flushing. The testing is on my local machine, single thread, and nobody is changing the entity as I'm also saving it.
We are using the following:
MySQL v5.5.x
Hibernate 4.3.11
Java 8
Spring 4.1.0
Key points:
The relationship between the parent and child is bi-directional one-to-many.
We use optimistic locking with the version column being a timestamp created by MySQL either during insert or during update. On the version field we specify #Generated(GenerationTime.ALWAYS) to ensure that the version details are obtained from the database automatically (avoid the time precision issue between Java and MySQL)
During saving a new entity (id = 0), I can see the logs that the entity is being inserted into the database, I can also see the child entities being inserted in the database (via the Hibernate logs). During this process, I can also see the a select is done to get the version details from the database.
Soon after the entities are inserted and the session is being flushed, there is a dirty checking is done on the collection and I see a message in the log that the collection is unreferenced. Straight after this, I see an update statement on the parent entity's table and this is where the problem occurs as the version value used in the update statement is different to what is in the database, the HibernateOptimisticLockingFailureException exception is thrown.
Hibernate Code
getHibernateTemplate().saveOrUpdate(parentEntity);
// a break point here and wait for 1 sec before executing
// always get the HibernateOptimisticLockingFailureException
getHibernateTemplate().flush();
Parent mapping
#Access(AccessType.FIELD)
#OneToMany(mappedBy="servicePoint", fetch=FetchType.EAGER, cascade={CascadeType.ALL}, orphanRemoval=true, targetEntity=ServicingMeter.class)
private List<ServicingMeter> meters = new ArrayList<ServicingMeter>();
Child mapping
#Access(AccessType.FIELD)
#ManyToOne(fetch=FetchType.EAGER, targetEntity=ServicePoint.class)
#JoinColumn(name="service_point_id", nullable=false)
private ServicePoint servicePoint;
Questions:
1. Why is there an update date on the parent table?
2. How can I avoid this update from happening?
3. Is there something wrong with the way my one-to-many mapping is setup?
The annotated log file can be found here
I am looking a smart and easily readable way to get the id of a persisted entity using JPA. The id is an Integer.
One could think of the following solutions:
Without using GeneratedValue strategy. This requires looking for a free id before persisting, then putting it into the entity to be persisted: cumbersome, but works.
With a GeneratedValue strategy. The persistence provider will take care of the id generation. This looks smarter, but how to get the id?
See below for solution 2
MyEntity en = new MyEntity();
en.setName("My name");
em.persist(en);
System.out.println(en.getId());
This prints a null id!
Any suggestions? I am using MySql, EclipseLink, but need a portable solution.
persist is not guaranteed to generate the ID. The ID is guaranteed to be generated at flush time only. So if you really need the ID before the transaction ends (and the entity manager is thus flushed), call flush() explicitely to get the ID:
MyEntity en = new MyEntity();
en.setName("My name");
em.persist(en);
em.flush();
System.out.println(en.getId());
I have problems updating entities in Googles App Engine.
EntityManager em = ... // constructed like in the doc
MyEntity myE = new MyEntity();
myE.setType("1"); // String
em.persist(myE);em.refresh(myE);
myE.setType("2");
em.merge(myE);em.refresh(myE);
I expect a entity with type="2", but there is only one entity with type="1" :-(
That's the correct behaviour, let me explain (I assume that all your code runs in the same persistence context / transaction).
# This line sets the value in the in-memory object without changing the database
myE.setType("2");
# this line doesn't do anything here, as the entity is already managed in the current
# persistence context. The important thing to note is that merge() doesn't save the
# entity to the DB.
em.merge(myE);
# This reloads the entity from the DB discarding all the in-memory changes.
em.refresh(myE);
It's because merge creates a new instance of your entity, copies the state from the supplied entity, and makes the new copy managed. You can find more info on merge vs. persist here and a full discussion about it here
I was facing similar issue too. My issue is solved after my put the Reresh() after Commit().
It would be something like:
em.getTransaction().begin();
//Code to update the entity
em.persist(myE);
em.getTransaction().commit();
em.refresh(myE)
This will ensure the updated entity in JPA Cache gets refreshed with the updated data.
Hope this helps.