Обнаружение тупика базы данных с помощью JPA при сохранении объектов с постоянными ссылками - PullRequest
2 голосов
/ 26 марта 2011

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

Несмотря на то, что раньше я успешно делал это с помощью Hibernate, я не смог заставить его работать с JPA, на чем я сейчас и концентрируюсь.

Предварительная информация:

  • Приложение Guice 3.0, использующее постоянное расширение, спящий режим (с аннотациями) и c3p0 (до 5 соединений). Мы внедряем EntityManager от провайдера, и я могу подтвердить, что объект EntityManager остается тем же, что и объект транзакции в течение всего этого процесса.
  • Разработанные как часть веб-приложения, текущие проблемы возникают только в автоматизированных интеграционных тестах, использующих JUnit и dbUnit. Доступ осуществляется к одноэлементному объекту (.in (Scopes.SINGLETON)), но этот объект Singleton зависит только от инжектора и @Transactional для обеспечения безопасности потока.
  • В базе данных есть таблица файлов, которая сопоставлена ​​с аннотированным FileContainer, и таблица mimetype, которая сопоставлена ​​с объектом MIMEType. В приложении при создании нового файла мы сначала извлекаем объект MIMEType как NamedQuery и сохраняем его.
  • База данных PostgreSQL 8.4 с использованием postgresql-8.4-702.jdbc3.jar.
  • Система умеренно требовательна к обстоятельствам, при которых происходит сбой: мне нужно создать отдельный поток и выполнить оттуда доступ.

Отображение для поля mimetype в объекте FileContainer:

@ManyToOne(fetch=FetchType.EAGER, cascade={})  
@JoinColumn(name="mimetype_id", referencedColumnName="id", nullable=false, updatable=true)

Объект MIMEType аннотируется следующим образом:

@Entity
@Table(name="mimetypes")  
@org.hibernate.annotations.Immutable  
@Cacheable(true)  
@NamedQuery(name="mt.ext", query="from MIMEType where extension = :ext",  
    hints= {@QueryHint(name="org.hibernate.fetchSize", value="1"),  
            @QueryHint(name="org.hibernate.readOnly", value="true")})

Все вышеприведенные аннотации являются версиями javax.persistence.

Метод addFile помечен @com.google.inject.persist.Transactional. Изменение этого параметра на использование введенного EntityManager в классе не влияет на результат.

Процесс создания объекта выглядит следующим образом:

  1. Возьмите EntityManager и FileDAO из инжектора. Они оба приходят от поставщика.
  2. Получите объект MIMEType, который мы будем использовать, из базы данных, вызвав em.createNamedQuery ("mt.ext", MIMEType.class) .setParameter ("ext", extension) .getSingleResult ();
  3. Создайте объект FileContainer и заполните его, включая вызов `setMimetype (MIMEType)` объектом, который мы только что получили
  4. Call dao.save(fileContainer), который устанавливает базовое поле в FileContainer объекте, а затем вызывает em.persist(Object). Извлечение этого из DAO и использование EntityManager, введенного ранее, не меняет результат.

В этот момент, как только EntityManager.flush() происходит (где бы это ни происходило - в конце транзакции или перед ней), происходит ВСТАВКА, и код блокируется.

Когда я проверяю pg_stat_activity и сравниваю его с pg_locks, я вижу, что оператор вставки блокируется соединением «Idle in транзакция» и находится в состоянии ожидания. Удаление вставки MIMEType (и установка для столбца разрешения пустых значений) позволяет коду работать нормально, а проверка значений MIMEType указывает на то, что он был получен правильно.

Любые мысли или идеи приветствуются.

EDIT:

Это иерархия, надеюсь, она проясняет порядок операций и что происходит:

  • Test Harness (Инициализирует инжектор и использует бегун для ввода его в определенные тестовые классы)
  • Контрольный пример
  • Тема начата
  • Ввести транзакционный блок на одноэлементном объекте (инициализированном тем же самым инжектором) из потока, подтвердив, что EntityManager объекты идентичны во всех объектах DAO и где происходит ошибка.
  • EntityManager объект последовательно получается с помощью вызова injector.getInstance(EntityManager.class) или путем получения объекта Provider.
  • Пройдите описанный выше процесс, все в одной теме.
  • Стоп flush()

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

Ответы [ 2 ]

3 голосов
/ 26 марта 2011

Единственное, на что я обращаю внимание в связи с тем, что вы здесь сказали, что, вероятно, является проблемой, это:

Система умеренно требовательна к обстоятельствам, при которых она отказывает: Мне нужно создать отдельный поток и выполнить оттуда доступ.

Guice-Persist единицы работы и транзакции ограничиваются потоком, используя ThreadLocal.Я могу определенно увидеть, как что-то подобное происходит, если вы делаете что-то в отдельном потоке (хотя я не уверен, как именно).Не могли бы вы объяснить более подробно, когда и как используется этот отдельный поток?Изнутри @Transactional метод?Используется ли EntityManager, который был введен в исходную нить?

Редактировать: Хмм ... Я не вижу ничего плохого в том, что вы делаете.Похоже, что работа и транзакция выполняются в одном потоке.Не уверен, что там происходит.

2 голосов
/ 28 марта 2011

Я нашел проблему.

В другом месте кода у меня была несвязанная, но продолжающаяся транзакция, которая запускалась методом @Before. По какой-то причине EntityManager блокировал эту транзакцию. Это не было проблемой, когда все было в одном потоке и для определенных операций только для чтения, но когда он был разделен на отдельный поток и попытка записи начала ждать другой транзакции, которая не заканчивалась в то время транзакции и коснулся многих из тех же таблиц.

...