2015-04-08 4 views
0

Я создаю веб-приложение с Spring и JPA, поддерживаемое Hibernate 4.3.6 для настойчивости. Некоторые предпосылки: результаты работы хранятся в репозитории, и контролер ResultsController проверяет их. В интерфейсе используется длительный опрос, поэтому ResultsController создает объект DeferredResult и затем отжимает поток, чтобы периодически проверять завершение задания, чтобы он мог заполнить отложенный результат и вызвать ответ.Исключая объект JPA из сохранения HibernateContext кэширования?

private DeferredResultsResponse getResults(String idString, String runType, boolean returnOnNotDone) { 
    String userId = userService.getCurrentUser(); 

    // Some basic checks; is the ID a valid format, etc. Not relevant, 
    // but the "response" variable is set if we find a problem 

    final DeferredResultsResponse deferredResult = new DeferredResultsResponse(runId, runType, userId, returnOnNotDone); 
    if (response != null) { 
     deferredResult.setResult(response); 
    } else { 
     Thread t = new Thread(() -> completeResult(deferredResult)); 
     t.run(); 
    } 

    return deferredResult; 
} 

private void completeResult(final DeferredResultsResponse result) { 
    final ResultsIdentifier id = new ResultsIdentifier(result.getJobId(), result.getRunType()); 
    int attemptCount = 0; 
    boolean returnOnUnfinished = result.isReturnOnUnfinished(); 

    while (!result.hasResult() && attemptCount < MAX_ATTEMPTS) { 
     attemptCount++; 
// ------- Problem line: ----------- 
     Optional<JobStatus> statusMaybe = jobStatusService.get(new ResultsIdentifier(result.getJobId(), result.getRunType())); 

     if (!statusMaybe.isPresent()) { 
      result.setResult(new ResultsResponse(null, false, null, "Unable to find job status entry.")); 
      continue; 
     } 

     JobStatus status = statusMaybe.get(); 
     // Incomplete job cases: sleep or respond "not done" based on the flag 
     if (!status.isComplete() && returnOnUnfinished) { 
      result.setResult(new ResultsResponse(false, false, null, null)); 
      continue; 
     } else if (!status.isComplete()) { 
      sleep(); 
      continue; 
     } 

     // Cases of completion: respond based on success 
     // Various business logic of processing results 
    } 
    if (!result.hasResult()) { 
     result.setResult(new ResultsResponse(true, false, null, String.format("Time out after %d checks", MAX_ATTEMPTS))); 
    } 
} 

Вопрос заключается в: запрос на линии Проблема не всегда сообщать об изменениях в состоянии задания. После того, как я посмотрел, я проследил это до глубин Hibernate. В SessionImpl есть поле типа StatefulPersistenceContext, и оно хранит копию объекта JobStatus с момента его вытаскивания из базы данных. Затем он повторно использует эту копию для всех последующих запросов в том же сеансе.

Теперь я понимаю, что могу решить эту проблему, получив текущую сессию и вызвав clear() или обновив (статус). Тем не менее, для меня, чтобы оттянуть занавес JPA и использовать материал Hibernate напрямую, когда повсюду он опосредуется через репозитории Spring/JPA, это плохая форма. Итак, есть ли способ разметки XML-файла ORM, чтобы исключить кеширование определенного типа в PersistentContext?


Для справки, здесь JobStatus.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd" 
      version="2.0"> 
<entity class="project.model.JobStatus"> 
<attributes> 
    <embedded-id name="jobIdentifier" /> 

    <basic name="complete" optional="false" /> 
    <basic name="userId" optional="false" /> 

    <basic name="successful" optional="true" /> 
    <basic name="message" optional="true" /> 

    <basic name="lastUpdateTime" optional="false"> 
    <temporal>TIMESTAMP</temporal> 
    </basic> 
</attributes> 
</entity> 
</entity-mappings> 

jobIdentifier является, который не содержит ничего, кроме одного элемента без детей.

Кроме того, здесь JobStatusService с транзакционными аннотациями:

public interface JobStatusService { 

/** 
* Retrieve the statuses of all jobs for the current user. 
* @return All jobs' statuses 
*/ 
@Transactional(readOnly = true) 
Iterable<JobStatus> getAllByUser(); 

/** 
* Retrieve the status of a particular job 
* @param identifier the combined job ID and type 
* @return The persisted job status 
*/ 
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW) 
Optional<JobStatus> get(ResultsIdentifier identifier); 

/** 
* Save the passed status, subbing in the current user's ID if none is set, 
* and updating the "last updated" time 
* @param status the job status object 
* @return The persisted status object 
*/ 
@Transactional(readOnly = false) 
@Modifying 
JobStatus save(JobStatus status); 

/** 
* Delete the status of a particular job 
* @param identifier the combined job ID and type 
*/ 
@Transactional(readOnly = false) 
@Modifying 
void remove(ResultsIdentifier identifier); 

/** 
* Remove all stored job statuses for the given user id. 
* @param userId User id 
*/ 
@Transactional(readOnly = false) 
@Modifying 
void clearByUser(String userId); 
+0

FWIW, который не является файлом «hbm». Это «orm.xml», часть JPA. –

+0

Правда! Извините, парень, который начал проект, изначально назвал их всеми * .hbm.xml, и я не потрудился менять имена, несмотря на то, что он был там, в xmlns. – DVA

ответ

2

ResultsController создает объект DeferredResult, а затем закручивает нить

На самом деле, нет, это не то, что он делает , Вы никогда не запускаете нить. Вы выполняете все в одном и том же потоке, и вы возвращаете только отложенный результат, как только возвращается completeResult(). Для того чтобы действительно начать нить, вам придется заменить

t.run(); 

по

t.start(); 

Теперь, чтобы убедиться, что проблема линия всегда идет в базу данных и перезагружает новое значение статуса работы, то, что вы должны сделать это, чтобы убедиться, что jobStatusService.get() работает в отдельной транзакции. Используя Spring, это обычно делается путем аннотации метода с помощью

@Transactional(propagation = Propagation.REQUIRES_NEW) 
+0

Я дал это попробовать, и он, похоже, не работает, и Propagation.NOT_SUPPORTED тоже звучит многообещающе. (Что угодно, чтобы сделать недействительными/очистить/сменить сеансы!) Добавил JobStatusService в OP, если другие аннотации плохо взаимодействуют, чтобы ваше предложение не получилось. Кроме того, после исправления ошибки потоковой передачи (спасибо за определение этого!) Я обнаружил, что 'completeResult()' начал бросать исключения из-за отсутствия сеанса вообще.Поэтому, чтобы передать сессию этому потоку, мне, возможно, придется сосать его и в любом случае заставить контроллер знать о сеансах Hibernate. – DVA

+0

У меня есть чувство, что я принципиально пропущу что-то (как настойчивость, так и на завершение асинхронного результата). Я никогда не получал «@ Async», чтобы работать правильно), но на удивление трудно найти хорошую документацию о том, как Spring взаимодействует между его структурами и с другими структурами, к которым он может привязываться. – DVA

+1

Сессия обычно имеет тот же жизненный цикл, что и весенняя транзакция. И статус транзакции хранится Spring в переменной ThreadLocal. Поэтому начало потока, который вызывает транзакционную услугу, должен начать новую транзакцию Spring, и это должно привести к привязке нового сеанса к транзакции. Трудно сказать, почему это не работает в ваших тестах. –

Смежные вопросы