Вопрос или проблема
Я использую 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 могут быть связаны с несколькими факторами, включая неправильное управление соединениями, проблемы с транзакциями, ошибки конфигурации и жизненного цикла сущностей. Рекомендуется провести тщательный анализ и возможно обновить конфигурацию вашего приложения и базы данных. Кроме того, оптимизация работы с пулом соединений и улучшение обработки исключений могут помочь справиться с проблемами, вызывающими сбои.