在SpringBoot 2.1.4.RELEASE应用

时间:2019-04-16 09:36:18

标签: spring jpa spring-data-jpa spring-data

我有一个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 个答案:

答案 0 :(得分:3)

执行MenuAlertNotification menuAlertNotification = new MenuAlertNotification (menuAlert);以及Long menuAlertNotificationId = menuAlertNotification.getId();时都不会生成ID,这将始终返回null

您应该将junit测试的第二行更改为

menuAlertNotification = menuAlertNotificationService.save(menuAlertNotification);

我假设您的服务menuAlertNotificationService.save(menuAlertNotification);返回的内容类似于return notificationRepo.save(entity)

这样,在插入行之后,您将用ID填充新对象,因此不会得到所述异常。

答案 1 :(得分:1)

问题是因为您使用的是内存数据库,并且Test方法完成后,Junit会删除所有表,但顺序不正确。

服务层代码应取决于存储库。但是,要测试服务层,您无需了解或关心持久层的实现方式。

理想情况下,您应该能够编写和测试我们的服务层代码,而无需在我们的整个持久层中进行布线。

要实现此目的,可以使用Spring Boot Test提供的模拟支持。

答案 2 :(得分:0)

该错误是由于数据库打算删除一条记录而被具有外键的其他记录引用。(外键约束)

我猜在您的测试用例中,您在此处解析的代码段之前,有一个删除操作代码,类似

MenuAlertRepository.deleteAll()

发生事务时,Spring JPA不会立即执行此语句。 Spring JPA满足JPQL查询(changeMenuNotificationReadStatus)后,便将所有缓存的语句刷新到db中。此时,delete语句正在正确执行。然后抛出了外键约束异常。

答案 3 :(得分:0)

如剑所言,导致错误的不是对changeMenuNotificationReadStatus的调用,而是先前未刷新的语句。

某事正在生成此delete语句,该语句负责FK约束违反:

delete from t_menu_alert where id=?

在测试中,您可以定期使用saveAndFlush而不是saveflush方法来发出SQL修改请求并查看问题出在哪里。

在这里,JPA尝试在删除引用MenuAlert之前删除MenuAlertNotification,因此FK约束可能会阻塞。

由于通知对于删除的警报没有意义,因此您还可以级联删除操作:

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

或在数据库级别更改FK约束ON DELETE操作:CASCADE将删除引用MenuAlertNotification,SET NULL将通过在引用MenuAlertNotification.menuAlert上设置null来清除关联。

您还可以根据条件编写Java代码以删除MenuAlertNotification之前的MenuAlert

我很想阅读您的整个测试代码。

Adil Khalil,如果您直接使用Spring Data JPA Repository.save方法是正确的,那么我们需要获取返回值以获取更新的ID。但是在这里,我认为他调用了一个服务层,该服务层将返回更新后的值:

@Service
public class MenuAlertNotificationService {
    private MenuAlertNotificationRepository repository;

    public MenuAlertNotification save(MenuAlertNotification entity) {
        return repository.save(entity);
    } 
}