使用 Hibernate 和 HSQLDB 在数据库级别锁定实体

Posted

技术标签:

【中文标题】使用 Hibernate 和 HSQLDB 在数据库级别锁定实体【英文标题】:Lock a entity at database level using Hibernate and HSQLDB 【发布时间】:2013-04-30 17:59:00 【问题描述】:

我有一个系统,我需要在并发上下文中创建 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 实例。

主要问题

我需要。

示例

为了举例说明我需要什么,我创建了一个简单的项目,其中我有一个名为 Person 的实体类,它具有两个属性(id 和名称)和一个主程序,它将以并发方式为单个实例更新 name 属性(人#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>

【问题讨论】:

【参考方案1】:

似乎有一个休眠HSQLDialect 错误。它不会在生成的SQL 中放入for update:https://hibernate.atlassian.net/browse/HHH-7479

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

【讨论】:

您为我节省了大量调试 hibernate 和 hsqldb 方言代码的时间!我已经尝试过 Hibernate 4.3.0.Beta1 并且锁按预期工作。

以上是关于使用 Hibernate 和 HSQLDB 在数据库级别锁定实体的主要内容,如果未能解决你的问题,请参考以下文章

如何在当前使用 HSQLDB 的 Spring 和 Hibernate 后台使用 MySQL

确保更新发生在 Hibernate/HSQLDB 中的并发锁定?

带有spring和hibernate的hsqldb启动时数据文件中的数据在哪里

使用本机 SQL 创建表适用于 Hibernate/HSQLDB,插入失败。为啥?

使用 Hibernate 和 HSQLDB 找不到表

如何使用 Hibernate 加载 HSQLDB 脚本