Почему hibernate envers вызывает PersistenceException?

Вопрос или проблема

Я использую hibernate envers для аудита изменений полей. Hibernate envers периодически выбрасывает PersistenceException. Только перезапуск pod в k8s помогает с этим. Так что ошибка регулярна, но происходит нечасто (несколько раз в месяц).

Тело ответа:


"status": 500,
    "error": "Внутренняя ошибка сервера",
    "message": "PersistenceException",
    "path": "/api/bookinglegs/47449a96-a63f-40d0-9111-9d84eb1530ef/history",
    "code": "internal_server_error"

Логи:

2024-09-15T12:35:38.808Z  INFO 1 --- [io-8080-exec-14] d.m.s.c.e.h.CommonGlobalExceptionHandler : Запрос не выполнен: ErrorResponseDto{status=500, error="Внутренняя ошибка сервера", message="PersistenceException", path="/api/bookinglegs/d47804d2-a929-4242-88ce-0c46417d6e84/history", code="internal_server_error"}
java.base/java.lang.Thread.run(Thread.java:833)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1739)
org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:400)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:741)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
io.sentry.spring.jakarta.SentrySpringFilter.doFilterInternal(SentrySpringFilter.java:71)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
io.sentry.spring.jakarta.tracing.SentryTracingFilter.doFilterInternal(SentryTracingFilter.java:87)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:351)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:191)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:233)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:62)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.context.SecurityContextHolderFilter.doFilterInternal(SecurityContextHolderFilter.java:69)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:93)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:107)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
de.mopla.server.security.filter.FirebaseSecurityFilter.doFilterInternal(FirebaseSecurityFilter.java:143)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:179)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:100)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:120)
org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:126)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:374)
org.springframework.security.web.access.intercept.AuthorizationFilter.doFilter(AuthorizationFilter.java:100)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:365)
org.springframework.security.web.FilterChainProxy.lambda$doFilterInternal$3(FilterChainProxy.java:231)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116)
io.sentry.spring.jakarta.SentryUserFilter.doFilterInternal(SentryUserFilter.java:56)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:110)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
de.mopla.server.security.filter.ResponseAddAllowMethodsHeaderFilter.doFilter(ResponseAddAllowMethodsHeaderFilter.java:30)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223)
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:814)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:880)
jakarta.servlet.http.HttpServlet.service(HttpServlet.java:705)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:895)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1003)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:973)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1080)
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797)
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152)
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207)
java.base/java.lang.reflect.Method.invoke(Method.java:568)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jdk.internal.reflect.GeneratedMethodAccessor478.invoke(Unknown Source)
de.mopla.server.core.restcontroller.HistoryRestController$$SpringCGLIB$$0.getBookingLegHistory(<generated>)
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:703)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor.invoke(AuthorizationManagerBeforeMethodInterceptor.java:199)
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:752)
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
java.base/java.lang.reflect.Method.invoke(Method.java:568)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
jdk.internal.reflect.GeneratedMethodAccessor478.invoke(Unknown Source)
de.mopla.server.core.restcontroller.HistoryRestController.getBookingLegHistory(HistoryRestController.java:26)
de.mopla.server.core.services.HistoryService.getHistoryForEntity(HistoryService.java:43)
org.hibernate.envers.query.internal.impl.AbstractAuditQuery.getResultList(AbstractAuditQuery.java:115)
org.hibernate.envers.query.internal.impl.RevisionsOfEntityQuery.list(RevisionsOfEntityQuery.java:179)
org.hibernate.envers.query.internal.impl.RevisionsOfEntityQuery.getQueryResults(RevisionsOfEntityQuery.java:211)
org.hibernate.envers.query.internal.impl.AbstractAuditQuery.buildAndExecuteQuery(AbstractAuditQuery.java:109)
org.hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1032)
org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:374)
org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:165)
2024-09-15T12:35:38.808Z ERROR 1 --- [io-8080-exec-14] d.m.s.c.e.h.CommonGlobalExceptionHandler : PersistenceException с сообщением 'Преобразование `org.hibernate.exception.GenericJDBCException` в JPA `PersistenceException`: не удалось подготовить оператор' и стек-трейс: 
2024-09-15T12:35:38.807Z  INFO 1 --- [io-8080-exec-14] d.m.s.c.e.h.CommonGlobalExceptionHandler : Нет отдельного обработчика исключений для класса jakarta.persistence.PersistenceException, вернуть стандартный ответ
2024-09-15T12:35:38.805Z ERROR 1 --- [io-8080-exec-14] o.h.engine.jdbc.spi.SqlExceptionHelper   : Подключение закрыто
2024-09-15T12:35:38.805Z  WARN 1 --- [io-8080-exec-14] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL ошибка: 0, SQLState: null

Перезапуск pod в k8s помогает

код:

@Slf4j
@RestController
public class HistoryRestController {

    @Autowired
    private HistoryService historyService;

    @GetMapping("/api/bookinglegs/{uuid}/history")
    @PreAuthorize("hasAuthority('PRIV_CAN_READ_HISTORY')")
    public List<HistoryDto> getBookingLegHistory(@PathVariable UUID uuid) {
        return historyService.getHistoryForEntity(BookingLegEntity.class, uuid);
    }
}

@Slf4j
@Service
public class HistoryService {
    @Autowired
    private AuditReader auditReader;

    @Autowired
    private UserRepository userRepository;

    @SuppressWarnings("unchecked")
    @NotNull
    public <T> List<HistoryDto> getHistoryForEntity(Class<T> entityClass, UUID uuid) {
        List<Object[]> auditDataBookingLeg = auditReader.createQuery()
            .forRevisionsOfEntityWithChanges(entityClass, true)
            .add(AuditEntity.id().eq(uuid))
            .addOrder(AuditEntity.revisionProperty("timestamp").asc())
            .getResultList();
        // сопоставление результата с HistoryDto
        // ... 
}
}
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED, withModifiedFlag = true)
@Getter
@Entity
@NamedEntityGraphs({
    @NamedEntityGraph(name = "bookingLegEntityWithPassengers", attributeNodes = @NamedAttributeNode("passengerList"))
})
@Table(name = "BOOKING_LEGS",
    indexes = {
        @Index(name = "idx_distributor", columnList = "distributor")
    })
@SQLDelete(sql = "UPDATE BOOKING_LEGS SET deleted = true WHERE id=?")
@Where(clause = "deleted=false or deleted is null")
public class BookingLegEntity {

    @Id
    @Column(name = "ID", unique = true, updatable = false)
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;

    // важно не использовать CascadeType.Merge здесь: если мы сохраняем бронирования после планирования,
    // мы не хотим изменять что-либо, связанное с сутью бронирования (например, состояния оплаты, которые изменил
    // пользователь во время планирования)
    // важно не использовать CascadeType.All: удаление не должно быть каскадным
    @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @JoinColumn(name = "BELONGS_TO")
    private BookingEntity belongsTo;

    // хранит информацию о последовательности в бронировании с несколькими этапами
    @NotAudited
    @Column(name = "SORTING")
    private int sorting;

    @NotAudited
    @Type(JsonBinaryType.class)
    @Column(name = "FROM_LOCATION", columnDefinition = "JSONB")
    private Location from;

    @NotAudited
    @Type(JsonBinaryType.class)
    @Column(name = "TO_LOCATION", columnDefinition = "JSONB")
    private Location to;

    @NotAudited
    @Column(name = "ORIGINAL_START_TIMEWINDOW_START")
    private Instant originalStartTimewindowStart;

    @NotAudited
    @Column(name = "ORIGINAL_START_TIMEWINDOW_END")
    private Instant originalStartTimewindowEnd;

    @NotAudited
    @Column(name = "ORIGINAL_END_TIMEWINDOW_START")
    private Instant originalEndTimewindowStart;

    @NotAudited
    @Column(name = "ORIGINAL_END_TIMEWINDOW_END")
    private Instant originalEndTimewindowEnd;

    @NotAudited
    @Column(name = "START_TIMEWINDOW_START")
    @Convert(converter = InstantTimestampConverter.class)
    private Instant startTimewindowStart;

    @NotAudited
    @Column(name = "START_TIMEWINDOW_END")
    @Convert(converter = InstantTimestampConverter.class)
    private Instant startTimewindowEnd;

    @NotAudited
    @Column(name = "END_TIMEWINDOW_START")
    @Convert(converter = InstantTimestampConverter.class)
    private Instant endTimewindowStart;

    @NotAudited
    @Column(name = "END_TIMEWINDOW_END")
    @Convert(converter = InstantTimestampConverter.class)
    private Instant endTimewindowEnd;

    @NotAudited
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "DISTRIBUTOR")
    private DistributorEntity distributor;

    @NotAudited
    @Column(name = "TRANSPORT_TYPE")
    @Convert(converter = TripTypeEnumConverter.class)
    private TripTypeEnum transportType;

    @NotAudited
    @Column(name = "ROUTE_NAME")
    private String routeName; // "route" в файле gtfs

    @NotAudited
    @Column(name = "TRIP_NAME")
    private String tripName; // "shortTripName" в файле gtfs

    @Getter
    @NotAudited
    @Column(name = "AGENCY_NAME")
    private String agencyName; // "agencyName" в файле gtfs

    @Getter
    @ManyToOne(fetch = FetchType.LAZY)
    // не каскадировать здесь, мы сохраняем бронирования и запланированные этапы отдельно после планирования (легче обрабатывать, так как они связаны друг с другом)
    @JoinColumn(name = "GET_ON")
    private ScheduledLegEntity getOn;

    @Getter
    @NotAudited
    @ManyToOne(fetch = FetchType.LAZY)
    // не каскадировать здесь, мы сохраняем бронирования и запланированные этапы отдельно после планирования (легче обрабатывать, так как они связаны друг с другом)
    @JoinColumn(name = "GET_OFF")
    private ScheduledLegEntity getOff;

    /**
     * -- ГЕТТЕР --
     * Состояние планирования этого этапа бронирования.
     * <p>
     * <ul>
     *     <li><b>NEW:</b> этот этап бронирования еще не был запланирован</li>
     *     <li><b>SCHEDULED_AUTOMATICALLY:</b> этот этап бронирования был запланирован автоматически</li>
     *     <li><b>SCHEDULED_MANUALLY:</b> этот этап бронирования был запланирован/назначен вручную</li>
     *     <li><b>FAILED_TO_SCHEDULE:</b> последняя попытка запланировать этот этап бронирования не удалась.</li>
     * </ul>
     */
    @Column(name = "SCHEDULING_STATE")
    @Convert(converter = SchedulingStateEnumConverter.class)
    private SchedulingStateEnum schedulingState = SchedulingStateEnum.NEW;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CREATED_BY")
    private RecurringManualBookingLegEntity createdBy;

    @NotAudited
    @Column(name = "TITLE")
    @Convert(converter = TitleNullableConverter.class)
    private Title title;

    @Column(name = "DRIVER_NOTICE")
    @Convert(converter = DriverNoticeNullableConverter.class)
    private DriverNotice driverNotice;

    @Column(name = "BOOKING_NOTICE")
    @Convert(converter = BookingNoticeNullableConverter.class)
    private BookingNotice bookingNotice;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MANUALLY_ASSIGNED_TO")
    private AssignmentEntity manuallyAssignedTo;

    @Enumerated(EnumType.STRING)
    @Column(name = "CHECK_IN_STATE")
    private CheckInStateEnum checkInState = CheckInStateEnum.NEW;

    @NotAudited
    @Column(name = "SCHEDULER_KEY")
    @Convert(converter = ObjectConverter.class)
    private SchedulerKey schedulerKey;

    @Column(name = "PAYMENT_AMOUNT")
    private Integer paymentAmount;

    @Setter
    @Column(name = "CANCELLATION_FEE_AMOUNT")
    private Integer cancellationFeeAmount;

    @Setter
    @Column(name = "CANCELLATION_FEE_REASON")
    @Convert(converter = CancellationFeeReasonEnumConverter.class)
    private CancellationFeeReasonEnum cancellationFeeReason = CancellationFeeReasonEnum.NOT_CANCELLED;

    /**
     * Как только планирование решит время для поездки, мы проверяем, изменит ли это цену поездки.
     * Поездка может только стать дешевле. Если была выбрана более низкая цена, она сохраняется как измененная сумма платежа.
     */
    @Column(name = "OVERWRITTEN_PAYMENT_AMOUNT")
    private Integer overwrittenPaymentAmount;

    @Column(name = "PAYMENT_CURRENCY_CODE")
    @Convert(converter = CurrencyCodeConverter.class)
    private CurrencyCodeEnum currencyCode;

    @Column(name = "PAYMENT_FORM")
    @Convert(converter = PaymentFormConverter.class)
    private PaymentFormEnum paymentForm;

    @Enumerated(EnumType.STRING)
    @Column(name = "PAYMENT_STATE")
    private PaymentStateEnum paymentState = PaymentStateEnum.TO_PAY;

    @NotAudited
    @Column(name = "INVOICE_ID")
    @Convert(converter = StripeInvoiceIdConverter.class)
    private StripeInvoiceId invoiceId;

    @NotAudited
    @Column(name = "PAYMENT_ID")
    @Convert(converter = StripePaymentIdConverter.class)
    private StripePaymentId paymentId;

    @Type(JsonBinaryType.class)
    @Column(name = "START_TRIP_LOCATION", columnDefinition = "JSONB")
    private Location startTripLocation;

    @Type(JsonBinaryType.class)
    @Column(name = "END_TRIP_LOCATION", columnDefinition = "JSONB")
    private Location endTripLocation;

    // TODO: MC-5293, должно быть заменено как cancellationLocation
    @Deprecated(forRemoval = true)
    @Type(JsonBinaryType.class)
    @Column(name = "NO_SHOW_LOCATION", columnDefinition = "JSONB")
    private Location noShowLocation;

    @Type(JsonBinaryType.class)
    @Column(name = "CANCELLATION_LOCATION", columnDefinition = "JSONB")
    private Location cancellationLocation;

    @CreationTimestamp
    @Column(name = "CREATED_TIME")
    @Audited(withModifiedFlag = true, modifiedColumnName = "created_time_mod")
    private Timestamp creationTimestamp;

    @UpdateTimestamp
    @NotAudited
    @Column(name = "UPDATED_TIME")
    private Timestamp updateTimestamp;

    @NotAudited
    @Column(name = "DELETED")
    private boolean deleted;

    @NotAudited
    @Type(JsonBinaryType.class)
    @Column(name = "METADATA", columnDefinition = "JSONB DEFAULT '{}'", nullable = false)
    private Map<String, Object> metadata = new HashMap<>();

    @NotAudited
    @OneToOne
    @NotFound(action = NotFoundAction.IGNORE)
    @JoinColumn(name = "RESERVATION_ID")
    private PrivateTripReservationEntity reservation;

    /**
     * Зона, в которой изначально была создана эта бронь
     * Предпочитайте использовать эту зону для расчета цен, если доступно несколько зон
     */
    @NotAudited
    @ManyToOne
    @JoinColumn(name = "PREFERRED_ZONE_ID")
    private ZoneEntity preferredZone;

    @OneToMany(mappedBy = "bookingLeg", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<PassengerEntity> passengerList = new HashSet<>();

    @NotAudited
    @Column(name = "SPECIAL_NEEDS")
    @Convert(converter = SpecialNeedsConverter.class)
    private SpecialNeedsEnum specialNeeds;

исключение происходит в

    .getResultList();

Ответ или решение

Почему Hibernate Envers выбрасывает PersistenceException

PersistenceException в Hibernate Envers может быть вызвана рядом факторов, каждый из которых может привести к сбоям при выполнении запросов к базе данных. В вашем случае, когда ошибка возникает нечасто и решение проблемы требует перезапуска пода в Kubernetes, это может указывать на проблемы с управлением сессиями или состоянием подключения к базе данных. Давайте рассмотрим возможные причины более подробно.

1. Закрытое соединение с базой данных

Из приведенных вами логов видно, что ошибка SQL указывает на то, что "Соединение закрыто". Это может произойти, если соединение, ранее установленное с базой данных, не используется в течение продолжительного времени и разрывается. В результате попытка выполнить запрос через AuditReader приводит к PersistenceException.

Решение данной проблемы может заключаться в настройке пула соединений, чтобы избегать разрывов соединений. Некоторые базы данных могут иметь предустановленные таймауты на бездействие; вы можете настроить ваш пул (например, HikariCP) с параметрами connectionTimeout, idleTimeout или maxLifetime, чтобы компенсировать это.

2. Проблемы с транзакциями

Ошибки в управлении транзакциями могут привести к выходу из состояния гонки или блокировке. Убедитесь, что транзакции обрабатываются правильно, то есть правильно начаты и завершаются. Например, если транзакция была неправильно зафиксирована или отменена, это может вызвать проблемы с последующими запросами.

Если ваш код позволяет возможность конкурирующих операций, подумайте о реализации механизма управления конкурентным доступом.

3. Неправильные конфигурации Hibernate

Если Hibernate не может правильно сгенерировать SQL-запрос для вашей модели, это также может привести к PersistenceException. Это может произойти, например, если есть проблемы с аннотациями в классе BookingLegEntity или с настройками Envers. Оцените ваши аннотации, такие как @Audited, @SQLDelete и другие, чтобы убедиться, что они соответствуют вашим ожиданиям. Также стоит проверить целостность модели и связи между сущностями.

4. Проблемы с жизненным циклом сущностей

Если вы используете различные контексты (например, в Spring), убедитесь, что сущности корректно управляются их жизненным циклом. Неправильная работа с сущностями, которые были загружены в одном контексте и попытаются быть использованы в другом, также может быть источником ошибок.

5. Обработка исключений

Убедитесь, что у вас правильно настроена обработка исключений. В вашем коде Exception Handler не предоставляет отдельной обработки для PersistenceException. Это может затруднить понимание источника ошибки. Рекомендуется улучшить обработку, чтобы вы могли получать больше информации о возникшей проблеме (например, записывать детализированную информацию в логи).

Заключение

Проблемы с PersistenceException в Hibernate Envers могут быть связаны с несколькими факторами, включая неправильное управление соединениями, проблемы с транзакциями, ошибки конфигурации и жизненного цикла сущностей. Рекомендуется провести тщательный анализ и возможно обновить конфигурацию вашего приложения и базы данных. Кроме того, оптимизация работы с пулом соединений и улучшение обработки исключений могут помочь справиться с проблемами, вызывающими сбои.

Оцените материал
Добавить комментарий

Капча загружается...