EJB Singleton и оптимистическая блокировка JPA вызывает исключение OptimisticLockException

У меня проблема, связанная с синглтоном EJB и блокировкой базы данных.

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

@Entity
@NamedQuery(name = Connote.FIND_NEXT, query = "SELECT c from Connote c WHERE c.consumed = false")
public class Connote implements Serializable {

    /**
    * 
    */
    private static final long serialVersionUID = 1L;
    public static final String FIND_NEXT = "Connotes.FindNext";

    @Id
    @Size(max = 15)
    private String connote;

    @Version
    private Long version;

    private Date insertedDate;
    private boolean consumed;
    private Date consumedDate;

    public Connote() {
        super();
    }

    public String getConnote() {
        return connote;
    }

    public void setConnote(String connote) {
        this.connote = connote;
    }

    public Date getInsertedDate() {
        return insertedDate;
    }

    public void setInsertedDate(Date insertedDate) {
        this.insertedDate = insertedDate;
    }

    public boolean isConsumed() {
        return consumed;
    }

    public void setConsumed(boolean consumed) {
        this.consumed = consumed;
    }

    public Date getConsumedDate() {
        return consumedDate;
    }

    public void setConsumedDate(Date consumedDate) {
        this.consumedDate = consumedDate;
    }

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }    
}

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

При создании нового заказа на фрахт необходимо получить номер следующей «неизрасходованной» партии из этого набора и доставить ее запрашивающей службе.

В данном случае служба представляет собой синглтон EJB, который предоставляет метод службы «calculateConnoteNumber ()». Поскольку класс ejb снабжен аннотацией «@Lock (LockType.WRITE)», одновременный доступ к этому методу должен быть предотвращен. При извлечении следующего неиспользованного объекта флаг «потреблен» будет установлен в истинное значение и будет установлена ​​дата потребления. Наконец, обновленная сущность будет объединена, и будет возвращено значение connote.

Однако во время нагрузочного тестирования мы столкнулись с "OptimisticLockExceptions" (в среднем 5 из 1000) при создании новых заказов.

Я не понимаю, как это возможно, поскольку доступ к бизнес-методу и, следовательно, к транзакции должен быть последовательным.

Изменение типа блокировки на уровне сохраняемости путем установки

query.setLockMode(LockModeType.PESSIMISTIC_WRITE); 

работает.

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

Используемая среда: WildFly 8.2.0, MySql 5.7, xa-datasource

/**
 * Session Bean implementation class ConnoteCalculatorFacade
 */
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@TransactionManagement(TransactionManagementType.CONTAINER)
@Lock(LockType.WRITE)
public class ConnoteCalculatorFacade {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @PersistenceContext(unitName = "some_unit_name")
    private EntityManager entityManager;

    /**
     * Default constructor.
     */
    public ConnoteCalculatorFacade() {
        if (logger.isDebugEnabled()) {
            logger.debug("ConnoteCalculatorFacade instantiated!");
        }
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public String calculateConnoteNumber() throws OrderManagementException {
        if (logger.isInfoEnabled()) {
            logger.info("Retrieving new RS connote!");
        }

        try {
            Query query = this.entityManager.createNamedQuery(Connote.FIND_NEXT);
            query.setMaxResults(1);
            //query.setLockMode(LockModeType.PESSIMISTIC_WRITE); <-- works when using pessimistic_write

            List<Connote> validConnoteList = query.getResultList();

            if (validConnoteList.size() == 1) {

                Connote connote = validConnoteList.get(0);
                connote.setConsumed(true);
                connote.setConsumedDate(new Date());

                this.entityManager.merge(connote);
                this.entityManager.flush();

                return connote.getConnote();
            } else {
                throw new OrderManagementException(AddressLabelExceptionReason.NO_CONNOTES_AVAILABLE);
            }

        } catch (Exception e) {
            logger.error("Error calculating connote number! ",e);
            throw new OrderManagementException(AddressLabelExceptionReason.ERROR_ASSIGNING_CONNOTE);
        }

    }
}

Обновление. Служба используется другим локальным EJB без сохранения состояния "PDFCreatorFacade", который внедряет синглтон

@EJB
private ConnoteCalculatorFacade connoteCalculator;
...

а затем вызывает метод синглтона в его (не аннотированном) бизнес-методе.

public Label createPdfDocument(Label label) throws  OrderManagementException {
    ....
    String connoteNumber = connoteCalculator.calculateConnoteNumber();
    ....
}

person chrisF    schedule 09.12.2016    source источник
comment
Это система с одним узлом?   -  person Steve C    schedule 09.12.2016
comment
Да, это одноузловая система.   -  person chrisF    schedule 09.12.2016
comment
Можете ли вы опубликовать код, который вызывает метод calculateConnoteNumber ... Как вы вызываете (или вводите) свое определение синглтона EJB?   -  person Carlitos Way    schedule 09.12.2016
comment
Обновил мой ответ, как вызывается служба.   -  person chrisF    schedule 10.12.2016


Ответы (1)


Вы получаете этот эффект, потому что вы не указали @ TransactionAttribute(REQUIRES_NEW) в своем calculateConnoteNumber методе.

Этот метод вызывается несколькими клиентами, которые, как я ожидаю, также являются EJB-компонентами. Первый EJB в цепочке вызовов запустит транзакцию и впоследствии попытается зафиксировать транзакцию, причем все это вне ваших средств управления параллелизмом.

person Steve C    schedule 09.12.2016
comment
SteveC, спасибо за ответ. Извините, я ошибся при публикации вопроса. Бизнес-метод действительно был аннотирован @TransactionAttribute (TransactionAttributeType.REQUIRES_NEW), я удалил его при переходе на пессимистическую блокировку и забыл вернуть его для вопроса. (Обновил мой вопрос сейчас). Все еще есть OptimisticLockingExceptions (средний 5/1000) с тестовый пример, указанный в моем вопросе. - person chrisF; 11.12.2016
comment
Вместо этого стоит попробовать WildFly 10.1. Вы можете столкнуться с старой доброй ошибкой в ​​реализации сервера. - person Steve C; 11.12.2016