Делает ли версионирование / оптимистическая блокировка Hibernate грязным экземпляр - PullRequest
1 голос
/ 29 октября 2019

Проблема обновления существующей записи при использовании аннотации @Version для оптимистической блокировки.

Наличие таблицы (скажем, "X") с этим подходом аннотации:

@Id()  @GeneratedValue(strategy = GenerationType.SEQUENCE)   
@Column(name="calcid")
private int calcId;
@Version @Column(name="version")
protected Integer version;

Использование этогоподход (pp - поставщик одноэтапной устойчивости):

Выборка записей:

Session session = [my persistency provider singleton].openSession();
session.beginTransaction();
List<X> xx = session.createQuery("from x",X.class).getResultList();
pp.closeSession();
return xx

Изменение записи x из списка xx и помещение этого (и только этого) в список отправленныхк процедуре слияния (не путайте со списком - в нем есть только одна запись - измененная, но таким образом я готов к объединению нескольких записей):

Слияние одной записи

  Session session = [my persistency provider singleton].openSession();
  session.beginTransaction(); 
  xx.forEach(x -> { session.merge(x);});
  try {
    session.getTransaction().commit();
  } catch (OptimisticLockException e) {
    pp.closeSession();
    throw e;
  }     
  pp.closeSession();

Хотя я могу проверить @Id непосредственно перед слиянием и увидеть, что он совпадает с исходно извлеченной записью, в результате получается новая запись с новым идентификатором.

Если я просто удаляю аннотацию версии (и поле версии), слияние ведет себя так, как ожидалось (тот же код, что и выше, только без @Version и version-column), обновляя существующую запись.

Обратите внимание, что, хотя проблема, по-видимому, связана с @ Version'ing, включенным в оптимистический подход блокировки, в приведенном выше примере задействован только один сеанс.

Кажется, что только @version заставляет Hibernate рассмотретьисходный экземпляр как новый (даже если идентификатор тот же) и, следовательно, создание нового с новым идентификатором.

Впервые в Hibernate, поэтому TIA для любой помощи!

Использование MariaDB (FKA MySQL) версии 10. Hibernate 5 и это cfg:

    <?xml version="1.0" encoding="UTF-8"?>

<!-- <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 5.3//EN" "http://www.hibernate.org/dtd/hibernate-configuration-5.3.dtd"> -->

<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd" PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN">
-<hibernate-configuration>
-<session-factory>
<!-- <property name="hbm2ddl.auto">update</property> -->
<property name="hbm2ddl.auto">update</property>
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<property name="connection.url">jdbc:mysql://localhost:3306/test</property>
<property name="connection.username">xxx</property>
<property name="connection.password"/>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.show_sql">false</property>
<mapping class="niels.test.Test"/>
</session-factory>
</hibernate-configuration>

С уважением

Ответы [ 2 ]

0 голосов
/ 02 ноября 2019

Если честно, я не смог воспроизвести вашу проблему. Итак, приведу полный рабочий пример. Надеюсь, это будет полезно.

Определения тестовых данных:

create table TST_DATA
(
   RECID int primary key,
   VAL varchar(100),
   OPTLOCK int
);
create sequence SEQ_TST_DATA_RECID start with 1 increment by 1;
insert into TST_DATA(RECID, VAL, OPTLOCK)
values (NEXTVAL(SEQ_TST_DATA_RECID), "row 1", 0), (NEXTVAL(SEQ_TST_DATA_RECID), "row 2", 0);

Конфигурация гибернации hibernate.cfg.xml:

<!DOCTYPE hibernate-configuration SYSTEM "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">org.mariadb.jdbc.Driver</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MariaDB103Dialect</property> 

        <property name="hibernate.connection.url">jdbc:mariadb://localhost:3306/TEST_HIBERNATE</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">xxxx</property>

        <property name="hibernate.id.new_generator_mappings">true</property>

        <property name="hibernate.current_session_context_class">thread</property>

        <!-- https://stackoverflow.com/questions/438146 -->
        <property name="hbm2ddl.auto">update</property>

        <!-- You can use this for checking actually generated sql -->
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="use_sql_comments">true</property>
    </session-factory>
</hibernate-configuration>

Сущность:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
@Table(name="TST_DATA")
public class TestData
{
   private Long recId;
   private String value;
   private int version;

   @Id
   @Column(name="RECID")
   @SequenceGenerator(name="SEQ_TST_GEN", sequenceName="SEQ_TST_DATA_RECID", allocationSize=1)
   @GeneratedValue(generator="SEQ_TST_GEN", strategy=GenerationType.SEQUENCE) 
   public Long getRecId()
   {
      return recId;
   }
   public void setRecId(Long val)
   {
      recId = val;
   }

   @Column(name="VAL")
   public String getValue()
   {
      return value;
   }
   public void setValue(String val)
   {
      value = val;
   }

   @Version
   @Column(name="OPTLOCK")
   public int getVersion()
   {
      return version;
   }
   public void setVersion(int val)
   {
      version = val;
   }
}

Модуль запуска:

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

import com.sternkn.inhibernate.entities.TestData;


public class Main
{
   private static final SessionFactory sessionFactory;

   static {
      MetadataSources metadata = new MetadataSources(
            new StandardServiceRegistryBuilder().configure("hibernate.cfg.xml").build()
      );
      metadata.addAnnotatedClass(TestData.class);
      sessionFactory = metadata.buildMetadata().buildSessionFactory();
   }

   public static void main(String[] args)
   {
      List<TestData> datas = getData();
      print(datas);

      merge(datas);
      print(datas);

      datas.forEach(row -> {
         row.setValue(row.getValue() + " new");
      });
      merge(datas);
      print(datas);

      TestData data = new TestData();
      data.setValue("test www...");
      data.setVersion(0);
      create(data);
   }

   private static void print(List<TestData> datas)
   {
      datas.forEach(row -> {
         System.out.println("row(" + row.getRecId() + ", " + row.getValue() + ", " + row.getVersion() + ")");
      });
   }

   private static List<TestData> getData()
   {
      Session session = sessionFactory.openSession();
      List<TestData> data;
      try
      {
         Transaction transaction = session.beginTransaction();
         data = session.createQuery("from TestData", TestData.class).getResultList();
         transaction.commit();
      }
      finally {
         session.close();
      }
      return data;
   }

   private static void merge(List<TestData> datas)
   {
      Session session = sessionFactory.openSession();
      try
      {
         Transaction transaction = session.beginTransaction();
         datas.forEach(row -> {
            session.merge(row);
         });
         transaction.commit();
      }
      finally {
         session.close();
      }

   }

   private static void create(TestData data)
   {
      Session session = sessionFactory.openSession();
      try
      {
         Transaction transaction = session.beginTransaction();
         session.persist(data);
         transaction.commit();
      }
      finally {
         session.close();
      }
   }
}

И, наконец, Maven pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sternkn.inhibernate</groupId>
    <artifactId>InHibernate</artifactId>
    <version>0.1</version>
    <packaging>jar</packaging>
    <name>InHibernate</name>
    <description>Hibernate learning project</description>

    <properties>
        <outputDirectory>${project.build.directory}/lib</outputDirectory>
        <java.version>1.8</java.version>
        <hibernate.version>5.4.4.Final</hibernate.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>

        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>2.5.1</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
      <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.xml.bind</groupId>
                    <artifactId>jaxb-api</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.glassfish.jaxb</groupId>
                    <artifactId>jaxb-runtime</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
      </dependencies>
    </dependencyManagement>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <resources>
            <resource>
                <directory>resources</directory>
            </resource>
        </resources>

        <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.8.1</version>
               <configuration>
                   <source>${java.version}</source>
                   <target>${java.version}</target>
               </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>2.10</version>
                <configuration>
                    <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    <overWriteReleases>true</overWriteReleases>
                    <overWriteSnapshots>true</overWriteSnapshots>
                    <overWriteIfNewer>true</overWriteIfNewer>
                </configuration>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <classpathPrefix>lib</classpathPrefix>
                            <mainClass>com.sternkn.inhibernate.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

В результате работы в приведенном выше примере мы получим следующее состояние БД:

SQL> select * from TST_DATA;
RECID  VAL          OPTLOCK
------ ------------ --------
1      row 1 new    1
2      row 2 new    1
3      test www...  0
0 голосов
/ 30 октября 2019

Пожалуйста, обратитесь к следующей статье для получения дополнительной информации

https://vladmihalcea.com/jpa-entity-version-property-hibernate/

...