JPA 实体在 Ubuntu 上更新,但在 Windows 上抛出乐观锁定异常

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

考虑这个例子,我在其中创建了两个 JPA 实体并使用 Spring Data JPA 存储库来执行简单的 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;
    }
}

之后,我为 Demo 实体创建了一个 JPA 存储库,从 JpaRepository -

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

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

对应的服务实现-

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 文件 -

<?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 应用-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>

现在每当我尝试在 Windows 7 -

上更新这样的实体时
public class App {
    public static void main(String[] args) {
        DemoService demoService = (DemoService) SpringHelper.INSTANCE.getBean("demoService");
        demoService.testRun();
    }
}

我遇到这样的异常 -

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

如果我 运行 Ubuntu 中的相同示例,那么 我完全没有异常并且我的应用程序成功完成 。这是为什么?

我正在使用 Windowsw 7 64 位版本 -

OS Name: Microsoft Windows 7 Enterprise

OS Version: 6.1.7601 Service Pack 1 Build 7601

我的Ubuntu版本是12.04.5 64位版。

JDK用于Windows:jdk7更新75

JDK用于Ubuntu:jdk7更新51

MySQL Windows 中的服务器版本:5.6.23-log MySQL 社区服务器 (GPL)

MySQL Ubuntu 中的服务器版本:5.5.41-0ubuntu0.12.04.1 (Ubuntu)

感觉这跟MySQL5.6的时间戳精度有关。 MySQL5.6.4引入微秒精度,会造成版本不匹配,加锁失败

这与您的问题没有直接关系,但在 高度并发的 环境中,您不应使用 timestamp 作为您的 version,因为两个实体可能有同一时间!最好使用如下所示的 long/int 版本-

@Version long version;

另外,从设计的角度来看,请让您的超级 class abstract 也一样。你能看看改变这些是否能解决你的问题吗?