Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge and get the to-be-updated version without flush?

Is it possible to change a versioned entity instance, and get the to-be-incremented-version without using flush ? Because from what i read, im afraid flush is not a good practice because it's bad impact for the performance or even data corruption ? Im not sure :D


Here's a simple code and also the output as the comment :

/*
    Hibernate: select receivingg0_.id as id9_14_, receivingg0_.creationDate as creation2_9_14_, ... too long
    the version before modification : 16
    the version after modification : 16
    after merge the modification, the version is : 16
    Hibernate: update ReceivingGood set creationDate=?, modificationDate=?, usercreate_id=?, usermodify_id=?,  ... too long
    after flushing the modification, the version is finally : 17
*/
public void modifyHeaderAndGetUpdatedVersion() {
    String id = "3b373f6a-9cd1-4c9c-9d46-240de37f6b0f";
    ReceivingGood receivingGood = em.find(ReceivingGood.class, id);
    System.out.println("the version before modification : " + receivingGood.getVersion());

    receivingGood.setTransactionNumber("NUM001xyz");
    System.out.println("the version after modification : " + receivingGood.getVersion());

    receivingGood = em.merge(receivingGood);
    System.out.println("after merge the modification, the version is : " + receivingGood.getVersion());

    em.flush();
    System.out.println("after flushing the modification, the version is finally : " + receivingGood.getVersion());
}

In my test, the version got incremented after the flush. The instance returned from the merge operation doesnt have the incremented version.

But in my case, i would like to return the entity to my webui in the form of DTO, and the entity should have the version-after-flush/commit before converting it to DTO and returning it to the UI to be rendered. And then the UI could have the latest version, and will pass this version for the next submission.


Is there any way where i can get the latest version without doing flush ?

Thank you !


UPDATE


In my experience, incrementing this manually can be problematic as can be seen from this example below. In this example, we have 2 flushes.

The 1st one is to synchronize the changes to the db connection, so that a stored procedure call from the same connection could see the changes made from the entityManager.

The second flush is called to get the final version. And we can see this is incremented twice. So getting version just from the manual increment without flushing wouldnt work in this condition, as we have to really count how many flushes are being made.

/*
Hibernate: select receivingg0_.id as id9_14_, receivingg0_.creationDate as creation2_9_14_, .. too long
the version before modification : 18
the version after modification : 18
after merge the modification, the version is : 18
now flushing the modification, so that the stored procedure call from the same connection can see the changes
Hibernate: update ReceivingGood set creationDate=?, modificationDate=?, usercreate_id=?, .. too long
after flushing the modification, the version is : 19
Hibernate: update ReceivingGood set creationDate=?, modificationDate=?, usercreate_id=?, .. too long
after the second flush, the version got increased again into : 20
*/
public void modifyHeaderAndGetUpdatedVersionWith2Flushes() {
    String id = "3b373f6a-9cd1-4c9c-9d46-240de37f6b0f";
    ReceivingGood receivingGood = em.find(ReceivingGood.class, id);
    System.out.println("the version before modification : " + receivingGood.getVersion());

    //auditEntity(receivingGood, getUser("3978fee3-9690-4377-84bd-9fb05928a6fc"));
    receivingGood.setTransactionNumber("NUM001xyz");
    System.out.println("the version after modification : " + receivingGood.getVersion());

    receivingGood = em.merge(receivingGood);
    System.out.println("after merge the modification, the version is : " + receivingGood.getVersion());
    System.out.println("now flushing the modification, so that the stored procedure call from the same connection can see the changes");
    em.flush();
    System.out.println("after flushing the modification, the version is : " + receivingGood.getVersion());

    receivingGood.setTransactionNumber("NUM001abc");

    em.flush();
    System.out.println("after the second flush, the version got increased again into : " + receivingGood.getVersion());
}

Does this mean i really have to depend on the flush at the end to get the latest version for the modified entity ?


UPDATE 2


Here's a simple example of a service method that will update the ReceivingGood entity and should return a DTO that has the newest version.

public ReceivingGoodDTO update(ReceivingGood entity) {
  // merge it
  entity = entityManager.merge(entity);

  // the version is not incremented yet, so do the flush to increment the version
  entityManager.flush(); // if i dont do this, the dto below will get the unincremented one

  // use a mapper, maybe like dozer, to copy the properties from the entity to the dto object, including the newest version of that entity
  ReceivingGoodDTO dto = mapper.map(entity, dto);

  return dto;
}

and here is an example that makes use of that method :

@Transactional
public ReceivingGoodDTO doSomethingInTheServiceAndReturnDTO() {
  // do xxx ..
  // do yyy ..
  dto = update(entity);
  return dto; // and the transaction commits here, but dto's version isnt increased because it's not a managed entity, just a plain POJO
}
like image 455
Albert Gan Avatar asked Mar 11 '11 08:03

Albert Gan


People also ask

What does EntityManager flush () do?

The EntityManager. flush() operation can be used the write all changes to the database before the transaction is committed. By default JPA does not normally write changes to the database until the transaction is committed. This is normally desirable as it avoids database access, resources and locks until required.

Which method of the session interface is used to update an already existing record in the database table?

Hibernate merge can be used to update existing values, however this method create a copy from the passed entity object and return it.

Does Hibernate flush commit?

By default, Hibernate uses the AUTO flush mode which triggers a flush in the following circumstances: prior to committing a Transaction. prior to executing a JPQL/HQL query that overlaps with the queued entity actions.

Which method in JPA is used for updating data in a table?

JPA and Hibernate provide different methods to persist new and to update existing entities. You can choose between JPA's persist and merge and Hibernate's save and update methods.


2 Answers

I'll again recommend reading more about Hibernate and how it works, be it by its documentation or "Java Persistence with Hibernate".

You are seeing this in flush operation because that's where the database updates happens. If you just omit the flush and commit the transaction, you'll see the same behavior. Meaning, whenever your unit-of-work finishes, Hibernate will flush the changes to the database. It's in this step that Hibernate compares and updates the version number. If the database version is 17 and you are updating the version 16, Hibernate will thrown an exception, about updating a stale object.

That said, you can "predict" what would be the next version by incrementing 1 on the value that you currently have in your instance. But this will never be real, because this is only effectively incremented right before updating the database record. So, any concurrent changes will not be visible to your thread, unless you query the database.

Edit:

You are seeing two increments because you are doing two flushes. The "version" is a technique for "optimistic" locking, meaning, you expects that only one thread will update a record during any unit-of-work (in a broader sense, think as a "user action", where he lists the records, pick one and updates it). It's purpose is mainly to avoid Hibernate from updating a stale object, where two users pick the same record for editing, do some concurrent changes and updates it. One of the edits will have to be rejected, and the first one hitting the database wins. As you are updating the record twice (by calling flush twice), you are seeing two increments. But it's really not about flushes, it's about "updates issued in the database". If you had two transactions, you'd see the same behavior.

That said, it's worth noting that this technique should be managed by Hibernate. You should not increment it by yourself. It's often a good practice to not provide setters for this kind of fields.

In other words: you should regard the "version" as a read-only value and that you have no control over it. So, it's safe to display its value on-screen for the user, but it's not safe to manipulate it (or "determine" the next version). At most, you can make a "best-guess", predicting that it'll be current_version+1.

like image 82
jpkrohling Avatar answered Nov 05 '22 17:11

jpkrohling


The latest version is the current one + 1. You could increment the version yourself in the entityToDto() method.

But in your example, doing the flush won't cause such a performance problem, IMO, since Hibernate will do it anyway at commit time. There will be two flushes, but the second one won't have anything to flush anymore.

like image 40
JB Nizet Avatar answered Nov 05 '22 19:11

JB Nizet