Hibernate: Join table with extra columns, remove children from one side - java

The scenario is the following
I have 2 tables, Company and Activity. A company can have one or more activities. One of these activities is a "primary" activity, and all others become secondary.
To handle this, I created 2 entities (Activity, Company) and a third entity for the join table, which is CompanyActivity
I used this tutorial as a starting point
Below my code (getters and setters omitted)
Company.java
#Entity
#Table(name = "T_COMPANY")
public class Company {
#Id
#Column(name = "COM_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
private List<CompanyActivity> activities = new ArrayList<>();
}
Activity.java
#Entity
#Table(name = "T_ACTIVITY")
public class Activity {
#Id
#Column(name = "ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String code;
private String description;
private boolean availableOnline;
}
CompanyActivity.java
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Adding activities for a company works without a problem. The children collection contains the newly added activities, and there is always one marked as primary as expected.
The problem happens when updating a company.
When I add a new activity, all previous existing activities are persisted again.
When I remove an activity, it is not removed from the table.
I'm using this code to update a company' activities
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
In this code, newActivities have the new activities that should be considered (this collection does not have the previous ones, I just replace them all)
I tried adding orphanRemoval=true to the #OneToMany(cascade = CascadeType.ALL, mappedBy = "company") on Company, but this deletes the activity type when no other company is using it, which is wrong as they should be available always.
Can you please help me sync the activities collection on Company without removing elements from Activity table ?
Thanks a lot!

I solved it. Here are the steps I followed.
First, I changed my Join table entity cascade types as follows
#Entity
#Table(name = "T_COMPANY_ACTIVITY")
public class CompanyActivity {
#Id
#Column(name = "COM_ACT_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "COM_ID")
private Company company;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "ACT_ID")
private Activity activity;
private boolean primary;
}
Then, I added again the "orphanRemoval" property to Company mapping, and changed my CascadeTypes too, as follows
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "empresa", orphanRemoval = true)
private List<CompanyActivity> activities = new ArrayList<>();
With these changes, my mapping works as expected with the same code I used to replace the relationships.
company.getActivities().clear();
company.getActivities().addAll(newActivities);
company = repository.save(company);
Thanks :)

The way you created your entities is not correct. You don't need to create an entity for your join table (CompanyActivity/T_COMPANY_ACTIVITY). Instead you should be using the #JoinTable on your activities entity. Something like below:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "company")
#JoinTable(
name = "T_COMPANY_ACTIVITY",
joinColumns = #JoinColumn(name = "COM_ID"),
inverseJoinColumns = #JoinColumn(name = "ACT_ID")
)
private List<CompanyActivity> activities = new ArrayList<>();
for more detailed explanation on how One-to-Many/Many-to-One with Join tables work here: http://www.codejava.net/frameworks/hibernate/hibernate-one-to-many-association-on-join-table-annotations-example

Related

Spring JPA stop creating a new table with relationship

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)

Changing a OneToMany Bidirectional Relationship to ManyToMany Bidirectional

I want to convert the following mapping on courseDetails to manyToMany.
This is because I get an exception Found shared references to a collection: com.xyz.courseDetails and I assume this happens because the relation is not actually one to many in the database, since there are some course_detail tuples that has multiple courses.
#Entity
#Table(name = "courses")
public class Course
{
#Column(name = "course_detail_id")
private Long extendedCourseDetailId;
...
#OneToMany(fetch = FetchType.LAZY, targetEntity = CourseDetail.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName="course_detail_id")
private List<CourseDetail> courseDetails = new ArrayList<>();
}
Simply changing the annotation to ManyToMany does not work, JPA somehow couldn't find the related columns. Why? How can I do this?
What do you think of this :
Let's assume the entity CourseDetail has as ID :
public class CourseDetail
{
#Id
#Column(name = "cd_id")
private Long courseDetailId;
So this non tested code might help you.
where the table "course__course_detail" will be automatically created to hold the relationship with 2 columns : "course_id" and "coursedetail_id".
#Entity
#Table(name = "courses")
public class Course
{
#Id
#Column(name = "c_id")
private Long courseId;
// #Column(name = "course_detail_id") // I comment because I dont understand the purpose
// private Long extendedCourseDetailId;
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "course__course_detail",
joinColumns = #JoinColumn(name = "course_id", referencedColumnName="c_id"),
inverseJoinColumns = #JoinColumn(name = "coursedetail_id", referencedColumnName="cd_id"),
)
private List<CourseDetail> courseDetails = new ArrayList<>();
}
PS: NOT TESTED
Feel free to tell me more in comments.

Unable to remove childs in OneToMany Mapping and add new childs [Hibernate]

I know this question has been asked many times but none of the solution is working for me.
So I have a Parent class :
class User{
#Id
#NotNull
#Column(name = "`UserId`", nullable = false)
private Long userId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "`UserId`")
private Set<Phone> phoneList;
}
And a child class:
class Phone {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "`UserId`")
private User user;
}
Now when I received a update for User class with new phone list, I want to remove all the old phones and add new phones. Please note that this all operation is happening in same #Transactional.
Solution I tried:
user.getPhoneList().clear()
user.getPhoneList().addAll(new phone list)
When I try the above logic, Hibernate is trying to set old phone with userId as null. At this position I am getting DataIntegrityViolation as userId in Phone table is non null column.
Please provide any appropriate solution which can work here.
Hhhmmm... I have the exact same logic and it works fine by me. Here are my classes
#Data
#Entity
public class ProductReference {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "productReference", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Attribute> attributes = new HashSet<>();
}
The only difference I see is the CascadeType.REMOVE
#Data
#Entity
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
private ProductReference productReference;
}
My deletion:
productReference.getAttributes().clear();
Which Hibernate version you have? by me it is org.hibernate.Version - HHH000412: Hibernate Core {5.4.10.Final}

Get id's of a ManyToMany mapping table

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
}

Java Spring Repository delete not working OneToMany Relationship

I have an issue with a delete to a Many side of a ManyToOne relationship. I've already removed all CascadeTypes from the relationship but the issue still remains. The entry won't be removed (only selects are executed and no delete query). I'm trying to delete it through a CRUD repository call to delete. It calls the method and executes successfully but nothing happens.
The relationship goes as follows: an Activity has an assigned Course, a course can have many activities assigned to it. An Activity has a specific ActivityType.
The classes are as below.
Activity
public class Activity implements Item, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne
#JoinColumn(name = "type_id", nullable = false)
private ActivityType type;
#ManyToOne
#JoinColumn(name = "course_id", nullable = false)
#JsonSerialize(using = CustomCourseSerializer.class)
private Course course;
...
}
Course
public class Course implements Item, Serializable {
...
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER, targetEntity = Activity.class) //cascade = { CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH, CascadeType.REMOVE}
#Fetch(value = FetchMode.SUBSELECT)
private List<Activity> activities;
...
}
Activity Type (has no reference to Activity)
public class ActivityType implements Item, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
...
}
Any ideas how can I solve this issue or at least debug it? Thank you.
Add orphanRemoval = true attribute in the #OneToMany annotation in your Course entity.
public class Course implements Item, Serializable {
...
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER, targetEntity = Activity.class, orphanRemoval = true, cascade = CascadeType.REMOVE )
#Fetch(value = FetchMode.SUBSELECT)
private List<Activity> activities;
...
}
Try to delete reference to Activity from Course. It seems unnecessary to me

Categories

Resources