JPA @Version field doesn't get incremented
Asked Answered
H

6

16

I'm new to JPA and Hibernate and I have a problem with optimistic locking. I have a class which has an @Version annotated field. When I update the Entity represented by this class, the version counter does not increase. Here is my code: The class:

@Entity
@Table (name = "Studenten")
public class Student implements Serializable{

private static final long serialVersionUID = 705252921575133272L;

@Version
private int version;
private int matrnr;
private String name;
private int semester;


public Student (){
    
}
public Student (int matrnr, String name){
    this.matrnr = matrnr;
    this.name = name;
}

public Student (int matrnr, String name, int semester){
    this(matrnr, name);
    this.semester = semester;
}

and here is the main method:

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("VEDA_Vortrag");
    EntityManager em = emf.createEntityManager();
    
    EntityTransaction tx = em.getTransaction();
    
    try{
        tx.begin();
        Student s = em.find(Student.class, 195948);
        s.setSemester(1);
        em.persist(s);
        tx.commit();
    }catch(Exception e){
        if(tx != null && tx.isActive()){
            tx.rollback();
            System.out.println("Error in Transaction. Rollback!");
        }
    }
    finally{
        em.close();
        emf.close();
    }
}

and here is what the console says:

Hibernate: 
select
    student0_.matrnr as matrnr0_0_,
    student0_.name as name0_0_,
    student0_.semester as semester0_0_,
    student0_.version as version0_0_ 
from
    Studenten student0_ 
where
    student0_.matrnr=?
Hibernate: 
    update
        Studenten 
    set
        name=?,
        semester=?,
        version=? 
    where
        matrnr=?

Can somebody tell me what's wrong?

Edit: ok, I have tried something. I've set the locking to LockModeType.OPTIMISTIC_FORCE_INCREMENT and got this Error-Message:

ERROR: HHH000099: an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: cannot force version increment on non-versioned entity
Jun 20, 2014 9:00:52 AM org.hibernate.AssertionFailure <init>

So it seems clear that I have a non-versioned entity. But why? I have the @Version Annotation in my class.

Edit: I begin to think that the problem is in the persistence.xml. Here it is:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
    version="2.0">
<persistence-unit name="VEDA_Vortrag">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/uni"/>
        <property name="javax.persistence.jdbc.user" value="*******"/>
        <property name="javax.persistence.jdbc.password" value="******"/>
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
        <property name="hibernate.show_sql" value="true"/>
        <property name="hibernate.format_sql" value="true"/>
    </properties>
</persistence-unit>

Is there anything wrong?

Hayfork answered 20/6, 2014 at 6:32 Comment(2)
Maybe you imported the wrong @Version (from some other library)?Teishateixeira
No, it's from javax.persistence package as all the other annotations are too.Hayfork
R
14

The entityManager.persist() method is meant to be used only for new entities that have never been persisted before.

Because you are fetching an entity you don't need to call persist or merge anyway. The dirty checking will do the update on your behalf.

The commit will trigger the flush anyway so you should see the update.

Make sure you use the javax.persistence.Version annotation and not from another package (spring data or something similar).

Roller answered 20/6, 2014 at 10:3 Comment(10)
Thanks for your suggestion. I removed the call on the entityManager.persist Method but the problem is still the same. I also checked the package from wich the Version annotation is from. It is the javax.persistence package. :(Hayfork
The SQL log outputs the version, so you always see it 0, even when modifying and merging.Roller
ok, but when I call a "select * from studenten" shouldn't I see the new Version value in the output?Hayfork
Yes you should, even the response returned by merge should contain the updated versionRoller
well, then the question remains: why doesn't it work ;). The package for the annotation is right, the persist-Method call is removed but the problem remainsHayfork
Try also to add @Column(name="student") to the student field too.Roller
HOLY MOTHER OF.......!! You gave me the right hint. In fact there is no field called student, but I simply forgot to add the /@Version annotation to the getter and setter of the version field before the /@Column annotation of that field. Thanx a lot!!! :DHayfork
You're welcome. The field/property access is taken from the Id location and @Version should match it as well.Roller
"Make sure you use the javax.persistence.Version annotation and not from other package (spring data or something similar)". You just saved me!!!Venessavenetia
I added a "version" field, with @Version. It updates in database fine. Opt Lock exception works fine. Problem is after save, entity does not get updates, DB updates, but entity still has old version value. What am I doing wrong?Cyprinid
F
16

This may not be exactly on topic (as I am using SpringData repositories) but when I was looking for the answer to my problem I ended up here, so this may help the next person.

the following and my version field was not being returned updated.

savedJpa = repository.save(updateJpa);

In the end my solution was

savedJpa = repository.saveAndFlush(updateJpa);
Fanaticize answered 23/1, 2019 at 0:17 Comment(1)
This did it for me too, but I don't understand why. It makes me think there is still an underlying issue. If anyone can explain, that would be much appreciated.Tufts
R
14

The entityManager.persist() method is meant to be used only for new entities that have never been persisted before.

Because you are fetching an entity you don't need to call persist or merge anyway. The dirty checking will do the update on your behalf.

The commit will trigger the flush anyway so you should see the update.

Make sure you use the javax.persistence.Version annotation and not from another package (spring data or something similar).

Roller answered 20/6, 2014 at 10:3 Comment(10)
Thanks for your suggestion. I removed the call on the entityManager.persist Method but the problem is still the same. I also checked the package from wich the Version annotation is from. It is the javax.persistence package. :(Hayfork
The SQL log outputs the version, so you always see it 0, even when modifying and merging.Roller
ok, but when I call a "select * from studenten" shouldn't I see the new Version value in the output?Hayfork
Yes you should, even the response returned by merge should contain the updated versionRoller
well, then the question remains: why doesn't it work ;). The package for the annotation is right, the persist-Method call is removed but the problem remainsHayfork
Try also to add @Column(name="student") to the student field too.Roller
HOLY MOTHER OF.......!! You gave me the right hint. In fact there is no field called student, but I simply forgot to add the /@Version annotation to the getter and setter of the version field before the /@Column annotation of that field. Thanx a lot!!! :DHayfork
You're welcome. The field/property access is taken from the Id location and @Version should match it as well.Roller
"Make sure you use the javax.persistence.Version annotation and not from other package (spring data or something similar)". You just saved me!!!Venessavenetia
I added a "version" field, with @Version. It updates in database fine. Opt Lock exception works fine. Problem is after save, entity does not get updates, DB updates, but entity still has old version value. What am I doing wrong?Cyprinid
W
4

JPA only updates the version only when there really exists some dirty data (uncommitted) present in the entity. I assume Student 195948 already had semester value as "1". Though you are setting the value, which is equal to the previous value, ideally there is no change in the entity state. Hence JPA is not updating the version value. If you set the semester value to different value (not the earlier one), then there is a change in the state of the entity and it updates the version column accordingly.

Thanks, Jagan

Waver answered 20/6, 2014 at 6:40 Comment(9)
Thanks for the answer but that was exactly what I did ;) . The value for semester was 2 and I updated it to 1.Hayfork
But why is an SQL UPDATE being issued then? And one that does not even bother to check the version field, which seems highly dangerous.Teishateixeira
Well, I am in fact updating the student table with a new value for the field semester. So it should check the version field. I don't know why it does not. That's my problem.Hayfork
@Thilo, version comaparision is against the object in memory to the object that was fetched by the em.find(). There would be no explicit query to check the version. If both are different JPA automatically throws an exception.Waver
@Raistlin, are you asking about why the version check is not happening or the value is not being incremented in database after transaction commit?Waver
both. I don't understand why the value in the database stays 0 and why there is no version check in the sql statement.Hayfork
@user3694267: "There would be no explicit query to check the version". Yes, but the update needs to read WHERE matnr = ? AND VERSION = theOldVersion. Without that extra condition there is no optimistic locking going on.Teishateixeira
Yes, you are correct. But anyway its treating entity as a non-versioned entity. Need to look into this.Waver
This is the exact answer I'm looking for. Version will not be updated if value is not changed compared with the previous one. Thanks a lot.Ithaca
C
0

I may find a important point of your issue. Try to replace

private int version

to

private Long version

For the reason that @Version tag may not work with primitive type.Thanks.

Colossus answered 29/3, 2018 at 3:45 Comment(1)
Didn't work with EclipseLink 2.2.1Izy
C
0

I had a different problem that most here. I was developing a REST API with JAX-RS and was trying to change an existing resource, but the @Version field was always 0 no matter how many times I hit the PUT HTTP endpoint (the endpoint that updates the data).

Once the update HTTP endpoint was called it would call the following code:

SERVER CODE:

public Todo save(Todo todo) {
    Todo createdTodo = this.entityManager.merge(todo);
    return createdTodo;
}

NOTE: Merge method creates a new Entity if a entity does not already exist (if the @Id of the entity does not exist or the id is 0). If the @Id of the entity exists it just updates the existing entity with the new data.

Client was sending the following json

{
  "todoTitle" : "testTitle",
  "priority" : 100 
}

My mistake: I forget to send the ID in the JSON and when my mapped put the JSON data into a Java object it set the "id" value to the default value of 0 (because primativies can't be null). Because of that, every time I send a PUT HTPP request (tried to update a resource) I was actually creating a new entity and that new entity would always have version 0, because it was just created.

Clupeoid answered 11/10, 2021 at 12:8 Comment(0)
G
0

When we use the save() method, the data associated with the save operation won't be flushed to the DB unless, and until, an explicit call to the flush() or commit() method is made.

If we use JPA implementations like Hibernate, then that specific implementation will be managing the flush and commit operations.

One thing we have to keep in mind here is that, if we decide to flush the data by ourselves without committing it, then the changes won't be visible to the outside transaction unless a commit call is made in this transaction or the isolation level of the outside transaction is READ_UNCOMMITTED.

Genetic answered 9/10, 2022 at 19:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.