исключение оптимистической блокировки для столбца даты и времени с Hibernate + Microsoft SQL Server 2016 + Microsoft JDBC - PullRequest
0 голосов
/ 04 сентября 2018

У меня проблема с комбинацией hibernate + mssql 2016 + microsoft jdbc driver + datetime column.
это же программное обеспечение прекрасно работает с другими базами данных (oracle, mysql, но также mssql <2016) и с mssql 2016, использующим драйвер jtds, поэтому я считаю, что проблема в драйвере microsoft jdbc. </p>

я использую версии этих библиотек:

<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);
    }

Журнал SQL Hibernate:

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

если я переключаюсь с на (с числовым столбцом базы данных), это работает.

У кого-нибудь была такая же проблема?
Спасибо,
David

1 Ответ

0 голосов
/ 04 сентября 2018

Вероятно, что-то не так, когда драйвер запрашивает дату для базы данных, что является поведением Hibernate по умолчанию, использующим тип метки времени в качестве оптимистической блокировки. Попробуйте использовать другой подход, получая время непосредственно от JVM. Это возможно с использованием предопределенного атрибута в файле HBM.

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

Попробуйте, это должно работать, но помните о недостатке этого решения, как указано в официальной документации Hibernate (cluster, more jvm, ...)

...