Issue
Guys I'm new to Spring so bear with me. I have two entities. User and Genre. User entity contains a list of Genre entities. i.e. "User has genres" ("Genre has users" is not a requirement of me.)
My two entities...
@Entity
@Table(name = "user")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
@Id
@GeneratedValue
private int id;
private String username;
private String userEmail;
private String firstName;
private String lastName;
private String imgUrl;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "user_genres", joinColumns = @JoinColumn(name = "User_id"), inverseJoinColumns = @JoinColumn(name = "genres_id"))
private List<Genre> genres;
}
@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class Genre {
@Id
@GeneratedValue
private int id;
private String name;
}
and this is the request body I'm sending to save my User record...
{
"username":"test username",
"userEmail":"[email protected]",
"firstName":"test fname",
"lastName":"test lname",
"imgUrl":"url",
"genres":[
{
"id":8,
"name":"Drama"
}
]
}
I maintain a table of genres. When I want to save a user, I pick some genres from the table, save them in a list in User entity and send them to save(). My expectation is to save those relationships in user_genres table with user_id and genre_id.
My problem is, when I try to save a user with a genre object, if there is already a user with a genre object of id as same as in the user I'm trying to save, it gives me an exception saying that id of genre already exists.
i.e. Suppose I already have a user which contains a genre of id 8. Now I'm sending a new user, that means with a new user_id, which also contains a genre with id 8. Then it will give me an exception,
SQLIntegrityConstraintViolationException: Duplicate entry '8' for key 'UK_1ro2jvu91tg2xsrdye9sk9j1q'
This works fine if I use a different genre with a different id. Please tell me a way to obtain the result as I expected. Thanks.
My repository,
public interface UserRepository extends JpaRepository<User, Integer> {}
My service,
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Override
public ResponseEntity save(User user) {
if (getUserByUserEmail(user.getUserEmail()) == null)
return new ResponseEntity(userRepository.save(user), HttpStatus.OK);
return new ResponseEntity(null, HttpStatus.BAD_REQUEST);
}
public User getUserByUserEmail(String userEmail) {
return userRepository.findByUserEmail(userEmail);
}
}
My controller,
@Autowired
UserServiceImpl userService;
@PostMapping("/users")
public ResponseEntity save(@RequestBody User user) {
return userService.save(user);
}
Update : Cascade type changed to MERGE In User,
@ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
@JoinTable(name = "user_genres", joinColumns = @JoinColumn(name = "User_id"),
inverseJoinColumns = @JoinColumn(name = "genres_id"))
private List<Genre> genres;
Update : Overrided following methods inside User and Genre
In User,
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(getId(), user.getId());
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
In Genre,
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Genre)) return false;
Genre genre = (Genre) o;
return Objects.equals(getId(), genre.getId());
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
Solution
Step 1
Create hashcode()
and equals()
methods for both User and Genre based on their Id fields.
Step 2
@ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
@JoinTable(name = "user_genres", joinColumns = @JoinColumn(name = "User_id"),
inverseJoinColumns = @JoinColumn(name = "genres_id"))
private List<Genre> genres;
Now you should be ready to go.
Answered By - Panagiotis Bougioukos