I'm trying to understand how to properly delete a many to one relationship.
Let's suppose I have the following entities:
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String lastname;
}
@Entity
@Table(name = "badge")
public class Badge {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String code;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "badge_id")
private User user;
}
Now these two entites have different controllers and services. I can delete a badge in the BadgeService and delete a user in its service.
@Service
public class BadgeService {
@Autowired
BadgeRepository badgeRepository;
public void delete(int id) {
badgeRepository.deleteById(id);
}
}
@Service
public class UserService {
@Autowired
UserRepository userRepository;
public void delete(int id) {
userRepository.deleteById(id);
}
}
The problem is that if I delete a badge everthing works but If I delete a User a got an error due to the FK.
To solve the problem I came up with 2 ways but I was wondering if there is a better way to handle this kind of problem:
First Way
I simply create a method in the badge repository to delete all badges related to the specific user.
public interface BadgeRepository extends CrudRepository<Badge, Integer> {
@Modifying
@Query(value = "DELETE Badge b WHERE b.user.id = :userId")
public void deleteByUserId(@Param("userId") int userId);
}
Then I create a method in the badge service.
@Service
public class BadgeService {
@Autowired
BadgeRepository badgeRepository;
public void delete(int id) {
badgeRepository.deleteById(id);
}
@Transactional
public void deleteByUserId(int userId) {
badgeRepository.deleteByUserId(userId);
}
}
And last I simply autowire badge service in user service and call the method in the user delete.
@Service
public class UserService {
@Autowired
UserRepository userRepository;
@Autowired
BadgeService badgeService;
@Transactional
public void delete(int id) {
badgeService.deleteByUserId(id);
userRepository.deleteById(id);
}
}
Cons:
If I have multiple relationships with the User entity, I will end up autowiring a lot of services in the user service and that is bad.
Second Way
Instead of having an unidirectional relationship I create a bidirectional relationship between User and Badge.
@Entity
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String lastname;
@OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<Badge> badges = new ArrayList<Badge>();
}
And when I delete a user, the cascade or simplying removing the badge from the colletion will delete all the related badges.
Cons:
Extra Query
If the collection is too big the app performances will decrease
That being said, what would you suggest? first or second approach? Maybe there is a better approach to handle this problem?
Thank you all.
CodePudding user response:
By handling the badge deletion in user service, you make it clear what should happen at user deletion. Now anyone who deletes an user will delete also its relations (you can think about that as a side effect). So the first way is the best that you can do.
Just make sure to not try to delete users from badge service. That would mean circular dependency and would create confusion about who is the owner of the relation.
CodePudding user response:
First approach looks good in this scenario. To keep UserService clean you can create a PreRemove lisener on User entity which will remove all associations before user deletion, and this will help to keep UserService clean. The next thing to handle here, you need to change CASCADE
@Entity
@Table(name = "user")
@EntityListener(UderDeletionListener.class)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String lastname;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Badge> badges = new ArrayList<Badge>();
}
@Entity
@Table(name = "badge")
public class Badge {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String code;
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinColumn(name = "badge_id")
private User user;
}
@Component
@RequiredArgsConstructor
public class UderDeletionListener {
private final BadgeRepository badgeRepo;
@PreRemove
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void onDeletion(final User user) {
badgeRepo.deleteByUserId(user.getId());
}
}