使用 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启动时数据文件中的数据在哪里