Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate / MySQL concurrency issue

I get an insanely strange result with Hibernate (4.1.9.Final) and MySQL (14.14 Distrib 5.5.29, InnoDB table):

When I persist something to the database using one thread and try to fetch it using another thread, Hibernate does not always find the entity. (Despite the fact that I'm properly committing the transaction and closing the persist-session before opening the load-session.)

Some observations:

  • I can not reproduce this using a single-threaded program.

  • I can see the "missing" entity in the database, and

  • If I restart the application Hibernate can successfully load the entity.

Here's an SSCCE illustrating the problem. (Imports left out for brevity):

public class StressTest {

    static SessionFactory sessionFactory;

    public static void main(String[] args) throws InterruptedException,
                                                     ExecutionException {

        // Configure Hibernate
        Configuration conf = new Configuration();
        conf.setProperty("hibernate.dialect",
                         "org.hibernate.dialect.MySQLInnoDBDialect");
        conf.configure();

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

        sessionFactory = conf.buildSessionFactory(serviceRegistry);

        // Set up producer / consumer
        BlockingQueue<Long> queue = new LinkedBlockingQueue<Long>();

        new Consumer(queue).start();
        new Producer(queue).start();
    }

}

class DummyEntity {
    long id;
    public long getId() { return id; }
    public void setId(long id) { this.id = id; }
}

Producer class (creates DummyEntities and persists them).

class Producer extends Thread {

    BlockingQueue<Long> sink;

    public Producer(BlockingQueue<Long> sink) {
        this.sink = sink;
    }

    @Override
    public void run() {
        try {
            while (true) {

                Session session = StressTest.sessionFactory.openSession();

                DummyEntity entity = new DummyEntity();
                entity.setId(new Random().nextLong());

                session.beginTransaction();
                session.save(entity);
                session.getTransaction().commit();
                session.close();

                sink.put(entity.getId());
            }
        } catch (InterruptedException ignore) {
            System.exit(-1);
        }
    }
}

Consumer class (loads DummyEntities from database):

class Consumer extends Thread {

    BlockingQueue<Long> source;

    public Consumer(BlockingQueue<Long> source) {
        this.source = source;
    }

    @Override
    public void run() {

        try {
            while (true) {

                long entityId = source.take();

                Session session = StressTest.sessionFactory.openSession();
                Object entity = session.get(DummyEntity.class, entityId);
                session.close();

                if (entity == null) {
                    System.err.printf("Entity with id %d NOT FOUND", entityId);
                    System.exit(-1);
                }
            }
        } catch (InterruptedException ignore) {
            System.exit(-1);
        }
    }
}

Finally, here's the mapping-xml for the DummyEntity.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping
    default-cascade="all"
    default-lazy="false">

    <class name="se.stresstest.DummyEntity">

        <id name="id" type="long">
            <generator class="assigned"/>
        </id>

    </class>

</hibernate-mapping>

The resulting output always ends with something like:

Entity with id -225971146115345 NOT FOUND

Any ideas why?

(This is a refined version of a previous question.)

like image 542
aioobe Avatar asked Nov 13 '22 07:11

aioobe


1 Answers

This behavior can be consistent with a transaction isolation mode of REPEATABLE READ, which happens to be the default transaction isolation mode for InnoDB:

http://dev.mysql.com/doc/refman/5.5/en/set-transaction.html#isolevel_repeatable-read

If your application logic is dependent upon being able to see data committed in other transactions after the current transaction was started - at the expense of repeatable reads (data can/will change as a result of manipulation in other transactions) - you should set the transaction isolation to READ COMMITTED:

http://dev.mysql.com/doc/refman/5.5/en/set-transaction.html#isolevel_read-committed

like image 161
Todd Farmer Avatar answered Nov 15 '22 05:11

Todd Farmer