Как реализовать пользовательскую потокобезопасную последовательность с помощью hibernate 4?

Мне нужно реализовать решение для генерации серийных номеров для объектов разных видов, но одного и того же типа (тот же класс, та же таблица). Более того, правила генерации серийных номеров определяются во время выполнения (начальный серийный номер, максимальное количество и т. д.). Я использую MySQL, спящий режим MySQL5Dialect не поддерживает генерацию последовательности, поэтому я решил реализовать эту функцию с помощью таблицы последовательности, где каждая строка представляет собой последовательность для разных типов объектов:

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

Я создал дао, который увеличивает значение:

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;
    }

И это вызывается из сервисного уровня как:

@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;
    }

Я пробовал все: установил уровень изоляции Isolation.SERIALIZABLE, чтобы программно зафиксировать транзакцию внутри метода, добавив к нему ключевое слово synchronized, также добавил запрос на блокировку, но я думаю, что он устарел, поскольку уровень изоляции уже установлен.

Тем не менее, если я создам 100 потоков и вызову этот метод 60 раз из каждого, в результате значение столбца nextVal будет около 4000 вместо 6000.

Я подозреваю, что здесь мне не хватает какой-то очень простой вещи, но я не могу понять, что нужно для того, чтобы это работало должным образом.

Спасибо за любые подсказки!


person wmiki    schedule 04.07.2014    source источник
comment
Привет, ты нашел способ решить проблему? Я столкнулся с той же проблемой.   -  person jvacaq    schedule 12.06.2021


Ответы (3)


Используйте select ... for update, Hibernate поддерживает его — см. мой ответ здесь.

person vanOekel    schedule 04.07.2014
comment
Спасибо за Ваш ответ! Да, это тоже жизнеспособная альтернатива. Я сделаю тест с этим, а также. - person wmiki; 05.07.2014

Это похоже на проблему с атомарной переменной :) Пожалуйста, взгляните на http://docs.oracle.com/javase/tutorial/essential/concurrency/atomicvars.html и попробуйте использовать AtomicInterger вместо int.

person Koziołek    schedule 04.07.2014
comment
Спасибо за ваш ответ, но это не имеет никакого отношения к моей проблеме... с точки зрения производительности синхронизированные методы могут быть не оптимальными, но они должны иметь тот же эффект. Более того, я думаю, что в идеале мне не нужна синхронизация, поскольку она должна быть обеспечена на уровне транзакций БД... синхронизированные методы были сделаны просто из паники :) - person wmiki; 04.07.2014

Кажется, ответ заключается в связанном с этим вопросе: использовать-гибернацию-в-многопоточном-приложении

Единственное, что я пропустил, это вызывать getCurrentSession().flush() каждый раз, когда я увеличивал счетчик, потому что я не учёл, что мой сервисный уровень работал с отдельным сеансом для каждого потока, созданного тестовым клиентом, таким образом, без сброса сеансов я не мог синхронизировать счетчик между нитями.

person wmiki    schedule 04.07.2014