带有Hibernate + Microsoft SQL Server 2016 + Microsoft JDBC的datetime列上的乐观锁异常

时间:2018-09-04 09:05:47

标签: java sql-server hibernate sql-server-2016

我在休眠+ mssql 2016 +微软jdbc驱动程序+ datetime列组合时遇到问题。
相同的软件可以完美地与其他数据库(oracle,mysql和mssql <2016)以及使用jtds驱动程序的mssql 2016完美配合,因此我相信问题出在Microsoft jdbc驱动程序中。

我使用以下库版本:

<dependencies>
  <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.3.5.Final</version>
  </dependency>        
  <dependency>
    <groupId>com.microsoft.sqlserver</groupId>
    <artifactId>mssql-jdbc</artifactId>
    <version>7.0.0.jre8</version>
  </dependency>        
</dependencies>

hibernate.cfg.xml:

<hibernate-configuration>
  <session-factory>
        <property name="connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
        <property name="connection.url">jdbc:sqlserver://sql2016host\Sql2016;databaseName=problem</property>
        <property name="connection.username">user</property>
        <property name="connection.password">password</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.SQLServer2012Dialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
      <mapping resource="User.hbm.xml"/>
  </session-factory>
</hibernate-configuration>

User.hbm.xml:

<hibernate-mapping package="...">
  <class name="User" table="USERS">
    <id name="id" type="long" column="ID">
      <generator class="native">
        <param name="sequence_name">HIBERNATE_SEQUENCE</param>
      </generator>
    </id>
    <timestamp name="lastChange" column="LAST_CHANGE"/>
    <property name="userId" column="USERID" type="string" not-null="true"/>
    <property name="domain" column="DOMAIN" type="string" />
    <property name="expiredOn" column="EXPIRED_ON" type="timestamp" />
    <property name="firstName" column="FIRSTNAME" type="string" not-null="true"/>
    <property name="lastName" column="LASTNAME" type="string" not-null="true"/>
    <property name="language" column="LANGUAGE" type="string" not-null="true"/>
    <property name="role" column="ROLE" type="long" not-null="true"/>
    <property name="powerManager" column="POWERMANAGER" type="boolean" not-null="true"/>
    <property name="notes" column="DESCRIPTION" type="string" not-null="false"/>
    <property name="company" column="COMPANY" type="string" not-null="false"/>
    <property name="organization" column="ORGANIZATION" type="string" not-null="false"/>

  </class>
</hibernate-mapping>

数据库表:

CREATE TABLE USERS(
    ID numeric(19, 0) IDENTITY(1,1) NOT NULL,
    LAST_CHANGE datetime NOT NULL,
    USERID nvarchar(64) NOT NULL,
    DOMAIN nvarchar(64) NULL,
    SID nvarchar(255) NULL,
    EXPIRED_ON datetime NULL,
    FIRSTNAME nvarchar(255) NOT NULL,
    LASTNAME nvarchar(255) NOT NULL,
    LANGUAGE nvarchar(255) NOT NULL,
    ROLE numeric(19, 0) NOT NULL,
    POWERMANAGER tinyint NULL,
    AUTH_TYPE int NULL,
    AUTH_PWD_ID numeric(19, 0) NULL,
    AUTH_PWD_CHANGE tinyint NULL,
    AUTH_PWD_NOEXPIRE tinyint NULL,
    AUTH_PWD_ENFORCE_POLICIES tinyint NULL,
    AUTH_LOGIN_SUCCESS_DATE datetime NULL,
    AUTH_LOGIN_ERROR_DATE datetime NULL,
    AUTH_LOGIN_ERROR_COUNT int NOT NULL,
    DESCRIPTION nvarchar(255) NULL,
    COMPANY nvarchar(64) NULL,
    ORGANIZATION nvarchar(64) NULL
)

User.java:

public class User {

    private long id;
    private Date lastChange;        

    private String userId;
    private String domain;
    private String firstName;
    private String lastName;
    private String language;
    private String notes;
    private String company;
    private String organization;
    private Date expiredOn;
    private long role;
    private boolean powerManager;

    public User() {
    }

    public long getId() ..
    public void setId(long id) ...

    public Date getLastChange() ...
    public void setLastChange(Date lastChange) ...      

    public String getUserId() ...
    public void setUserId(String userId) ...

    public String getDomain() ...
    public void setDomain(String domain) ...

    ....
}

Main.java,这是一个命令行,单线程main():

    private void test() {

        try {
            Session session = HibernateUtil.getSessionFactory().getCurrentSession();

            session.beginTransaction();

            // i load and update the user 'USER'        
            User u = getUserAuth(session, "USER");
            u.setCompany("NEWCO");
            session.update(u);

            session.getTransaction().commit();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    private User getUserAuth(Session session, String userId) throws Exception
    {
      TypedQuery<User> query = session.createQuery("from User u where u.domain = NULL and upper(u.userId) = upper(:userId)", User.class);
      query.setParameter("userId", userId);
      List<User> users = query.getResultList();
      if (users.size() != 1)
         throw new Exception(userId);
      return users.get(0);
    }

Hibernate的SQL日志:

Hibernate: select user0_.ID as 0_, user0_.LAST_CHANGE as JS2_0_, user0_.USERID as JS3_0_, user0_.DOMAIN as JS4_0_, user0_.EXPIRED_ON as JS5_0_, user0_.FIRSTNAME as JS6_0_, user0_.LASTNAME as JS7_0_, user0_.LANGUAGE as JS8_0_, user0_.ROLE as JS9_0_, user0_.POWERMANAGER as JS10_0_, user0_.DESCRIPTION as JS11_0_, user0_.COMPANY as JS12_0_, user0_.ORGANIZATION as JS13_0_ from USERS user0_ where (user0_.DOMAIN is null) and upper(user0_.USERID)=upper(?)
Hibernate: update USERS set LAST_CHANGE=?, USERID=?, DOMAIN=?, EXPIRED_ON=?, FIRSTNAME=?, LASTNAME=?, LANGUAGE=?, ROLE=?, POWERMANAGER=?, DESCRIPTION=?, COMPANY=?, ORGANIZATION=? where ID=? and LAST_CHANGE=?

异常日志:

ERROR: HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [....User#6]]
javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [.....User#6]
    at org.hibernate.internal.ExceptionConverterImpl.wrapStaleStateException(ExceptionConverterImpl.java:226)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:93)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1460)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:511)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3283)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2479)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:98)
    at ....BugTimestamp.test(BugTimestamp.java:43)
    at ....BugTimestamp.main(BugTimestamp.java:19)
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [...User#6]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2522)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3355)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3229)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3630)
    at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:146)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1454)
    ... 10 more

如果我从切换到(带有数字数据库列),则可以使用。

有人遇到过同样的问题吗?
谢谢
大卫

1 个答案:

答案 0 :(得分:0)

当驱动程序向数据库请求日期时,可能出了点问题,这是使用时间戳类型作为乐观锁定的默认Hibernate行为。尝试使用其他方法,直接从JVM获取时间。使用HBM文件中的预定义属性可以做到这一点。

<timestamp name="lastChange" column="JS1_LAST_CHANGE" source="vm"/>

尝试,这应该可以工作,但是请注意此解决方案的缺点,如官方Hibernate文档(群集,更多jvm等)中突出显示的那样