Found shared references to a collection many to many relation - spring

I have this method:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
movieRepository.save(movie);
userRepository.save(new HashSet<User>(){{
add(new User(current_user, new HashSet<Movie>(){{
add(movie);
}}));
}});
return movieRepository.save(movie);
}
When I run my application and call that function I get this error:
Found shared references to a collection: com.movieseat.model.security.User.movies
In my User model I have:
#ManyToMany
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies;
And in my Movie model I have:
#ManyToMany(mappedBy = "movies", cascade = CascadeType.ALL)
private Set<User> users = new HashSet<>();
public Set<User> getUsers() {
return users;
}
What produces the error?

As I understand your code, you're trying to create a Movie in database and bind it to the current User. Correct me if I'm wrong.
At first, as you may learn from Hibernate User Guide, bidirectional #ManyToMany association should be defined and used differently.
A bidirectional #ManyToMany association has an owning and a mappedBy side. To preserve synchronicity between both sides, it’s good practice to provide helper methods for adding or removing child entities.
Secondly, you should not use CascadeType.ALL on #ManyToMany associations:
For #ManyToMany associations, the REMOVE entity state transition
doesn’t make sense to be cascaded because it will propagate beyond the
link table. Since the other side might be referenced by other entities
on the parent-side, the automatic removal might end up in a
ConstraintViolationException.
For example, if #ManyToMany(cascade = CascadeType.ALL) was defined and
the first person would be deleted, Hibernate would throw an exception
because another person is still associated with the address that’s
being deleted.
So, we should move cascade to the owning side, change cascade type, provide helper methods to the User and update only the owning side (User) of the association in our business logic. Let's change the code.
User model:
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "user_movie",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "movie_id", referencedColumnName = "id")
)
private Set<Movie> movies = new HashSet<>();
public void addMovie(Movie movie) {
movies.add(movie);
movie.getUsers().add(this);
}
public void removeMovie(Movie movie) {
movies.remove(movie);
movie.getUsers().remove(this);
}
Movie model:
#ManyToMany(mappedBy = "movies")
private Set<User> users = new HashSet<>();
And business logic:
#Override
public Movie createMovie(Movie movie) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
JwtUser user = (JwtUser)authentication.getPrincipal();
User current_user = userRepository.findOne(user.getId());
current_user.addMovie(movie);
userRepository.save(current_user);
return movie;
}

Related

Spring Data JPA, change to one attribute of Many To Many entity is wrongly being shown on all other entities that share it

When I make changes to one attribute of an entity, it also somehow gets changed for every other entity that uses that entity. I have three entities as you can see below.
Students and courses need to have a many-to-many relationship between them and the course needs to have a one-to-many relationship with course lectures.
When I make changes to courses or course lectures that belong to a specific student by doing #Transactional student.getCourse().get(0).setTitle("whatever"), those changes are also reflected in other students who share the same course. I need help with this, thank you
The student class
public class Student {
#Id
#SequenceGenerator(
name = "student_sequence",
sequenceName = "student_sequence",
allocationSize=1
)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "student_sequence")
private Long studentId;
private String fullName;
#Column(name = "email_address", nullable = false)
private String email;
private String username;
private String password;
#ManyToMany(mappedBy = "students", fetch = FetchType.EAGER)
private List<Course> courses ;
public void addCourse(Course course) {
if (courses == null) {
courses = new ArrayList<>();
}
courses.add(course);
}
Course Class
public class Course {
#Id
#SequenceGenerator(
name = "course_sequence",
sequenceName = "course_sequence",
allocationSize = 1
)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "course_sequence")
private Long courseId;
private String title;
private double courseRating = 0;
private LocalDateTime createdAt = LocalDateTime.now();
private double completedProgress = 0;
#Embedded
private CourseInformation courseInformation;
#OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.EAGER)
#JoinColumn(name = "course_id", referencedColumnName = "courseId")
private List<CourseLecture> courseLectures;
#ManyToMany(
cascade = CascadeType.MERGE,
fetch = FetchType.LAZY
)
#JoinTable(
name = "student_course_mapping",
joinColumns = #JoinColumn(
name = "course_id",
referencedColumnName = "courseId"
),
inverseJoinColumns = #JoinColumn(
name = "student_id",
referencedColumnName = "studentId"
)
)
#ToString.Exclude
private List<Student> students;
There is no relationship mapping in the CourseLecture class.
This is not wrong, but just the way JPA works.
Technically it works, because they all reference the same instance as JPA guarantees to always return the same instance for a given class and id in single session.
If you don't want that you'd have to do the work either in different sessions, or you have to change your data model, so that each student has their own course. Of course this would be a strange model.
Update based on your comment:
Looks like indeed you need a different model, instead of Student -N-M-> Course you need something like Student -1-N-> Attendance -N-1-> Course, making the mapping table of your relationship into an entity and allowing it to store extra data that is specific to Student AND Course

Hibernate mapping user relation to entities

Let's se we have Hibernate entity User with basic fields such as username, password, roles etc..
Now we have an entity such as Car.
User has a OneToOne relationship with Car, cause he can own a car. But he also has besides this a OneToMany relationship to Car, because he also owns the cars of his children. But in the frontend I want to know which cars he owns for himself and which cars he owns for his children. The same applies to the relationship between User and motorbike (his own, his childrens, etc...)
How would the User entity class look like? Is it good to have the relationships mapped in an "Helper" entity such as UserData:
#Entity
#Data
#Table( name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username")
})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 150)
private String username;
#NotBlank
#Size(max = 120)
private String password;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_DATA_ID")
private UserData userData;
UserData:
#Entity
#Data
#Table( name = "user_data")
public class UserData {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "OWN_CAR_ID")
private Car ownCar;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTNER_CAR_ID")
private Car partnerCar;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_children_cars",
joinColumns = #JoinColumn(name = "user_data_id"),
inverseJoinColumns = #JoinColumn(name = "car_id"))
private Set<Car> childrenCars = new HashSet<>();
public boolean addToChildrenCarSet(Car c) {
return childrenCars.add(c);
}
public UserData() {
}
}
As you ask for an opinion, I would say it gets unnecessary complicated if you use the intermediate entity user_data. :-) There is no real drawback to add more fields and keys into the user class - performance is probably also better then using the EAGER fetching. If performance is an issue, better optimize querys later on then splitting the table now.
Also the #ManyToMany I would avoid - better create the intermediate table and relations yourself. You can check out https://bootify.io and create your database schema there. There is no EAGER fetching and also no CascadeType.ALL (both only good ideas in special cases), you would probably add more problems with that then actual helping in any way.
So the addToChildrenCarSet method would end up in a #Service class, in a method with #Transactional, in my proposal.

spring one-to-may annotation exception

I'm using spring-boot.
I have a class named Datum and a class named User. A user can have many datum. So the relation is one-to-many from user's perspective. Now I want to design those classes.
Here is what I have tried :
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
And :
public class User{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
Set<Datum> data = new HashSet<>();//i have tried list-arrayList too
...
}
But this gives me org.hibernate.AnnotationException: Illegal attempt to map a non collection as a #OneToMany, #ManyToMany or #CollectionOfElements: error.
What are wrong here?
public class Datum{
...
#OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
private User user;
...
}
This is not a OneToMany relationship. This is a ManyToOne, by looking at your User class.
This will do the job:
public class Datum {
...
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private User user;
...
}
As a user can have many datum, so in user class relation will be like
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Datum> datums = new ArrayList<>()
And in Datum class no relation is needed.
The One to Many relationship is a Many to One on the other side. You should map it in this way:
public class Datum{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
...
}
And:
public class User{
...
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private Set<Datum> data = new HashSet<>();
...
}
As you can see, the relation is a Many to One on the Datum side. Furthermore it should have a FetchType.LAZY to increase performance and, if can't be a Datum without user, you should also ad "orphanRemoval = true".

Spring boot domain class required for mapping table

I m new to Spring Boot. I have a table (Team) that has resources, am storing in a separate table (Resources) and have team_resource mapping table (with fields teamid, resourceid). My question is should I have a domain class for the mapping_table too ?
When I m inserting a new team (POST) with resources I create entry in all 3 tables. I m using facade/dao pattern for writing/ reading to the DB. I have to handle when the team is modified/ deleted. Should I have a domain class for the mapping_table?
There are multiple approaches you can handle it
Approach 1
Define #ManyToMany between your Team and Resources entity like this:
In Team Entity
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "resources",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "id") })
private Set<Resources> resources= new HashSet<>();
In your resources entity:
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "resources")
private Set<Team> teams= new HashSet<>();
Approach 2
#Entity
#Table(name = "team_resources")
public class TeamResources implements Serializable {
#EmbeddedId
private TeamResourcesId id;
#ManyToOne
#JoinColumn(name = "team_id", referencedColumnName = "id", insertable = false, updatable = false)
private Team team;
#ManyToOne
#JoinColumn(name = "resources_id", referencedColumnName = "id", insertable = false, updatable = false)
private Resources resources;
public TeamResources (Team u, Resources r) {
// create primary key
this.id = new TeamResourcesId (u.getUserId(), q.getQuestionId());
// initialize attributes
this.user = u;
this.question = q;
}
#Embeddable
public static class TeamResourcesId implements Serializable {
#Column(name = "team_id")
protected Long teamId;
#Column(name = "resources_id")
protected Long resourcesId;
public TeamResourcesId () {
}
public TeamResourcesId (Long teamId, Long resourcesId) {
this.teamId= teamId;
this.resourcesId= resourcesId;
}
//Getter , setter. equal and hash
}
so to answer your question, follow second approach and its good to not define bidirectional approach as it can lead to some run time problem if not handled properly.

JPA many to many - do i have to remove\add from both collection sets?

I have a User entity,
And a Department Entity
i want to have #manyToMany relationship between them :
many users can have many departments.
in my User entity:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "UserDepartments", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "department_id", referencedColumnName = "id") })
private Set<Department> departments;
and my Department entity has a SET as well of users..
my question is:
if i need to implement the method :
public void removeUserFromDepartment(User user, Department department) {
//bla bla
}
do i have to call
department.getUserCollection.remove(user);
AND
user.getDepartmentCollection.remove(department);
Or is there a way to maintain this logic by only removing one of them ?
If i have to save both its pretty hard to maintain especially for someone who doesn't know about the many to many relation of the two entities..
When a OneToMany or ManyToMany relationship exists in JPA the client code is responsible for managing the relationship. This means that you must explicitly remove the object from both sides of the relationship.
So lets say you have a User instance and need to remove a department.
User user = dao.findUser(1L); //Find a user, psuedo code
Department dept = user.getDepartments().get(0);
dept.getUsers().remove(user);
user.getDepartments().remove(user);
dao.update(user); //once again psuedo code
To use the code above you should add a cascade type to the relationship in the user entity:
#ManyToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
#JoinTable(name = "UserDepartments", joinColumns = { #JoinColumn(name = "user_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "department_id", referencedColumnName = "id") })
private Set<Department> departments;
This will cause saves on the User entity to be cascaded to the Departments entity. Just a reminder save is psuedo code, it will boil down to a call on the EntityManager such as persist or merge.

Resources