Ошибка SQL: 23503, SQLState: 23503 в приложении SpringBoot 2.1.4.RELEASE - PullRequest
3 голосов
/ 16 апреля 2019

У меня есть приложение SpringBoot 2.1.4.RELEASE RESTful Web Service, использующее Spring Initializer, встроенный 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]

Ответы [ 4 ]

3 голосов
/ 22 апреля 2019

Идентификатор не генерируется, когда вы делаете MenuAlertNotification menuAlertNotification = new MenuAlertNotification (menuAlert);, а также Long menuAlertNotificationId = menuAlertNotification.getId();, это всегда будет возвращать null.

Вам следует изменить вторую строку теста junit на

menuAlertNotification = menuAlertNotificationService.save(menuAlertNotification);

Я предполагаю, что ваш сервис menuAlertNotificationService.save(menuAlertNotification); возвращает что-то вроде return notificationRepo.save(entity).

Таким образом, новый объект заполняется идентификатором после вставки строки и, следовательно, не получит указанное исключение.

1 голос
/ 26 апреля 2019

Проблема в том, что вы используете базу данных в памяти, и когда метод Test завершен, Junit удаляет все таблицы, но не в правильном порядке.

Код уровня сервиса должен зависеть от репозитория. Однако для тестирования уровня службы вам не нужно знать или заботиться о том, как реализован уровень постоянства.

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

Для этого вы можете использовать поддержку насмешки, предоставляемую Spring Boot Test.

0 голосов
/ 23 апреля 2019

Как сказал Цзянь, это не вызов changeMenuNotificationReadStatus, который вызывает ошибку, а, вероятно, предыдущий оператор, который не был сброшен.

Что-то генерирует этот оператор удаления, который отвечает за нарушение ограничения FK:

delete from t_menu_alert where id=?

В ваших тестах вы могли бы регулярно использовать saveAndFlush вместо save и flush методы для выдачи запросов на изменение SQL и посмотреть, в чем заключается проблема.

Здесь,JPA пытается удалить MenuAlert перед удалением ссылки MenuAlertNotification, поэтому ограничение FK блокируется, как вы, вероятно, знаете.

Поскольку уведомление для удаленного предупреждения не имеет смысла, вы также можете каскадировать удаление:

@ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)

или измените действие ограничения FK ON DELETE на уровне базы данных: CASCADE удалит ссылки MenuAlertNotification s, а SET NULL очистит связь, установив значение null при обращении к MenuAlertNotification.menuAlert.

Вы также можете написать Java-код для удаления MenuAlertNotification с до MenuAlert, в зависимости от условий.

Я будуЯ хочу прочитать весь ваш тестовый код.

Адил Халил, вы правы, что если метод Spring Data JPA Repository.save используется напрямую, нам нужно получить возвращаемое значение, чтобы иметь обновленный идентификатор.Но здесь, я думаю, он вызывает сервисный уровень, который должен вернуть обновленное значение:

@Service
public class MenuAlertNotificationService {
    private MenuAlertNotificationRepository repository;

    public MenuAlertNotification save(MenuAlertNotification entity) {
        return repository.save(entity);
    } 
}
0 голосов
/ 19 апреля 2019

Ошибка вызвана тем, что база данных собирается удалить запись, на которую ссылаются другие записи с внешним ключом (ограничение внешнего ключа)

Я полагаю, в вашем тестовом примере до фрагмента кода, который вы проанализировали здесь, есть код операции удаления, что-то вроде

MenuAlertRepository.deleteAll()

Spring JPA не выполнит этот оператор сразу, когда будет транзакция.После этого Spring JPA встретил запрос JPQL (changeMenuNotificationReadStatus), а затем сбросил все кэшированные операторы в базу данных.В это время оператор delete выполнялся точно.Затем было исключено ограничение внешнего ключа.

...