I am trying to stop my relationship making new tables. I have tried multiple approaches to this problem, but there seems to be an error every way I turn. For instance when I try the following code:
//other variables
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<User> users= new ArrayList<>();
I get the following error:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`eb322`.`#sql-3140_2e7`, CONSTRAINT `FK20sqpkpotyyf5wx4jfmp519lu` FOREIGN KEY (`user_id`) REFERENCES `year` (`year_id`))
I have checked all my tables and indexes in the database and I cannot find this constraint anywhere. How do I go about removing it. I basically want to have my schema be like this:
Year will have a list of all students, teachers. When a student is enrolled they will be added to that year etc.
If I don't add the join Column I simply get another table saying
Year.students
How do I combine these together.
This is my student class just incase there's something wrong here:
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int User_id;
}
How I am adding data to year table
//get data about student
Student s = ssrepo.findByName(name);
Year y = yyrepo.findByYear(year);
List<Student> students = y.getStudents();
students.add(s);
yyrepo.save(y)
You seem to be using Unidirectional OneToMany relationship
Hibernate uses an association table to map the relationship so when you remove #JoinColumn annotation an association table is created.
As Year has one to many relationship with student, the type of the List should be List<Student> instead of List<User>
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Student> users= new ArrayList<>();
And using OneToMany Unidirectional association is normally not recommended because of its performance issues. You can consider using bidirectional association. It would be something as follows
public class Year {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "YEAR_ID")
private Long id;
#Column(name = "TYPE_ID")
private Long typeId
#Column(name = "TYPE")
private Boolean type // 1 or 0 to know if typeId is of student or teacher
#Column(name = "YEAR")
private Date year
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Student> students;
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Teacher> teachers;
}
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TEACHER_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "STUDENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
There are two ways to do this. The first is bidirectional. Where you do the mapping in the two entities. here in this link.(https://dzone.com/articles/introduction-to-spring-data-jpa-part-4-bidirection)
hava exemples.
public class MyClass {
#OneToMany(mappedBy = "myClass", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<User> users;
}
mappedBy is to say who is the dominate in the relationship. In this case, MyClass has the strongest relationship.
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private MyClass myClass;
}
I believe that this is the best way, because her realities are apparent in both entities. There is a way to do it in a unidirectional way. Exemple in link (How to define unidirectional OneToMany relationship in JPA)
Related
In this case, I have 2 entities (Users table and UploadRecord table). I need to map a one-to-many relationship because one user can have many upload records. I need to use UserId as the primary key in the Users table and a foreign key as the UploadRecord table.
I tried using this code but the UploadRecord table fk_UserId is not updating. How to fix this issue?
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_UserId", referencedColumnName = "UserId")
private List<UploadRecord> uploadRecord;
I wrote Users entity class and UploadRecord entity class as follows.
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name= "UserId")
private Long UserId;
#Column(nullable = false, unique = true, length = 45)
private String email;
#Column(name = "fullName", nullable = false, length = 20)
private String fullName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_UserId", referencedColumnName = "UserId")
private List<UploadRecord> uploadRecord;
//Getters and setters
#Entity
#Table(name = "uploadrecord")
public class UploadRecord {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uploadRecordId;
#Column(nullable = false, unique = false, length = 1000)
private String fileName;
//Getters and setters
It seems you haven't finished modelling the relationship between these two entities.
Edit your models like this:
User:
#OneToMany(mappedBy="user", cascade = CascadeType.ALL)
private List<UploadRecord> uploadRecords;
UploadRecord :
#ManyToOne
#JoinColumn(name = "userId")
private User user;
More details for modelling relations: Baeldung
Moreover keep an eye on naming convention:
UserId -> userId
uploadRecord -> uploadRecords (Lists, Sets, ...) -> plural
I am new in Hibernate and just look at several examples and started to make some practice.
Here in the example, there are 3 entities which have relations e.g. #ManyToMany, #OneToMany and #ManyToOne.
Student:
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#JsonIgnore
#ManyToMany(mappedBy = "students")
private Set<Subject> subjects = new HashSet<>();
}
Subject:
#Entity
#Data
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#ManyToMany
#JoinTable(
name = "subject_student",
joinColumns = #JoinColumn(name = "subject_id"),
inverseJoinColumns = #JoinColumn(name = "student_id")
)
Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
}
Teacher:
#Entity
#Data
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonIgnore
#OneToMany(mappedBy = "teacher")
private Set<Subject> subjects;
}
My questions are:
1. In the subject entity, I tried to remove #JoinColumn and the related entities are connected as the example above:
#ManyToMany
#JoinTable(name="subject_student")
public Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Teacher teacher;
So, if we want to use subject_id - student_id pair in subject_student table and use teacher_id in subject table as it is created in the example, can I use my simplified notation by removing #JoinColumn? Because, if there is not a special case, I think it is redundant to verbose notation of relations.
2. When I use the second approach, the columns are created as plural e.g. subjects_id - students_id in subject_student. So, can I prevent this and create them as in the previous example by using my approach?
I'm writing an API using Spring Boot and Hibernate where my persisted entity objects are also used as DTOs sent to and from the client. This is a simplified version of a typical entity I use:
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#ElementCollection
#CollectionTable(name = "GROUP_STUDENT",
joinColumns = #JoinColumn(name = "GROUP_ID"))
#Column(name="STUDENT_ID")
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
and this is the associated class:
#Entity
#Table(name = "GROUP")
public class Group {
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
private Set<Student> students = new HashSet<>();
// getters and setters
}
As you can see, there is a #ManyToMany association between Student and Group.
Since I send objects like these to the client, I choose to send only the id's of the associations and not the associations themselves. I've solved this using this answer and it works as expected.
The problem is this. When hibernate tries to persist a Student object, it inserts the groups as expected, but it also tries to insert the groupIds into the mapping table GROUP_STUDENT. This will of course fail because of the unique constraint of the mapping table composite id. And it isn't possible to mark the groupIds as insertable = false since it is an #ElementCollection. And I don't think I can use #Formula since I require a Set and not a reduced value.
This can of course be solved by always emptying either the groups of the groupIds before saving or persisting such an entity, but this is extremely risky and easy to forget.
So what I want is basically a read only groupIds in the Student class that loads the data from the GROUP_STUDENT mapping table. Is this possible? I'm grateful for any suggestions and glad to ellaborate on the question if it seems unclear.
I've managed to solve this by making the id-collection #Transient and populating it using #PostLoad:
#Entity
#Table(name = "STUDENT")
public class Student {
#PostLoad
private void postLoad() {
groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
}
#Id
#GeneratedValue
#Column(name = "ID")
private Long id;
#Transient
private Set<Long> groupIds;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name="GROUP_STUDENT",
joinColumns = #JoinColumn(name="GROUP_ID"),
inverseJoinColumns = #JoinColumn(name="STUDENT_ID")
)
private Set<Group> groups = new HashSet<>();
// getters and setters
}
Here is my JPA structure:
Movie (look at cascade types):
#Entity
#Table(name = "movie")
public class Movie {
#Id
#Column(name = "movie_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//#OneToMany(cascade = CascadeType.ALL, mappedBy = "primaryKey.movie") //stack overflow
#OneToMany(mappedBy = "primaryKey.movie") //works fine
private List<Rating> ratings;
....
}
Rating:
#Entity
#Table(name = "rating")
#AssociationOverrides({#AssociationOverride(name = "primaryKey.movie", joinColumns = #JoinColumn(name = "movie_id")),
#AssociationOverride(name = "primaryKey.user", joinColumns = #JoinColumn(name = "imdb_user_id"))})
public class Rating {
#EmbeddedId
private RatingId primaryKey = new RatingId();
#Column(name = "rating_value")
private Integer ratingValue;
.....
}
RatingId:
#Embeddable
public class RatingId implements Serializable{
#ManyToOne
private Movie movie;
#ManyToOne
private User user;
}
When I call entityManager.merge(Movie movie) with CascadeType.ALL I get the StackOverflowError. If remove cascading, merge call doesn't throw the error. Where may be a problem?
I think this problem related to composite primary key. There is no error when merge performed on another entities with the same one-to-many relationship, but without composite id.
StackOverflow was caused by cyclic relations. To avoid exception I marked keys in many-to-many table as #ManyToOne(fetch = FetchType.LAZY).
That's how my tables look after modifications: https://stackoverflow.com/a/32544519/2089491
I'm looking how to map in JPA a hashmap with its value being a list.
I've 2 entities:
EntityA {
#Id
#GeneratedValue
private Integer id;
#MapKey(name = "nature")
#MapKeyEnumerated(EnumType.STRING)
#OneToMany(mappedBy = "entityA", cascade = CascadeType.ALL)
private Map<NatureEnum, List<EntityB>> mapEntityB = new HashMap<NatureEnum, List<EntityB>>();
}
EntityB {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name = "ID_ENTITYA", nullable = false)
private EntityA entityA;
#Column(name = "NATURE", nullable = false)
#Enumerated(EnumType.STRING)
private NatureEnum nature;
}
You will have guessed that of course the map doesn't work as it's missing something telling it it's not just one EntityB in it.
The rest does work and if i change the map by a regular List it works fine.
So how can i manage to "group by" my EntityB in one list depending on the value of Nature?
Thanks in advance for your help.