Problem with relation #ManyToOne in Spring Boot, Hibernate, JPA - spring-boot

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;

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",

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

I seem to need both #OneToOne and #OneToMany relationship

I'm writing an app to operate a collection of hardware that is supposed to be setup and shutdown repeatedly in different locations.
I want to track those installations so I've created separate entities for a physical object itself and the installation.
Station object needs to keep track of up to one (null while not installed) active installation information (so #OneToOne) but also all the previous installations (so #OneToMany)
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "stationInstallation", cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station_id", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
The other entity will have the station id, location and date of the setup as well as the shutdownDateTime being null.
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy ="stationInstallation", cascade - CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
Finally the shutdown method is supposed to unattach the entity from the Station entity by setting activeStationInstallation to null and setting shutdownDateTime to LocalDateTime.NOW.
...
stationInstallation.getStation().setActiveStationInstallation(null);
stationInstallation.setShotdownDateTime(LocalDateTime.NOW);
...
But that will obviously result in a growing number of StationInstallation "finished" objects, which would seem to require #ManyToOne relation with the Station all the while I want to keep #OneToOne relation with the activeStationInstallation.
What do?
I figured I could just make another entity calling it FinishedStationInstallation, remove shutdownDateTime from StationInstallation, make both immutable and instead of adding shutdownDateTime with a setter add it in the constructor while deleting the active version. And immutablity is an asset, ut at the same time I'd have to add several new tables to the db and keep track of and query two entities instead of one.
EDIT: I guess I could get rid of List stationInstallations from the 1st entity, since I don't really need to keep the track of it beyond db queries, but I added it for the clarity of the question.
I would model it this way:
#Entity
#Table(name = "station")
class Station{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.DETACH,
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH)
#JoinColumn(name = "station_installation_id", nullable = true)
private StationInstallation activeStationInstallation;
...
#OneToMany(fetch = FetchType.Lazy, mappedBy = "station", cascade =
{CascadeType.DETACH, CascadeType.PERSIST,
CascadeType.MERGE, CascadeType.REFRESH })
private List<stationInstallation> stationInstallations;
...
}
#Entity
#Table(name = "stationInstallation")
class StationInstallation{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne(mappedBy ="stationInstallation", cascade = CascadeType.ALL)
private Station station;
#Column(name = "location")
private String location;
#Column(name = "setupDateTime")
private LocalDateTime setupDateTime;
#Column(name = "shutdownDateTime")
private LocalDateTime shutdownDateTime
}
The one-to-one relation from Station to installation has a FK => so no mapped by
The one-to-many relation from Station to installation is mapped by the station association on installation, which obviously has to be a many-to-one association, since the other side is a one-to-many

OneToOne JPA issue

I have 2 class
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String age;
#OneToOne
#JoinColumn(name = "address_id", referencedColumnName = "id")
private Address address;
}
and
public class Address {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String building;
private String country;
#OneToOne(mappedBy = "address")
private User user;
}
in my table address, I have a few rows.
When I insert table user with data
{
"id":null,
"name":"Foo",
"age":"18",
"address":{
"id":1,
"building":"Too",
"country":"ABS"
}
}
Table user have 1 row with address_id =1.
I insert same data as above
Table user have 2 row with address_id =1.
My answer is: why 2 table connected by one to one can happen the above case?
You can find your answer here
Why #OneToOne is allowing duplicate associations?
Basically, #JoinColumn(name = "address_id", referencedColumnName = "id") alone doesn't serve the semantics of one-to-one in the database, you need to add unique=true into the #JoinColumn, which makes it #JoinColumn(name = "address_id", referencedColumnName = "id", unique = true).
Side-note: I suggest you drop your tables and then re-creating them before trying this out. If you are using Hibernate, you can set hibernate.hbm2ddl.auto to create-drop

Spring data JPA populate data of 2 One-To-Many relationship

I have a ManyToMany relationship that is broken down into 2 OneToMany relation. A Book can belong to multiple Categories and a Category can have many Books.
When I query for Book, the categories list just get empty. How can I get a list of all Categories that a Book belong to ? Am I missing something ?
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotBlank(message = "Please input book title")
private String title;
private Integer publishYear;
private String publisher;
private String language;
private Integer numberOfPages;
private String avatarUrl;
#OneToMany(targetEntity = BookCategory.class, cascade = CascadeType.ALL)
#JoinColumn(name = "category", nullable = false, insertable = false, updatable = false)
private Set<BookCategory> categories = new LinkedHashSet<>();
}
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false)
#NotBlank(message = "Please input category name")
private String name;
}
#Entity
public class BookCategory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "book", nullable = false)
private Book book;
#ManyToOne
#JoinColumn(name = "category", nullable = false)
private Category category;
}
Try using mappedBy attribute with #OneTOMany annotation on the 'One' side of the OneToMany relation.
Or you can also try using the following:
#ManyToMany
#JoinTable(
name = “book”_category,
joinColumns = #JoinColumn(name = “book_id”),
inverseJoinColumns = #JoinColumn(name = “category_id))
Read more:
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Resources