Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA entity gets updated on Ubuntu but throws Optimistic Locking Exception on Windows

Consider this example in which I have created two JPA entities and used Spring Data JPA repositories to perform simple CRUD -

import java.sql.Timestamp;
import javax.persistence.Version;

@MappedSuperclass
public class AbstractValueObject {
    @Id
    @GeneratedValue
    private Long id;

    @Version
    @Column(name = "time_stamp")
    private Timestamp version;

    public Long getId() {
        return id;
    }

    @Override
    public String toString() {
        if (id == null) {
            return "";
        }

        return id.toString();
    }
}

@Entity
@Table(name = "demo")
public class Demo extends AbstractValueObject {
    private String name;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "demo")
    private List<Owner> owners;

    public Demo() {
        owners = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Owner> getOwners() {
        return Collections.unmodifiableList(owners);
    }

    public void addOwner(Owner owner) {
        this.owners.add(owner);
        owner.setDemo(this);
    }

    public void addAllOwners(List<Owner> owners) {
        this.owners.addAll(owners);

        for (Owner owner : owners) {
            owner.setDemo(this);
        }
    }

    public void update(Demo demo) {
        this.setName(demo.getName());
        this.owners.clear();
        this.addAllOwners(demo.getOwners());
    }
}

@Entity
@Table(name = "owner")
public class Owner extends AbstractValueObject {
    private String attribute;

    @ManyToOne(cascade = CascadeType.ALL, optional = false)
    @JoinColumn(name = "demo_id", nullable = false)
    private Demo demo;

    public String getAttribute() {
        return attribute;
    }

    public void setAttribute(String attribute) {
        this.attribute = attribute;
    }

    public Demo getDemo() {
        return demo;
    }

    public void setDemo(Demo demo) {
        this.demo = demo;
    }
}

After that, I have created a JPA repository for the Demo entity, extending from JpaRepository -

import org.springframework.data.jpa.repository.JpaRepository;

public interface DemoRepository extends JpaRepository<Demo, Long> {}

Corresponding service implementation -

import javax.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;

public class DemoServiceImpl implements DemoService {
    @Resource
    private DemoRepository demoRepository;

    @Override
    @Transactional
    public Demo create(Demo demo) {
        return demoRepository.save(demo);
    }

    @Override
    @Transactional
    public Demo update(long id, Demo demo) {
        Demo dbDemo = demoRepository.findOne(id);
        if (dbDemo == null) {
            return demo;
        }

        dbDemo.update(demo);
        return dbDemo;
    }

    @Transactional
    public void testRun() {
        Owner owner = new Owner();
        owner.setAttribute("attribute");

        Demo demo = new Demo();
        demo.setName("demo");
        demo.addOwner(owner);

        this.create(demo);

        demo.setName("another");
        this.update(demo.getId(), demo);
    }
}

persistence.xml file -

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
    version="2.1">

    <persistence-unit name="jpa-optimistic-locking" transaction-type="RESOURCE_LOCAL">
    </persistence-unit>
</persistence>

Spring app-context.xml -

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <context:component-scan base-package="com.keertimaan.example.jpaoptimisticlocking" />
    <jpa:repositories base-package="com.keertimaan.example.jpaoptimisticlocking.repository" />

    <bean id="demoService" class="com.keertimaan.example.jpaoptimisticlocking.service.DemoServiceImpl" />

    <!-- JPA/Database/Transaction Configuration -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
        <property name="user" value="root" />
        <property name="password" value="admin123" />

        <property name="minPoolSize" value="1" />
        <property name="maxPoolSize" value="2" />
        <property name="acquireIncrement" value="1" />
        <property name="maxStatements" value="5" />
        <property name="idleConnectionTestPeriod" value="500" />
        <property name="maxIdleTime" value="1000" />
        <property name="loginTimeout" value="800" />
    </bean>
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="jpa-optimistic-locking" />
        <property name="dataSource" ref="dataSource" />
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
        </property>
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

Now whenever I try to update an entity like this on Windows 7 -

public class App {
    public static void main(String[] args) {
        DemoService demoService = (DemoService) SpringHelper.INSTANCE.getBean("demoService");
        demoService.testRun();
    }
}

I get an exception like this -

Exception in thread "main" org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [com.keertimaan.example.jpaoptimisticlocking.domain.Demo] with identifier [4]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.keertimaan.example.jpaoptimisticlocking.domain.Demo#4] at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:228) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:155) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:519) at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757) at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726) at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:478) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207) at com.sun.proxy.$Proxy31.testRun(Unknown Source) at com.keertimaan.example.jpaoptimisticlocking.App.main(App.java:9) Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.keertimaan.example.jpaoptimisticlocking.domain.Demo#4] at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285) at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183) at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525) at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463) at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:349) at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350) at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56) at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222) at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425) at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101) at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177) at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:515) ... 9 more

If I run the same example in Ubuntu, then I get no exception at all and my application completes successfully. Why is that?

I am using Windowsw 7 64-bit edition -

OS Name: Microsoft Windows 7 Enterprise

OS Version: 6.1.7601 Service Pack 1 Build 7601

and my Ubuntu version is 12.04.5 64-bit edition.

JDK used in Windows: jdk7 update 75

JDK used in Ubuntu: jdk7 update 51

MySQL Server version in Windows: 5.6.23-log MySQL Community Server (GPL)

MySQL Server version in Ubuntu: 5.5.41-0ubuntu0.12.04.1 (Ubuntu)

like image 336
Nur Bahar Yeasha Avatar asked Feb 16 '15 03:02

Nur Bahar Yeasha


People also ask

How does the optimistic locking version property work in JPA?

Knowing how the optimistic locking version property works is very important when using JPA and Hibernate, as it allows you to prevent the lost update anomaly when a given entity is being modified by multiple concurrent users.

What is optimistic_force_increment in JPA?

JPA provides us with two different optimistic lock modes (and two aliases): OPTIMISTIC obtains an optimistic read lock for all entities containing a version attribute. OPTIMISTIC_FORCE_INCREMENT obtains an optimistic lock the same as OPTIMISTIC and additionally increments the version attribute value. READ is a synonym for OPTIMISTIC.

What are the pessimistic lock modes in JPA?

JPA specification defines three pessimistic lock modes which we're going to discuss: PESSIMISTIC_READ – allows us to obtain a shared lock and prevent the data from being updated or deleted. PESSIMISTIC_WRITE – allows us to obtain an exclusive lock and prevent the data from being read, updated or deleted.

How to use optimistic locking in Java Persistence API?

To achieve that, we can use an optimistic locking mechanism provided by Java Persistence API. This way, multiple updates made on the same data at the same time do not interfere with each other. 2. Understanding Optimistic Locking In order to use optimistic locking, we need to have an entity including a property with @Version annotation.


1 Answers

I have a feeling that this is related to the timestamp precision of MySQL 5.6. MySQL 5.6.4 introduced microsecond precision, which will cause a version mismatch, and the locking will fail.

like image 81
Shakkhor Avatar answered Oct 12 '22 14:10

Shakkhor