Question

I have a system where I need to create a new instance of SomeEntity in a concurrent context, using another entity as "monitor" as exemplified below:

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

When I'm using Mysql this works perfectly with lock happening when I'm loading monitor with LockOptions.UPGRADE, but when I'm using HSQLDB the code above don't work and all threads run without lock at the database level, causing creation of many instances of SomeEntity in database.

Main Question

I need to lock a entity at database level using Hibernate and HSQLDB.

Examples

To exemplify what I need, I have created a simple project where I have a entity class called Person with two attributes (id an name) and a main program that will update the name attribute in a concurrent way for a single instance (Person#id = 1). There are 3 threads competing to update and all will be synchronized at the database level through the following instruction:

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

Using the code above only one thread at once will update the name attribute and all other threads will wait to obtain the lock.

The lock, if happens, can be visualized in the sql code generated by Hibernate and will looks like this:

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

or can be visualized during debug on Eclipse IDE, with a breakpoint at line where session.load(...) is called (only one thread go to the next instruction, all other threads wait).

So, remembering again: this works on Mysql but not in HSQLDB.

Attachments

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>
Was it helpful?

Solution

It seems there is a hibernate HSQLDialect bug. It doesn't put a for update in the generated SQL: https://hibernate.atlassian.net/browse/HHH-7479

The bug seem to be resolved in latest beta: 4.3.0.Beta1

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top