2

我有一个系统,我需要在并发上下文中创建 SomeEntity 的新实例,使用另一个实体作为“监视器”,如下所示:

//Begin transaction 
Monitor monitor = (Monitor) session.load(Monitor.class, monitor.getId(), LockOptions.UPGRADE);
SomeEntity entity = createSomeEntity(monitor);
//Save entity and commit transaction

当我使用 Mysql 时,这与使用 LockOptions.UPGRADE 加载监视器时发生的锁定完美配合,但是当我使用 HSQLDB 时,上面的代码不起作用,所有线程在数据库级别运行时都没有锁定,导致创建数据库中 SomeEntity 的许多实例。

主要问题

我需要使用 Hibernate 和 HSQLDB 在数据库级别锁定实体。

例子

为了举例说明我的需要,我创建了一个简单的项目,其中我有一个名为 Person 的实体类,它具有两个属性(id 和名称)和一个主程序,它将以并发方式更新单个实例的名称属性(Person#id = 1)。有 3 个线程竞争更新,所有将通过以下指令在数据库级别同步:

Person person = (Person) session.load(Person.class, 1L, LockOptions.UPGRADE);

使用上面的代码一次只有一个线程将更新 name 属性,所有其他线程将等待获取锁。

如果发生锁,可以在 Hibernate 生成的 sql 代码中可视化,如下所示:

select
    person0_.id as id1_0_0_,
    person0_.name as name2_0_0_ 
from
    Person person0_ 
where
    person0_.id=? for update

或者可以在 Eclipse IDE 的调试过程中可视化,在调用 session.load(...) 的行有一个断点(只有一个线程转到下一条指令,所有其他线程等待)。

所以,再次记住:这适用于 Mysql 但不适用于 HSQLDB

附件

package model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {

    @Id
    @GeneratedValue
    private Long id;
    private String name;

    public Person() {}

    public Person(String name) {
        this();
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

}

package app;

import static java.util.concurrent.Executors.newFixedThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import model.Person;

import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;

public class Main {

    private static final SessionFactory sessionFactory = init();

    public static void main(String[] args) {
        populate();
        changeNames("Christine", "Isabelle", "Katarina");
    }

    private static void changeNames(String... names) {
        ExecutorService executor = newFixedThreadPool(3);

        for (final String name : names) {
            executor.execute(new Runnable() {

                private Session session = sessionFactory.openSession();

                public void run() {
                    Transaction transaction = null;

                    try {
                        transaction = session.beginTransaction();

                        // At next line I put a breakpoint to debug in Eclipse Juno SR2
                        Person person = (Person) session.load(Person.class, 1L, LockOptions.UPGRADE);
                        person.setName(name);
                        session.saveOrUpdate(person);

                        transaction.commit();

                        System.out.println("Name changed to " + name);
                    } catch (Exception e) {
                        if (transaction != null) {
                            transaction.rollback();
                        }

                        e.printStackTrace();
                    }
                    finally {
                        session.close();
                    }
                }
            });
        }

        try {
            executor.shutdown();
            executor.awaitTermination(1, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void populate() {
        Session session = sessionFactory.openSession();

        Transaction transaction = session.beginTransaction();
        session.save(new Person("Old Name"));
        transaction.commit();

        session.close();
    }

    private static SessionFactory init() {
        Configuration configuration = new Configuration().configure();

        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                                            .applySettings(configuration.getProperties())
                                            .buildServiceRegistry();

        return configuration.buildSessionFactory(serviceRegistry);
    }
}

<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration PUBLIC 
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory name="">
        <mapping class="model.Person" />
    </session-factory>
</hibernate-configuration>

hibernate.connection.driver_class = org.hsqldb.jdbcDriver
hibernate.dialect = org.hibernate.dialect.HSQLDialect
hibernate.connection.url = jdbc:hsqldb:mem:test;hsqldb.tx=locks;hsqldb.write_delay=false
hibernate.connection.username = sa
hibernate.connection.password =
hibernate.hbm2ddl.auto = update

#hibernate.connection.driver_class = com.mysql.jdbc.Driver
#hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
#hibernate.connection.url = jdbc:mysql://localhost/test
#hibernate.connection.username = root
#hibernate.connection.password = 

hibernate.show_sql = true
hibernate.format_sql = true

<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>test</groupId>
    <artifactId>test</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.1.12.Final</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.18</version>
        </dependency>
        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>2.2.9</version>
        </dependency>
    </dependencies>
</project>
4

1 回答 1

2

似乎有一个休眠HSQLDialect错误。它没有for update在生成的SQLhttps ://hibernate.atlassian.net/browse/HHH-7479

该错误似乎在最新的测试版中得到解决:4.3.0.Beta1

于 2013-04-30T18:28:41.720 回答