Spring Data删除功能不删除记录

时间:2018-01-23 09:55:38

标签: java spring spring-boot spring-data-jpa querydsl

我有以下简单的应用程序

Users实体

@Entity
public class Users implements Serializable {

   @Id
   @GeneratedValue
   private long id;

   private String name;

   @OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
   private Set<UserRoleUser> userRoleUser;

   // GETTERS AND SETTERS
}

UserRole实体

@Entity
public class UserRole implements Serializable {

   @Id
   @GeneratedValue
   private long id;

   private String roleName;

   @OneToMany(mappedBy = "userrole", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
   private Set<UserRoleUser> userRoleUser;

   // GETTERS AND SETTERS
}

UserRoleUser多对应解析器

@Entity
public class UserRoleUser implements Serializable {

   @Id
   @GeneratedValue
   private long id;

   @ManyToOne
   @JoinColumn(name = "fk_userId")
   private Users user;

   @ManyToOne
   @JoinColumn(name = "fk_userroleId")
   private UserRole userrole;

   // GETTERS AND SETTERS
}

UserRoleUserRepository

@Repository
@Transactional
public interface UserRoleUserRepository extends JpaRepository<UserRoleUser, Long>, QueryDslPredicateExecutor<UserRoleUser>{

}

Application

@SpringBootApplication
@Configuration
public class Application {

   public static void main(String[] args) {
       ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);

       UserRoleUserRepository userRoleUserRepository = context.getBean(UserRoleUserRepository.class);

       Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));

       for (UserRoleUser userRoleUser : findAll) {
           userRoleUserRepository.delete(userRoleUser);
       }

   }

}

在运行main应用程序时,UserRoleUser表中的数据库记录不会被删除。可能是什么问题?我使用的是Spring DataQueryDsl

我还尝试将删除功能放在Controller上,但仍然无效。

@RestController
@RequestMapping("/api")
public class DeleteController {

    @Autowired
    UserRoleUserRepository userRoleUserRepository;

    @GetMapping("/delete")
    public String delete() {
        Iterable<UserRoleUser> findAll = userRoleUserRepository.findAll(QUserRoleUser.userRoleUser.id.gt(0));

        for (UserRoleUser userRoleUser : findAll) {
            userRoleUserRepository.delete(userRoleUser);
        }

        return new Date().toString();
    }
}

3 个答案:

答案 0 :(得分:2)

问题是实体仍然附着,在它们分离之前不会被删除。如果您按其ID而不是实体本身删除,则会删除它们。

我注意到的一件事是您一次删除一个用户,这可能会导致数据库性能下降,因为每次都会重新创建查询。最简单的方法是将所有id添加到集合中,然后删除一组id。像这样:

Set<Integer> idList = new HashSet<>();
for (UserRoleUser userRoleUser : findAll) {
  idList.add(userRoleUser.getId());
}

if (!idList.isEmpty()) {
  userRoleUserRepository.delete(idList);
}

然后在您的存储库中添加删除方法

@Modifying
@Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
@Transactional
void delete(Set<Integer> id);

答案 1 :(得分:2)

如果您需要使用CrudRepository提供的给定方法,请使用JpaRepository.deleteInBatch()。这解决了这个问题。

答案 2 :(得分:0)

在进行UserRoleUser调用时未删除子对象(userRoleUserRepository.delete(userRoleUser))的原因是,每个UserRoleUser指向一个Users,而该Set<UserRoleUser> userRoleUser则持有一个@OneToMany参考Set<UserRoleUser>。 如this StackOverflow answer中所述,您的JPA实现(例如Hibernate)有效执行的操作是:

  1. 缓存会记录请求的子排除项
  2. 但是,缓存不会验证Set<UserRoleUser>中的任何更改
  3. 由于父@OneToMany字段尚未更新,因此未进行任何更改

解决方案将首先从userRoleUserRepository.delete(userRoleUser)中删除子元素,然后继续进行userRepository.save(user)userRoleUserRepository.deleteById(userRoleUser.getId())

为了避免这种复杂性,提供了两个答案:

  1. 通过调用userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList()))来按ID删除元素:在这种情况下,在删除之前不检查实体结构(并因此检查父对象)。在delete的模拟情况下,必须使用所有更复杂的东西,例如deleteInBatch(userRoleUserList)
  2. 将您的CrudRepository转换为JpaRepository并使用其repo.deleteAll()方法。如this articlethis StackOverflow answer中所述,deleteInBatch方法尝试一次删除所有记录,在记录数量太大的情况下可能会产生StackOverflow错误。由于deleteInBatch每次都会删除一条记录,因此将这种风险降到最低(除非调用本身在@Transactional方法内)

根据this StackOverflow answer,在重复使用userRoleUserRepository.delete(userRoleUser)时应格外小心:

  1. 不会级联到其他实体
  2. 不更新持久性上下文,要求将其清除(该方法绕过缓存)

最后,据我所知,仅通过调用Route::delete('/whatever/{child}', [ChildController::class, 'deleteChild']) ->name('preliminary-children.deleteChild'); 而不先更新父对象是不可能做到的。关于此问题的任何更新(无论是通过批注,配置还是通过任何其他方式允许这种行为)都将是对答案的欢迎补充。