3

I need to implement a solution to generate serial numbers for objects of diferent kinds but same type (same class, same table). What's more the rules for serial generation are defined at runtime (starting serial, maximum number, etc.). I'm using MySQL, hibernate MySQL5Dialect does not support sequence generation, thus I chose to implement this feature using a sequence table where each row is a sequence for a different kind of objects:

+--------------------+--------------+------+-----+---------+-------+
| Field              | Type         | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+-------+
| seqenceName        | varchar(255) | NO   | PRI | NULL    |       | 
| nextVal            | bigint(20)   | NO   |     | NULL    |       | 
+--------------------+--------------+------+-----+---------+-------+

I created a dao which increments the value:

public synchronized long getNextValue(String seqenceName) throws MySequenceNotFoundException {

        MySequence seq = findByID(seqenceName);

        if (seq == null) {
            throw new MySequenceNotFoundException("Sequence does not exist with name: " + seqenceName);
        }//if not exists

        long nextVal = seq.getNextVal();
        getCurrentSession().saveOrUpdate(seq);

        return nextVal;
    }

And this is called from the service layer as:

@Transactional(readOnly=false, propagation=Propagation.REQUIRES_NEW, isolation=Isolation.SERIALIZABLE)
    public synchronized long incSequence(String seqName) throws MySequenceNotFoundException {

        getCurrentSession().getTransaction().begin();

        MySequence seq = sequenceDao.findByID(seqName);

        LockRequest lockRequest = getCurrentSession().buildLockRequest(new LockOptions(LockMode.PESSIMISTIC_WRITE));
        lockRequest.lock(seq);

        long l = sequenceDao.getNextValue(seqName);
        getCurrentSession().getTransaction().commit();

        return l;
    }

I tried everything: setting isolation level to Isolation.SERIALIZABLE, to programmatically commit the transaction within the method, adding synchronized keyword to it, also added a lock request but I think it's obsolete as the isolation level is already set.

Still if I create 100 threads and call this method 60 times from each, the result is that the value of the nextVal column is around 4000 instead of 6000.

I suspect I missing some very basic thing here but cannot find out what is needed for this to work as expected.

Thanks for any hints!

wmiki
  • 63
  • 5

3 Answers3

2

Use select ... for update, Hibernate has support for it - see my answer here.

Community
  • 1
  • 1
vanOekel
  • 6,358
  • 1
  • 21
  • 56
  • Thanks for your answer! Yes, it is also a viable alternative. I'll make a test with this as well. – wmiki Jul 05 '14 at 00:37
1

Its look like Atomic Variable problem :) Please take a look at http://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html and try to use AtomicInterger instead of int.

Koziołek
  • 2,791
  • 1
  • 28
  • 48
  • Thanks for your answer but it has nothing to do with my problem... from performance point of view synchronized methods might not be optimal but they should have the same effect. What's more I think ideally I shouldn't need synchronization as it should be ensured on the DB transaction level... the synchronized methods were done simply out of panic :) – wmiki Jul 04 '14 at 17:16
1

It seems the answer lies in a related question: how-to-use-hibernate-in-a-multi-threaded-application

The only thing I missed is to call getCurrentSession().flush() every time I incremented the counter because what I didn't consider is that my service layer worked with a separate session for each thread the test client created, thus without flushing the sessions I could not synchronize the counter between the threads.

Community
  • 1
  • 1
wmiki
  • 63
  • 5