Hibernate onetomany unidirectional relation , deleting mappings when updating Parent object? - java

I have a Parent class Seller , it have onetomany mappings to all many child classes. It also has some properties as name,id , email . In edit sequence I am fetching this object displays it on jsp , bind it through Spring MVC form , takes input from user and then save it as it is to db using session.saveorupdate(seller).
But while doing so my previous mappings with all the child classes are getting deleted. Only the mapping from join tables are getting deleted. If there is a two way mapping using mappedBy , it is not deleted.
All the mapping are lazy fetch.
Scenario is :
Step 1: Register seller . Add email ,name values.
Step 2 : Create child object in different journeys , create List of A ,Band add to existing seller object.
Step 3: Change name of seller , editing the seller and after saving all the previous mapping are gone.
Seller.java
#Entity
#Table(name = "Seller")
public class Seller {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column
private String name;
#Column
private String address;
#Column
private String email;
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "seller_roles", joinColumns = { #JoinColumn(name = "seller_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "role_id", referencedColumnName = "id") })
private Role role;
#OneToMany(mappedBy = "seller", cascade = CascadeType.ALL)
private List<A> a = new ArrayList<A>();
#OneToMany(cascade = CascadeType.ALL)
private List<B> b = new ArrayList<B>();
#OneToMany(mappedBy = "seller", cascade = CascadeType.ALL)
private List<C> c = new ArrayList<C>();
#OneToMany(cascade = CascadeType.ALL)
private List<D> d = new ArrayList<D>();
#OneToMany(cascade = CascadeType.ALL)
private List<E> e = new ArrayList<E>();
Saving seller in SellerDaoImpl as :
Session session = sessionFactory.openSession();
session.beginTransaction();
session.saveOrUpdate(seller);
session.getTransaction().commit();
session.close();
Please help.

Related

How to convert collection of entities relation into collection of primitive ids in JPA/Hibernate?

I have two entities connected with many-to-many relationship. For example:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Game> games = new HashSet<>();
}
#Entity
public class Game {
#Id
private Long id;
#ManyToMany(mappedBy = "games", fetch = FetchType.LAZY)
List<Account> accounts = new ArrayList<>();
}
So, there is a table account_games(account_id, game_id) in mysql describing entities many-to-many relations.
I don't want to have Game entity anymore. Is there a way to get rid of Game and leave gameId relation only? So, I'd like to have code something like that:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Long> gameIds = new HashSet<>();
}
without making changes in database.
I've tried different configuration on javax.persistance annotations, but none worked
You can use #ElementCollection and #CollectionTable to achieve that.
#Entity
public class Account {
#Id
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "account_games", joinColumns = #JoinColumn(name = "account_id"))
#Column(name = "game_id", nullable = false)
private Set<Long> gameIds = new HashSet<>();
}
You may have to change the query on how to filter data using gameId. Element Collection Query

Persisting a parent with existing children throws detached entity exception

I have this many-many relationship between students and courses as in following JPA models
#Entity(name = "course_result")
public class CourseResult {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
#ManyToOne(targetEntity = Student.class, cascade = CascadeType.ALL)
#JoinColumn(name = "student_id")
private Student student;
#ManyToOne(targetEntity = Course.class, cascade = CascadeType.ALL)
#JoinColumn(name = "course_id", referencedColumnName = "id")
private Course course;
private float grade;
}
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
private String courseName;
private String description;
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER)
private Set<CourseResult> courseResult;
}
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private BigInteger id;
private String studentName;
private String studentClass;
#OneToMany(mappedBy = "student", fetch = FetchType.EAGER)
private Set<CourseResult> courseResult;
}
When persisting a CourseResult, I want a new course/student would be created if it not yet exists otherwise they'll be updated only. That's why I set the cascade type to ALL. T
However, it always throws an exception when I try to persist a new CourseResult with existing Student and Course, say both with Id = 1
Student student = studentRepo.findById(BigInteger.ONE).get();
Course course = courseRepo.findById(BigInteger.ONE).get();
CourseResult courseResult = new CourseResult()
.setCourse(course)
.setStudent(student)
.setGrade(1.5F);
courseResultRepository.save(courseResult);
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: org.example.domain.Course; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: org.example.domain.Course
Do you have any idea why?
TIA,
There are a couple of approaches you could try
Is there a Transaction around your Code ? It is not shown in your example. Ensure that the import is using the spring Transaction.
Try adding the courseResult to the other enities
Student student = studentRepo.findById(BigInteger.ONE).get();
Course course = courseRepo.findById(BigInteger.ONE).get();
student.getCourseResult().add()
CourseResult courseResult = new CourseResult()
.setGrade(1.5F);
student.getCourseResult().add(courseResult);
course.getCourseResult().add(courseResult);
Try saving the CourseResult before adding it to the Objects. That way it might then be recognized by Hibernate as a managed entity
Student student = studentRepo.findById(BigInteger.ONE).get();
Course course = courseRepo.findById(BigInteger.ONE).get();
student.getCourseResult().add()
CourseResult courseResult = new CourseResult()
.setGrade(1.5F);
courseResultRepository.save(courseResult);
courseResult.setStudent(student);
courseResult.setCourse(course);
courseResultRepository.save(courseResult);
Try adding
cascade = CascadeType.ALL
to
#OneToMany(mappedBy = "course", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<CourseResult> courseResult;
and
#OneToMany(mappedBy = "student", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<CourseResult> courseResult;
Try using cascade= CascadeTyp.Mergeinsdead of cascade= CascadeTyp.ALL (PersistentObjectException: detached entity passed to persist thrown by JPA and Hibernate)

Inserting data in a ManyToMany relationship

I have two classes with ManyToMany relationship.
Student.java
#Entity
public class Student {
#Id
private Long id;
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "student_class", joinColumns = {#JoinColumn(name = "student_id")},
inverseJoinColumns = {#JoinColumn(name = "class_id")})
private List<Class> classList = new ArrayList<>();
}
Class.java
#Entity
public class Class {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToMany(mappedBy = "classList",cascade = {CascadeType.ALL})
private List<Student> students;
}
My question is how to implement a POST request in the controller? Do I add class to classList for a student and the student will automatically get inserted into students list?
The class that will actually be monitored by JPA for references is the one pointed with mappedBy .
This means that JPA during a save or update or delete will consider the mappings that the Student holds on classList. Therefore the cascade you have written here has no point.
#ManyToMany(mappedBy = "classList",cascade = {CascadeType.ALL})
private List<Student> students;
The only cascade that will actualy work is the one pointed with mappedBy
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "student_class", joinColumns = {#JoinColumn(name = "student_id")},
inverseJoinColumns = {#JoinColumn(name = "class_id")})
private List<Class> classList = new ArrayList<>();
So to your question
Do I add class to classList for a student and the student will
automatically get inserted into students list?
Yes it will be added to the list so you can see it, but it will not consider any changes that you make to that studentList. So if you make modifications directly on studentList then those modifications will not be persisted in your database.

What hibernate / jpa annotation is required

I am trying to create a new User(entity1) - it has reference to a Group (entity2) via a link table Member (entity3)
A user has a Set of groups as a class variable.
When i create my user object i want to say this user will be a member of group n (there are pre defined users that are linked to by id (1,2,3,4,5,6...) each group has some associated data in the table.
Whenever I create my user object as follows;
User user = new User();
user.setActive(1);
user.setCrby("me");
user.setUsername("username");
user.setCrdate("2016-06-20 12:42:53.610");
user.setCrwsref("...");
user.setModby("...");
user.setModdate("2016-06-20 12:42:53.610");
user.setModswref("..");
user.setBackground("Y");
user.setPassword("password");
user.setFullName("me");
Group group = new Group();
group.setId(1);
Group group2 = new Group();
group2.setId(2);
Set<Group> sets = new HashSet<Group>();
sets.add(group);
sets.add(group2);
user.setGroups(sets);
userDao.addUser(user);
I keep getting errors telling me that certain columns cannot be null. What I actually want to happen here is not to be doing an insert in to the group table but associating a user to a line in the group table. Is there a particular way I can prevent the columns in the group table being modified? I think I need to modify the mappings between the link table - this is how much pojos link right now
User
#Entity
#Table(name = "user")
public class User
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "username")}, inverseJoinColumns = {#JoinColumn(name = "id")})
private Set<Group> groups = new HashSet<Group>(0);
Member link table
#Entity
#Table(name = "member")
public class Member implements Serializable
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Id
#Column(name = "sgpid")
private int sgpid;
#Column(name = "username")
private String memberUsername;
Group
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
What is happening is there is no association to the link Member table so ideally should User have a set of member objects rather than a set of groups?
Thanks - this was quite hard to explain so sorry if it is hard to understand
This is a typical case for the #ManyToMany annotation. See for example:
https://dzone.com/tutorials/java/hibernate/hibernate-example/hibernate-mapping-many-to-many-using-annotations-1.html
The relationship from User to Group is essentially ManyToMany. You could model this is using the #ManyToMany annotation however one drawback with this approach is you cannot save additional information about the group in the join table such as 'date_joined'.
See: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#ManyToMany
Using this approach you would not need the Join entity Member and the relationship on User would look like:
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "zmember", joinColumns = {#JoinColumn(name = "member_id", referencedColumnName = "id")}, inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
private Set<Group> groups = new HashSet<Group>(0);
The alternative to using #ManyToMany is to use a Join entity Member(ship) as you have done. This would allow you to save additional data about the relationship (by defining additional field mappings in the Join entity).
In this case the mappings would look like:
User:
public class User{
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
//if required, you can 'hide' the join entity from client code by
//encapsulating add remove operations etc.
public void addToGroup(Group group){
Membership membershup = new Membership();
membership.setUser(this);
membership.setGroup(group);
memberships.add(membership);
)
public Set<Groupp> getGroups(){
//iterate memberships and build collection of groups
}
}
Membership:
public class Membership{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private Member member;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
}
Group:
#Entity
#Table(name = "group")
public class Group
{
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "group", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Membership> memberships = new HashSet<Membership>(0);
}

Specific deep level size to load association in hibernate

Recently I created a project and in my models I have ManyToMany back references.
My model is like below:
#Entity
public class A {
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
private Set<A> children = new HashSet<>();
#ManyToMany
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JoinTable(
name = "link_a_recursion",
joinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id")
)
private Set<A> parents = new HashSet<>();
//I removed the rest ( setter, getter and other fields )
}
When I fetch this model and I want to load children it throws StackOverFlowException error ( Recursive exception )
I want to know is there any way to say to hibernate just load one level of associate and don't go deep.
For clarify:
A.children[0].children[0].children should be null to stop recursion
I want to load the first children not all the children inside of the other children
Edited:
I add another entity:
#Entity
public class B {
#OneToMany(mappedBy = "b")
private Set<A> entities = new HashSet<>();
//setter, getter
}
and add below to A entity:
#ManyToOne
private B b;
then I changed below:
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
to
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
and in my BService my findOne function is like below:
#Transactional(readOnly = true)
public B findOne(Long id) {
B b = repository.findOne(id);
for (A a: b.getEntities()) {
a.getChildren().size();
}
return b;
}
but again I'm getting error :(
Try lazy fetch instead
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)

Categories