Сохранение записи с использованием JPA в планировщике загрузки Spring

Я использую Spring Boot Scheduler для ежедневного выполнения запроса к БД, чтобы найти некоторые записи на основе условия и обновить возвращенные записи. Извлечение записей с помощью JPA работает нормально, но когда я перебираю их, обновляю и пытаюсь сохранить каждую обновленную запись, я получаю следующую ошибку: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction

Caused by: javax.persistence.RollbackException: Error while committing the transaction at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:81) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:562) ... 30 more Caused by: java.lang.NullPointerException at com.xxx.yyy.config.JpaAuditingConfiguration.auditorProvider$lambda-0(JpaAuditingConfiguration.kt:15) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208) at com.sun.proxy.$Proxy168.getCurrentAuditor(Unknown Source) at java.base/java.util.Optional.map(Optional.java:265) at org.springframework.data.auditing.AuditingHandler.getAuditor(AuditingHandler.java:109) at org.springframework.data.auditing.AuditingHandler.markModified(AuditingHandler.java:104) at org.springframework.data.jpa.domain.support.AuditingEntityListener.touchForUpdate(AuditingEntityListener.java:112).

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

@Component
class Scheduler(
    private val repository: Repository
) {
    @Scheduled(cron = "0 0 2 * * *")
    fun expire() {
       val records = repository.findRecords()
       for (record in records) {
            try {
                 // Call some external API using record.id but this part is commented out for now until the saving works
                 record.active = false
                 repository.save(record)
            } catch (ex: Exception) {
                logger.error("Error expiring record " + record.id)
                logger.error("Exception: ${ex.printStackTrace()}")
                continue
            }
        }
    }
}

Исключение нулевого указателя происходит в конфигурации JpaAuditingConfiguration, которую я использую для хранения дат created_at и last_modified_at. Вот код, который у меня есть для этого класса:

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {
    @Bean
    fun auditorProvider(): AuditorAware<String> {
        return AuditorAware { Optional.of(SecurityContextHolder.getContext().authentication.name) }
    }
}

Можете ли вы включить соответствующую строку для Caused by: java.lang.NullPointerException?

void void 21.11.2022 19:08

Зачем делать обновление в коде, просто напишите оператор обновления вместо выбора + извлечения + изменения + сохранения. 1 запрос тоже может это сделать. Также ваш код неверен, поскольку поиск и сохранение являются отдельными транзакциями. Этот код медленный, так как каждое сохранение представляет собой отдельную транзакцию (или 1 большую транзакцию, и тогда это неправильно, так как вы выполняете перехват + продолжение).

M. Deinum 21.11.2022 19:41

@M.Deinum M.Deinum Я просматриваю записи, потому что мне нужно вызвать какой-то внешний API, используя идентификатор записи, прежде чем обновлять запись и сохранять. Эта часть кода пока закомментирована, потому что проблема не в ней. Я обновил свой код выше и добавил комментарий, где должен работать внешний API.

Simo 21.11.2022 21:35

@voidvoid это происходит в JpaAuditingConfiguration классе. Я добавил строки, которые показывают это в ошибке, и добавил код, который я использую в этом классе.

Simo 21.11.2022 21:56

@Simo, когда ваш планировщик выполняется, у вас нет принципала в контексте безопасности, однако, когда это делает запрос, у вас есть принципал из сеанса. Вот почему вы получаете ошибку. Пожалуйста, подтвердите это, чтобы я мог опубликовать ответ :)

void void 21.11.2022 22:02

@voidvoid, возможно, вы правы, но я не понимаю, почему субъект безопасности не применяется к планировщику

Simo 21.11.2022 22:10

@Simo, откуда он узнает, кого аутентифицировать? Вам нужно вручную аутентифицировать кого-то, обычно для этого вы создаете отдельного пользователя app.

void void 21.11.2022 22:12
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Интеграция Slack и Spring Boot: как отправлять сообщения из Java-приложений
Интеграция Slack и Spring Boot: как отправлять сообщения из Java-приложений
Как отправлять сообщения в slack с помощью spring boot легко и без зависимостей.
0
7
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Ваш JpaAuditingConfiguration требует, чтобы контекст безопасности был ненулевым, когда вы вносите изменения. Когда вы запускаете свою задачу в планировщике, нет активного запроса, поэтому нет активного сеанса, и поэтому ваша аутентификация недействительна. Обычно это решается путем создания специального пользователя app и его аутентификации вручную в запланированном задании.

Спасибо. Я смог заставить его работать благодаря вашему ответу и ответу, представленному в этом комментарии.

Simo 22.11.2022 18:43

Другие вопросы по теме