#spring #jpa #spring-data-jpa #spring-data
#весна #jpa #spring-data-jpa #весна-данные
Вопрос:
У меня есть приложение веб-службы SpringBoot 2.1.4.RELEASE RESTful., использующее инициализатор Spring, встроенный Tomcat, механизм шаблонов Thymeleaf и пакет в виде исполняемого файла JAR.
У меня есть этот класс:
@Entity
@Table(name="t_menu_alert_notification")
public class MenuAlertNotification implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
public MenuAlertNotification() {
super();
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonProperty("id")
private Long id;
@JsonProperty("subject")
protected String subject;
@JsonIgnore
protected Integer trend;
@JsonIgnore
protected String message;
@JsonProperty("notified")
private Boolean notified;
@Column(name = "is_read")
protected Boolean read;
@JsonProperty("creationDate")
@Convert(converter = LocalDateTimeAttributeConverter.class)
protected LocalDateTime creationDate;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "menu_alert_id")
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="name")
@JsonIdentityReference(alwaysAsId=true)
protected MenuAlert menuAlert;
..
}
и этот метод в репозитории:
@Transactional(propagation =Propagation.REQUIRED,
isolation=Isolation.SERIALIZABLE,
readOnly=false,
transactionManager="transactionManager")
@Modifying
@Query("update MenuAlertNotification n set n.read = :status where n.id in :notificationIdList and n.menuAlert.menu.user.id = :userId")
void changemenuNotificationListReadStatus( @Param("notificationIdList") List<Long> notificationIdList,
@Param("userId") long userId,
@Param("status") boolean status);
Я создал тест Junit :
MenuAlertNotification menuAlertNotification = new MenuAlertNotification (menuAlert);
menuAlertNotificationService.save(menuAlertNotification);
assertFalse (menuAlertNotification.getRead());
Long menuAlertNotificationId = menuAlertNotification.getId();
List<Long> notificationIdList = new ArrayList <Long>();
notificationIdList.add (menuAlertNotificationId);
menuAlertNotificationService
.changeMenuNotificationReadStatus (notificationIdList, user.getId(), Boolean.TRUE);
когда я сохраняю объект, все в порядке, но когда я вызываю метод changeMenuNotificationReadStatus
, я получаю эту ошибку:
019-04-16 11:21 [main] WARN o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions(137) - SQL Error: 23503, SQLState: 23503
2019-04-16 11:21 [main] ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions(142) - Referential integrity constraint violation: "FK56KKTN0YV9SJJIOJJVJBPAGNW: PUBLIC.T_MENU_ALERT_NOTIFICATION FOREIGN KEY(MENU_ALERT_ID) REFERENCES PUBLIC.T_MENU_ALERT(ID) (1)"; SQL statement:
delete from t_menu_alert where id=? [23503-197]
Комментарии:
1. я думаю, проблема заключается в «n.menuAlert.menu.user.id = :Идентификатор пользователя», потому что вы являетесь собственностью access дочернего элемента. и сравнивая его с id, вот почему возникает эта проблема. Поскольку MenuAlert стремится, поэтому он извлекается, но его дочерний элемент не извлекается, как дочерний пользователь menu и menu, их тип выборки будет ленивым. Поскольку они не извлекаются во время выполнения, эта проблема может возникнуть.
2. Я предполагаю, что вы создаете новый menuAlert в своем модульном тестировании, и платформа тестирования пытается удалить его в конце вашего теста. Можете ли вы включить весь модульный тест с аннотациями к тесту.
3. Да, пожалуйста, добавьте весь тест, если наши ответы не решили проблему.
4. Пожалуйста, посмотрите на мой ответ и отметьте его соответствующим образом, если это полезно
Ответ №1:
Идентификатор не генерируется MenuAlertNotification menuAlertNotification = new MenuAlertNotification (menuAlert);
, когда вы Long menuAlertNotificationId = menuAlertNotification.getId();
это делаете, и он всегда будет возвращаться null
.
Вы должны изменить вторую строку вашего теста junit на
menuAlertNotification = menuAlertNotificationService.save(menuAlertNotification);
Я предполагаю, что ваша служба menuAlertNotificationService.save(menuAlertNotification);
возвращает что-то вроде return notificationRepo.save(entity)
.
Таким образом, у вас есть новый объект, заполненный идентификатором после вставки строки, и, следовательно, он не получит указанное исключение.
Ответ №2:
Проблема заключается в том, что вы используете базу данных в памяти, и когда метод тестирования завершен, Junit удаляет все таблицы, но не в правильном порядке.
Код уровня сервиса должен зависеть от репозитория. Однако для тестирования уровня обслуживания вам не нужно знать или заботиться о том, как реализован уровень сохраняемости.
В идеале вы должны иметь возможность писать и тестировать наш код уровня обслуживания без подключения к нашему полному уровню сохраняемости.
Для достижения этой цели вы можете использовать поддержку mocking, предоставляемую Spring Boot Test.
Ответ №3:
Ошибка вызвана тем, что база данных собирается удалить запись, на которую ссылаются другие записи с внешним ключом.(Ограничение внешнего ключа)
Я предполагаю, что в тестовом примере перед фрагментом кода, который вы проанализировали здесь, есть код операции удаления, что-то вроде
MenuAlertRepository.deleteAll()
Spring JPA не будет выполнять эту инструкцию немедленно при наличии транзакции. После этого Spring JPA выполнил запрос JPQL (changeMenuNotificationReadStatus), затем он сбросил все кэшированные операторы в БД. В это время оператор delete выполнялся точно. Затем было выброшено исключение ограничения внешнего ключа.
Ответ №4:
Как сказал Цзянь, ошибка возникает не из-за вызова changeMenuNotificationReadStatus, а, вероятно, из-за предыдущего оператора, который не был сброшен.
Что-то генерирует этот оператор удаления, который отвечает за нарушение ограничения FK:
delete from t_menu_alert where id=?
В ваших тестах вы могли бы использовать saveAndFlush
методы вместо save
, и flush
регулярно отправлять запросы на изменение SQL и видеть, в чем проблема.
Здесь JPA пытается удалить MenuAlert
перед удалением ссылки MenuAlertNotification
, поэтому ограничение FK блокируется, как вы, вероятно, знаете.
Поскольку уведомление не имеет смысла для удаленного предупреждения, вы также можете каскадировать удаление:
@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
или измените ON DELETE
действие ограничения FK на уровне базы данных: КАСКАД удалит ссылки, MenuAlertNotification
а SET NULL очистит ассоциацию, установив значение null для ссылки MenuAlertNotification.menuAlert
.
Вы также можете написать Java-код для удаления MenuAlertNotification
s перед MenuAlert
, на основе условий.
Мне было бы любопытно прочитать весь ваш тестовый код.
Адиль Халил, ты прав, что если метод Spring Data JPA Repository.save используется напрямую, нам нужно получить возвращаемое значение с обновленным идентификатором. Но здесь, я думаю, он вызывает сервисный уровень, который должен возвращать обновленное значение:
@Service
public class MenuAlertNotificationService {
private MenuAlertNotificationRepository repository;
public MenuAlertNotification save(MenuAlertNotification entity) {
return repository.save(entity);
}
}