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

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

Related

Spring Boot many to many relation - How to get an additional property inside the join table as a property from another entity

Im new in Java Spring and I'm stuck at this point here for a while:
I have an entity model like this:
Entity Relation now
A channellist contains many channels. A channel can be assigned to many channellists. I have setup my code that this works fine. Now I would like to add a property "sort" to my channels, so I would be able to order the channels for every specific channellist in a way I would like to. From the database relation model I know I have to store this information in the joining table "Channellist_Channel".
MY PROBLEM IS: I dont understand how I'm able asign this property to my Entity "Channel" so that every Channel has a "sort"-value depending on the context of the channellist. I read for this case, I have to add a new Entity which will represent the join table "Channellist_Channel" and add the property "sort" there. But the puzzle in my head is just not complete to do it :/
Here are my two entitys Channellist and Channel
CHANNELLIST:
#Entity
#Table(name = "channellist", schema = "stream")
public class Channellist {
#Id
#Column(name = "ID")
private int id;
#Column(name = "DISPLAYNAME")
private String displayName;
#ManyToMany
#JoinTable(
schema = "stream",
name = "Channellist_Channel",
joinColumns = #JoinColumn(name = "channellist_id"),
inverseJoinColumns = #JoinColumn(name = "channel_id")
)
private Set<Channel> channels;
//Constructors...
//Getter Setter...
CHANNEL:
#Entity
#Table(name = "channel", schema = "stream")
public class Channel {
#Id
#Column(name = "id")
private String id;
#Column(name = "display_name")
private String displayName;
#Column(name = "type")
private int type;
#Column(name = "logo_url")
private String logoUrl;
#Column(name = "stream_url")
private String streamUrl;
#OneToMany(mappedBy = "channelId", fetch = FetchType.LAZY)
#OrderBy(value = "timeStart ASC")
private Set<ChannelEpg> programs;
#JsonIgnore
#ManyToMany
#JoinTable(
schema = "stream",
name = "Channellist_Channel",
joinColumns = #JoinColumn(name = "channel_id"),
inverseJoinColumns = #JoinColumn(name = "channellist_id")
)
private Set<Channellist> channellists;
//Constructors...
//Getters Setters...
My JSON Response from "GetAllChannellists: Please Ignore the TAG "programs" under "channels". Its not relevant for my problem.
JSON RESPONSE:
[
{"id":1,
"displayName":"Standard",
"channels":[
{"id":"344143862749137509158c22d1606ad5",
"displayName":"KiKa",
"type":0,
"logoUrl":"https://example.de/test/kika.png",
"streamUrl":"https://example.de/test/kika.m3u8",
"programs":[
{"channelId":"344143862749137509158c22d1606ad5",
"timeStart":"2022-08-09T11:30:00.000+00:00",
"timeEnd":"2022-08-09T11:40:00.000+00:00",
"title":"logo!",
"subTitle":null,
"description":"a verry long description, no one cares"},
{"channelId":"344143862749137509158c22d1606ad5",
"timeStart":"2022-08-09T11:40:00.000+00:00",
"timeEnd":"2022-08-09T12:10:00.000+00:00",
"title":"Tiere bis unters Dach",
"subTitle":"Jojo, der Held",
"description":"another long description, no one cares"},
[...]
{"id":2,
"displayName":"Deluxe",
"channels":[
[...]
My goal is it to make it look like this:
[
{"id":1,
"displayName":"Standard",
"channels":[
{"id":"344143862749137509158c22d1606ad5",
"displayName":"KiKa",
"sort":21,
"type":0,
"logoUrl":"https://example.de/test/kika.png",
"streamUrl":"https://example.de/test/kika.m3u8",

Problem with relation #ManyToOne in Spring Boot, Hibernate, JPA

I have classes -> Country and City.
I wanna create works request, that when I call to get all countries, I will get all countries, with cities.
When I call to get all cities, I will get all cities with only countries from Country model.
I wanna add new cities with relation to countries.
My Country model class:
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#NotEmpty(message = "Name of country is mandatory.")
#Column(unique = true)
private String nameOfCountry;
#NotBlank(message = "Name of capital is mandatory.")
private String capital;
#Max(value = 17098242L, message = "Maximum value for population = 10000000000")
private Long surface;
private String countryCode;
private String telephoneCode;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "country_Id", updatable = false, insertable = false)
private List<CityModelDao> cityModelDao;
My City model class:
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#NotEmpty(message = "Name of country is mandatory.")
#NotNull
#Column(nullable = false, unique = true)
private String nameOfCity;
I know that I don't have here #ManyToOne, but I still do it wrong and now I haven't got more ideas.
My response from get countries:
And this is it what i want.
But when i call to get cities my response is:
Unfortunately I havent got information about country.
In db I have in cities information about fk from country:
Could you help me to do works relation? I ve tried something like:
CityModel:
#ManyToOne()
#JoinColumn(name = "country_Id")
private CountryModelDao countryId;
CountryModel:
#OneToMany(mappedBy = "countryId", orphanRemoval = true, cascade = CascadeType.PERSIST)
private List<CityModelDao> cityModelDao;
But it was wrong. And when I tried with above relation city, I got error.
Could You tell me how to do correct #ManyToOne in this case? What I do wrong?
Thanks
The most simplistic bi-directional OneToMany relationship model should be:
#OneToMany(mappedBy = "countryId")
private List<CityModelDao> cityModelDao;
You set Country as the owner of the relationship Country - City;
You expect an attribute 'countryId' in the CityModelDao;
#ManyToOne
#JoinColumn(name = "country_id")
private CountryModelDao countryId;
You will populate with data based on a join operation that will be executed on the column country_id from the CityModelDao table.
Of course, afterwards, you can enrich the annotations with orphan removal, cascade type etc.
LE:
You are using this via REST and you need to avoid the infinite loop.
Please update the relations to:
#JsonManagedReference
#OneToMany(mappedBy = "countryId")
private List<CityModelDao> cityModelDao;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "country_id")
private CountryModelDao countryId;

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.

Foreign key constraint #ManyToMany relationship preventing deletion

I've three associated records (Conference, SubmissionRecord, SubmissionAuthorRecord). Every SubmissionRecord has a Conference object and has a List<SubmissionAuthorRecord>.
When I delete a Conference record if the SubmissionRecord is associated with that Conference, it should cascade and delete as well. However, I keep getting a java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`viz`.`submission_record_author_set`, CONSTRAINT `FKgnqq52l26bitkmojk1oiuaki1`
FOREIGN KEY (`submission_record_s_id`) REFERENCES `submission_record` (`s_id`)) error message.
The table submission_record_author_set is create automatically and I have no entity that maps to it.
I understand the issue lies in the fact that the submission_record_author_set rows are preventing the SubmissionRecord from being deleted and have tried the #PreRemove method described here (How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?) but to no avail. Maybe there's an issue with the ManyToMany annotation? Cause I do not see the equivalent annotation in the SubmissionAuthorRecord either.
#Entity
public class SubmissionRecord {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
#Column(name = "s_id")
private Long id;
#Exportable(name = "Submission Id", nameInDB = "s_submission_id")
#Column(name = "s_submission_id")
private String submissionId;
// internal set of authors of the associated
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonIgnore
private List<SubmissionAuthorRecord> authorSet;
#JoinColumn(name="conference_id")
#ManyToOne(fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
private Conference conference;
//...
}
#Entity
public class Conference {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String creatorIdentifier;
private String conferenceName;
private String conferenceYear;
}
#Entity
public class SubmissionAuthorRecord {
#Id
#GenericGenerator(name = "UseExistingIdOtherwiseGenerateUsingIdentity", strategy = "xyz")
#GeneratedValue(generator = "UseExistingIdOtherwiseGenerateUsingIdentity")
#JsonSerialize(using = ToStringSerializer.class)
#Column(name = "s_author_id")
private Long id;
private String dataSet;
#Column(name = "s_author_name")
private String name;
}
The submission_author_record_set table looks like the following:

Register data into Many-to-Many Relation Table

I have 'Course' and 'Student' entities. They have many-to-many relation. So, i have COURSE_STUDENT(contains 'student_id' and 'course_id' columns) table. I want to register students to courses with a button.(For example; a student lists courses and click Register button to register a specific course).
When i want to create new courses, i use courseRepository and courseMapper which comes from JHipster by default.
But i don't have repository and mapper files for COURSE_STUDENT. Because it is not actually a main entity. It is created for many-to-many relation.
How can i register students to courses?
Git repo:https://github.com/canberkizgi/monolithic-mucs
My course entity:
#Entity
#Table(name = "course")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Course implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name = "title", nullable = false)
private String title;
#Column(name = "description")
private String description;
#ManyToOne
private Instructor instructor;
#ManyToMany(fetch = FetchType.EAGER)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "course_student",
joinColumns = #JoinColumn(name="courses_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="students_id", referencedColumnName="id"))
private Set<Student> students = new HashSet<>();
Student entity:
#Entity
#Table(name = "student")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(unique = true)
private User user;
#ManyToMany(fetch = FetchType.EAGER,mappedBy = "students")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Course> courses = new HashSet<>();
For example; Createcourse function with Mapper and Repository
#PostMapping("/courses")
#Timed
public ResponseEntity<CourseDTO> createCourse(#Valid #RequestBody CourseDTO courseDTO) throws URISyntaxException {
log.debug("REST request to save Course : {}", courseDTO);
if (courseDTO.getId() != null) {
return ResponseEntity.badRequest().headers(HeaderUtil.createFailureAlert(ENTITY_NAME, "idexists", "A new course cannot already have an ID")).body(null);
}
Course course = courseMapper.toEntity(courseDTO);
course = courseRepository.save(course);
CourseDTO result = courseMapper.toDto(course);
return ResponseEntity.created(new URI("/api/courses/" + result.getId()))
.headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString()))
.body(result);
}
The relationship is owned by the course entity. Thats because on the student side the #ManyToMany annotation has a mappedBy attribute. This means, that the database will reflect the set in the course. You need to add students to that set to save the relationship. That change needs to be done within a transaction.
That being said it would probably be best to follow DDD here. I would create a registerTo method in the student class that would take the course as a parameter. I would then call this.courses.add(course) and course.getStudents().add(this) in that method.

Resources