首页 > 解决方案 > 为什么之前的sql语句又被执行了?

问题描述

我对 Spring、Hibernate、JPA 和这部分发展中国家的许多其他相关方面相当陌生。我希望这是一个简单回答的愚蠢问题,因为我已经浪费了几个小时来尝试调试我的问题。

简而言之,问题在于 Hibernate(JPA?Spring?)似乎想要执行它之前已经执行过的 sql 语句,尽管我调用了 saveAndFlush。对于插入,这会导致唯一索引/主键违规。

在控制器中,我有这个功能,它需要添加或删除用户和 ProcessDefinition 之间的关系:

    @Autowired
    ProcessDefinitionRepository processDefinitionRepository;

    @Autowired
    UserRepository userRepository;

    @PostMapping({"/toggleFavorite"})
    @ResponseBody
    public int toggleFavorite(@RequestBody ObjectNode jsonData){
        // Get the data
        int processID = jsonData.findValue("processID").asInt();

        // Find the process definition
        ProcessDefinition toggledProcessDefinition = processDefinitionRepository
                .findById(processID)
                .get();

        // Get the current user
        User currentUser = CurrentUser.get();

        // Either remove or...
        int result;
        if(toggledProcessDefinition.isFavoriteOfCurrentUser()){
            currentUser.removeFavoriteProcessDefinition(toggledProcessDefinition);
            result= 0;
        }
        // ...add to favorites
        else{
            currentUser.addFavoriteProcessDefinition(toggledProcessDefinition);
            result= 1;
        }

        // Send to database
        userRepository.saveAndFlush(currentUser);

        return result;
    }

用户通过以下方式引用 ProcessDefinition:

    @Id
    private String userId="";

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
        name = "user_process_definition",
            joinColumns= @JoinColumn(name="user_id"),
            inverseJoinColumns = @JoinColumn(name="process_definition_id")
    )
    Set<ProcessDefinition> favoriteProcessDefinitions = new HashSet<>();
    public void removeFavoriteProcessDefinition(ProcessDefinition processDefinition){
        favoriteProcessDefinitions.remove(processDefinition);
    }

    public void addFavoriteProcessDefinition(ProcessDefinition processDefinition){
        favoriteProcessDefinitions.add(processDefinition);
    }

ProcessDefinition 指的是用户:

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private int id;

    @ManyToMany(mappedBy = "favoriteProcessDefinitions")
    private Set<User> favoritingUsers = new HashSet<>();
    public boolean isFavoriteOfCurrentUser(){
        return CurrentUser.get().getFavoriteProcessDefinitions().contains(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ProcessDefinition that = (ProcessDefinition) o;

        return id == that.id;
    }

    @Override
    public int hashCode() {
        return id;
    }

现在,当我导航到相关页面并单击按钮删除收藏夹时,代码按预期执行(使用调试器验证)并且 Hibernate 启动删除查询:

Hibernate: 
    delete 
    from
        user_process_definition 
    where
        user_id=? 
        and process_definition_id=?

返回一个结果,Ajax 调用完成。我单击另一个按钮以删除不同的 ProcessDefinition。Hibernate 再次执行上述删除查询。然后我单击按钮添加收藏夹(又一个 ProcessDefinition,它的 ID 为 3),我看到发生了这种情况:

Hibernate: 
    delete 
    from
        user_process_definition 
    where
        user_id=? 
        and process_definition_id=?
Hibernate: 
    delete 
    from
        user_process_definition 
    where
        user_id=? 
        and process_definition_id=?
Hibernate: 
    insert 
    into
        user_process_definition
        (user_id, process_definition_id) 
    values
        (?, ?)

为什么又要删除了?

似乎有什么东西在保留前面的语句并再次执行它们。当我尝试添加另一个收藏夹 (ID=2) 时发生的事情进一步支持了这一点。因为现在发生的...

Hibernate: 
    delete 
    from
        user_process_definition 
    where
        user_id=? 
        and process_definition_id=?
Hibernate: 
    insert 
    into
        user_process_definition
        (user_id, process_definition_id) 
    values
        (?, ?)
2019-08-21 16:29:08.254  WARN 8528 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505
2019-08-21 16:29:08.254 ERROR 8528 --- [nio-8080-exec-7] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_6 ON PUBLIC.USER_PROCESS_DEFINITION(USER_ID, PROCESS_DEFINITION_ID) VALUES 3"; SQL statement:
insert into user_process_definition (user_id, process_definition_id) values (?, ?) [23505-199]
2019-08-21 16:29:08.257  INFO 8528 --- [nio-8080-exec-7] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2019-08-21 16:29:08.278 ERROR 8528 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PUBLIC.PRIMARY_KEY_6 ON PUBLIC.USER_PROCESS_DEFINITION(USER_ID, PROCESS_DEFINITION_ID) VALUES 3"; SQL statement:
insert into user_process_definition (user_id, process_definition_id) values (?, ?) [23505-199]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_6 ON PUBLIC.USER_PROCESS_DEFINITION(USER_ID, PROCESS_DEFINITION_ID) VALUES 3"; SQL statement:
insert into user_process_definition (user_id, process_definition_id) values (?, ?) [23505-199]

再次删除,但更糟糕的是,之前的插入(ID=3)再次执行!当然,这会遇到唯一索引违规。但是为什么它要再次运行前面的语句呢?

请注意,当我执行删除后(在不同的 Ajax 调用中!)重新添加收藏夹时,甚至不会将插入发送到数据库。

请注意,当我进行添加时,(在不同的 Ajax 调用中)后跟不同的添加,我总是得到一个“唯一索引或主键冲突”,其值是第一个 ProcessDefinition 的 ID。

我应该关闭还是提交某些东西,saveAndFlush 还不够吗?发生了什么事,我该如何解决?

标签: javahibernatespring-data-jpa

解决方案


将以下内容添加到 application.properties:

spring.jpa.open-in-view = false

并使用save而不是saveAndFlush

我不确定它是否会立即解决您的问题,但我相信您肯定会更接近解决方案。

另一个想法:

if(toggledProcessDefinition.getFavoriteProcessDefinitions().contains(currentUser){
    toggledProcessDefinition.getFavoriteProcessDefinitions().remove(currentUser);
    result= 0;
}

// ...add to favorites
else{
    toggledProcessDefinition.getFavoriteProcessDefinitions().add(currentUser);
    result= 1;
}
processDefinitionRepository.save(toggledProcessDefinition);

这样您就不会从两种方式加载引用。

@Transactional如果您关闭 open-in-view,您还需要添加到您的控制器方法。


推荐阅读