пятница, 29 ноября 2013 г.

Плавающая ошибка из-за PersistenceContextType

Сдали проект. Запустили в эксплуатацию. Через 3-4 дня приходит первый алёрт от Заказчика об ошибке, блокирующей работу админки. Перезагрузка или переустановка приложения проблемного модуля восстанавливает работоспособность админки. Приложение работает с использованием Spring 3.0 + Hibernate 4.1 по спецификации JPA 2.0.

Недели 2 переписки с Заказчиком и выяснения обстоятельств проявления ошибки + 2 недели активного поиска причины ошибки. Ошибка плавающая, проявляется с периодичностью 3-5 дней только при сохранении @Entity объектов. В stacktrace рутовая ошибка разная, но чаще всего:
Caused by: org.hibernate.HibernateException: Flush during cascade is dangerous

Экспериментальным путем было выявлено, что ошибка проявляется только при одновременной интенсивной работе нескольких редакторов. Важная деталь: другой модуль этого же приложения такой ошибки не проявляет. Похоже на проблему конфигурации или модели данных конкретного модуля?

Манипуляции с исходным кодом админки, DAO и модели в результате привели к интересной ошибке:
java.util.ConcurrentModificationException
 at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)

Это навело на мысль, что ошибка возникает в условиях одновременного сохранения в БД достаточно большого числа разных потоков. Нашли в документации Spring упоминание специфики использования DAO на базе JPA, и в частности подключения EntityManager к DAO с PersistenceContextType.EXTENDED:
The alternative, PersistenceContextType.EXTENDED, is a completely different affair: This results in a so-called "extended EntityManager", which is not thread-safe and hence must not be used in a concurrently accessed component such as a Spring-managed singleton bean.

Проверили: так и есть, один из предыдущих разработчиков модуля включил в аннотацию @PersistanceContext атрибут PersistenceContextType.EXTENDED. Включение этого атрибута позволило ему упростить код в отношении получения коллекций связанных сущностей по LazyFetch, однако в результате привело к плавающей ошибке!

вторник, 19 ноября 2013 г.

Постраничная навигация AssetPublisher

В Liferay Portal 6.1.1 GA2 недокументированная особенность метода AssetEntryServiceUtil.getEntriesCount

Если настроить Публикатор по одному типу ресурса (например, "Сетевой контент" подтип "Новость"), без фильтрации и группировки по категориям и тегам, включить постраничную навигацию и отметить галочку "Включить права", то подсчет найденных ассетов никогда не превысит 200! В результате если у вас наберется новостей скажем 398, то при количестве элементов на страницу 20 выдасться всего 10 страниц. Игра с настройками не помогает - похоже какое-то внутрисистемное ограничение на максимальное количество ассетов выбираемых для проверки разрешений, типа asset.filter.search.limit

Быстрым решением было получение общего количества ассетов удовлетворяющих условию через AssetEntryLocalServiceUtil и а-ля аппроксимация в сторону убывания к правильному значению. Пример модификации /jsp/html/portlet/asset_publisher/view_dynamic_list.jspf (начиная со строки 93):
 else if (!groupByClass) {
  assetEntryQuery.setClassNameIds(classNameIds);

  /* total = AssetEntryServiceUtil.getEntriesCount(assetEntryQuery); */
  total = AssetEntryLocalServiceUtil.getEntriesCount(assetEntryQuery);
  if(total > delta){
   do{
    assetEntryQuery.setEnd(total);
    assetEntryQuery.setStart(total - delta);
    results = AssetEntryServiceUtil.getEntries(assetEntryQuery);
    if(results.size() < delta){
     total = total - delta + results.size();
    }
   } while (results.size() == 0);  
  }else{
   total = AssetEntryServiceUtil.getEntriesCount(assetEntryQuery);
  }

  searchContainer.setTotal(total);