5

I have the following simple application

Users Entity

@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

@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 Many to many resolver class

@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>{

}

Main Application class

@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);
       }

   }

}

On running the main application, the database records in the UserRoleUser table are not being deleted. What could be the issue? I am using Spring Data and QueryDsl.

I have also tried putting the delete functionality on a Controller but still doesn't work.

@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();
    }
}
Kihats
  • 3,326
  • 5
  • 31
  • 46
  • If your code runs normally til the end of main method, then maybe it's because you put `@Transactional` on `UserRoleUserRepository` interface. See this post: https://stackoverflow.com/questions/5551541/where-to-put-transactional-in-interface-specification-or-implementation answer of `El Guapo`. – STaefi Jan 23 '18 at 10:36
  • Actually, I was just trying out when I put the `@Transactional` annotation because the records are not being deleted with or without it. – Kihats Jan 23 '18 at 10:56
  • could it be because in order to effectively delete you should first remove the undesired elements from Set userRoleUser in the parent class and then proceed with the deletion? – epol Nov 16 '20 at 18:35

3 Answers3

12

If you need to use the given methods provided by CrudRepository, use the JpaRepository.deleteInBatch(). This solves the problem.

Kihats
  • 3,326
  • 5
  • 31
  • 46
7

The problem is the entities are still attached and will not be deleted until they become detached. If you delete by their id instead of the entity itself, it will delete them.

One thing I noticed is you are deleting the users one at a time which could lead to a database performance hit as the query will be recreated each time. The easiest thing to do is to add all the ids to a set then delete the set of ids. Something like this:

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

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

then in your repository add the delete method

@Modifying
@Query("DELETE FROM UserRoleUser uru WHERE uru.id in ?1")
@Transactional
void delete(Set<Integer> id);
locus2k
  • 2,802
  • 1
  • 14
  • 21
  • What I needed is to use the given methods provided by `CrudRepository`. But thanks for your answer it has triggered something else. Posting the answer below. – Kihats Jan 23 '18 at 18:47
  • This answer answer is the correct answer not the accepted one. So downvoted the accepted one and upvoted this – Morteza Oct 08 '20 at 11:44
  • Deleting by ID also resolved my issue. Thank you! – vbknez Jan 30 '21 at 14:52
1

The reason why the child objects (UserRoleUser) are not being deleted upon userRoleUserRepository.delete(userRoleUser) call is that each UserRoleUser points to a Users which in turn holds a @OneToMany reference Set<UserRoleUser> userRoleUser. As described in this StackOverflow answer, what your JPA implementation (e.g. Hibernate) effectively does is:

  1. The cache takes note of the requested child exclusion
  2. The cache however does not verify any changes in Set<UserRoleUser>
  3. As the parent @OneToMany field has not been updated, no changes are made

A solution would go through first removing the child element from Set<UserRoleUser> and then proceed to userRoleUserRepository.delete(userRoleUser) or userRepository.save(user)

In order to avoid this complication two answers have been provided:

  1. Remove element by Id, by calling userRoleUserRepository.deleteById(userRoleUser.getId()) : in this case the entity structure (and therefore the parent) is not checked before deletion. In the analog case of deleteAll something more convoluted such as userRoleUserRepository.deleteByIdIn(userRoleUserList.stream().map(UserRoleUser::getId).collect(Collectors.toList())) would have to be employed
  2. Convert your CrudRepository to a JpaRepository and use its deleteInBatch(userRoleUserList) method. As explained in this article and this StackOverflow answer the deleteInBatch method tries to delete all records at once, possibly generating a StackOverflow error in the case the number of records is too large. As repo.deleteAll() removes one record at a time this error it minimizes this risk (unless the call is itself inside a @Transactional method)

According to this StackOverflow answer, extra care should be used when recurring to deleteInBatch as it:

  1. Does not cascade to other entities
  2. Does not update the persistence context, requiring it to be cleared (the method bypasses the cache)

Finally , as far as I know , there is no way this could be done by simply calling userRoleUserRepository.delete(userRoleUser) without first updating the parent object. Any updates on this (whether by allowing such behaviour through annotations, configuration or any other means) would be a welcome addition to the answer.

epol
  • 1,022
  • 8
  • 18
  • deleteById didn't work for me in the case of JpaRepository. Setting Cascade.All in the detail entry removes the master along with all detail entries. – Chris Aug 21 '21 at 11:51
  • Had to pick master, update master's list with once removed the detail entry from the list, save master, delete detail object – Chris Aug 21 '21 at 12:40