Assign existing child entity for parent entity JPA- GAE - java

I have 2 entity that have a relationship between them.
both of the entities has already created and inserted to the DB.
in some point the user has the ability to connect between them.
when im try doing it im getting:
javax.persistence.PersistenceException: Detected attempt to establish
WannaMeetUser("654321") as the parent of WannaMeetUser("123456") but
the entity identified by WannaMeetUser("123456") has already been
persisted without a parent. A parent cannot be established or changed
once an object has been persisted.
this is a transitive relation (user can have many friends from king of user):
the code is attached:
#Entity
public class WannaMeetUser {
#Id //signifies the primary key
#Column(name = "ID", nullable = false)
private Key id;
#ManyToMany
#Basic
private List<WannaMeetUser> userFriends = new ArrayList<WannaMeetUser>();
}
public void addFriendToWannaMeetUser(#Named("userId") String userId,
#Named("friendId") String friendId) {
EntityManager mgr = getEntityManager();
try
{
WannaMeetUser user = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", userId));
WannaMeetUser friend = mgr.find(WannaMeetUser.class, WannaMeetServerUtils.getKeyFromString("WannaMeetUser", friendId));
String coupleId = getcoupleId(userId.toString(), friendId.toString());
if (friend == null || user == null) {
throw new EntityNotFoundException("Object does not exist");
}
WannaMeetCouple couple=mgr.find(WannaMeetCouple.class, coupleId);
if (couple == null) {
couple = createCouple(userId.toString(), friendId.toString());
couple.setId(coupleId);
setUserJoined(couple, userId.toString(), friendId.toString(), true);
}
else {
if (isFriendAllready(couple, userId.toString(), friendId.toString()))
;
setUserJoined(couple, userId.toString(), friendId.toString(), false);
doAddFriend(user, friend, 10, 12321321);
mgr.persist(couple);
mgr.persist(friend);
mgr.persist(user);
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
mgr.close();
}
}
my question is What is the best way to crate such a relationship ?
Thanks

The messages says user 1 has already been persisted without having any parent, but now we try
to persist user 2 as a parent of user 1, but user 1 is known not to have a parent.
try to persist everything in one go using cascading instead. You can declare a cascading relation like this:
#ManyToMany(cascade=CascadeType.ALL)
private List<User> userFriends;
And then persist everything in one go using cascading:
User user1 = new User();
User user2 = new User();
user1.getUserFriends().add(user2);
// this persists the whole tree in one go
entityManager.persist(user1);

Parent-child entities should be used only for relationships that never change (e.g. a user and his photo). For dynamic relationships you have three options:
(A) Store references to other objects as a property (e.g. userFriends property in a User entity). If a relationship is unidirectional (e.g. User A likes User B), then you can update only one entity. For bidirectional relationships, you update both entities.
(B) Create a new entity type Relationship with two properties User A and User B. Create and delete these entities as necessary.
(C) When User A likes User B, create a new entity Relationship as a child entity of User A and use an id of User B as an id for this new Relationship entity.
The choice between these options depends on your data model and data access patterns.

Related

2 Objects dependent on each other. What now?

I did this simple Spring Security tutorial. https://www.boraji.com/spring-mvc-5-spring-security-5-hibernate-5-example
Now I want to create a user in my database.
The problem is that I need to create 2 objects dependent on each other and I get
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance beforeQuery flushing:
So What's the solution for this??
public static boolean createUser(byte[] image, String name, String username, String password, String permissions) {
String hashedPassword = new BCryptPasswordEncoder().encode(password);
//check if user already exists
boolean exists = User.checkIfUserExists(username);
//if it doesn't add to database
if(!exists) {
UserRole userRole = new UserRole();
User user = new User();
userRole.setRole(permissions);
userRole.setUser(user);
Database.addToDatabase(userRole);
//user table
user = new User(image, name, username, true, hashedPassword, userRole);
Database.addToDatabase(user);
//user role table
userRole.setUser(user);
Database.updateObject(userRole);
return true;
}
else
return false;
}
//Save the object to the database
public static void addToDatabase(Object object) {
SessionFactory factory = HibernateUtil.GetSessionFactory();
Session session = factory.openSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
session.save(object);
tx.commit();
}catch (HibernateException e) {
if (tx!=null) tx.rollback();
e.printStackTrace();
}finally {
session.close();
}
}
Basically UserRole already needs to be in db to save User and User needs to be in db to save UserRole.
That exception means exactly what is written. You have a relationship between two objects. For example UserRole has a linked User. But when you are trying to save the UserRole into the database the User object you have attached to it is not yet saved (it is in transient state) so Hibernate's problem is how to save that user role in the database when the user might not exist (it still doesn't have valid ID in the database ).
There are different ways you can deal with that. One of them is to mark the list as Cascade=ALL (or Cascade=SAVE_UPDATE). This way you will bind these two objects and when you save the user it will automatically save the user role. There are pros and cons with that. The "+" is that it is easier and you will have just one save. The "-" is that you have these objects connected and you might save/update an object by mistake if you are not careful.
Another solution would be to first save the User (without the roles). Then save the roles with the user set to the newly saved user (it should have a real ID in the database). Then add the roles to the user and update it. This way you will not save anything with transient values and you will go around that problem. Also do it in a transaction so you don't have bad data if something breaks.

Hibernate throws "org.hibernate.HibernateException: Found two representations of same collection" on entitymanager.flush()

I am currently working on a medium sized Java project with Hibernate and I have come across what seems to be a rare but quite persistent error. The situation is as follows: I have a Student entity who has a bidirectional many-to-many relation to an Education entity (implemented as a join table on the database) and an Admin entity who is a subclass of Student. My code allows for a Student to be "upgraded" to an Admin by removing the Student from the database, creating a new Admin based on the Student and persisting this Admin. However, whenever this happens, Hibernate throws the following error on EntityManager.flush():
org.hibernate.HibernateException: Found two representations of same collection: domain.Student.enrolledEducations
Below you can find the relevant code:
Education class
#Entity
public class Education {
...
#ManyToMany
#JoinColumn(name = "education_id")
private Set<Course> courses = new HashSet<>();
Student class
#Entity
public class Student {
....
#ManyToMany
#JoinColumn(name = "student_id")
private Set<Education> enrolledEducations = new HashSet<>();
Admin class
#Entity
public class Admin extends Student {
...
public Admin(Student student) {
this.setId(student.getId());
this.setFirstName(student.getFirstName());
this.setLastName(student.getLastName());
this.setEmail(student.getEmail());
this.setSalt(student.getSalt());
this.setSuperAdmin(false);
this.setEnrolledEducations(student.getEnrolledEducations());
this.setSessions(student.getSessions());
this.setManagedEducations(new HashSet<Education>());
}
Database methods
public Admin upgrade(Person person) {
Admin admin;
if (person instanceof Student){
removeStudent((Student) person);
admin = new Admin((Student) person);
}
else{
removePerson(person);
admin = new Admin(person);
}
addAdmin(admin); //exception happens here
return admin;
}
public void addAdmin(Admin admin) {
manager.getTransaction().begin();
if(manager.contains(admin)){
manager.merge(admin);
}
else{
manager.persist(admin);
}
manager.flush(); //exception happens here
manager.getTransaction().commit();
}
Test method
#Test
public void getEducationsForAdmin_and_upgrade_to_admin_work_correctly(){
educationSetup();
Admin admin1 = facade.upgrade(student1); //exception happens here
Admin admin2 = facade.upgrade(student2);
admin1.addNewEducation(education1);
admin1.addNewEducation(education2);
admin2.addNewEducation(education1);
facade.updateAdmin(admin1);
facade.updateAdmin(admin2);
Set<Education> educations1 = new HashSet<>(facade.getEducationsForStudent(admin1));
Set<Education> educations2 = new HashSet<>(facade.getEducationsForStudent(admin2));
assertTrue("admin 1 does not have exactly 1 education", educations1.size()==1);
assertTrue("admin 2 does not have exactly 2 educations", educations2.size()==2);
assertTrue("admin 1 does not have the IT education",educations1.contains(education1));
assertTrue("admin 2 does not have the IT education",educations2.contains(education1));
assertTrue("admin 2 does not have the M education",educations2.contains(education2));
}
It seems that you have a problem that both Admin and Student have the same identifier.
Since the Admin is created by calling the new function, it is not in the persistent state, the code
manager.contains(admin)
will always return false, so it will always go to the manager.persist statement.
Since Admin is a different object with the same identifier, you will get the exception
Found two representations of same collection
All you need to do is to add
manager.delete(person)
in your
removePerson
function. It should solve this problem.

Hibernate #OneToMany not saving null values inside my List

I am using postgresql database and I have the following tables: "user", "game" and "game_user" all inside the schema "allin". In my web application I'm using Hibernate 4.3.5, Spring MVC, and spring data jpa.
In my Game class I have the following OneToMany relationship:
#OneToMany(fetch=FetchType.EAGER)
#JoinTable(schema = "allin", name = "game_user",
joinColumns = { #JoinColumn(name = "game_id") },
inverseJoinColumns = { #JoinColumn(name = "user_id",nullable=true) })
private List<User> users;
And here is my unit test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { ServletInitializer.class, SecurityWebApplicationInitializer.class, WebMvcConfig.class})
#WebAppConfiguration
public class GameRepositoryTest
{
#Autowired
private GameService gameService;
#Autowired
UserService userService;
#Test
public void testRepostory()
{
List<User> users = new ArrayList<User>();
users.add(userService.findByUsername("luizcarlosfx"));
users.add(null);
users.add(null);
Game newGame = new Game(users, 10);
Game savedGame = gameService.save(newGame);
assertNotNull(savedGame);
Game getGame = gameService.findById(savedGame.getId());
assertEquals("There must be the same amount of users in the craeted game and in the saved game",newGame.getUsers().size(), getGame.getUsers().size());
}
}
When I run my unit test I get the following error: "There must be the same amount of users in the craeted game and in the saved game. expected:<3> but was:<1>."
The problem is that hibernate is not saving the null values that are inside the list of users, and I need to save my guest players as null, because they don't have any user. This part of the application must save games history of my multiplayer game.
My question is how to make hibernate save also the null values?
Using
inverseJoinColumns = { #JoinColumn(name = "user_id",nullable=true) })
would not allow you to save a Null-Object. You cannot save a Null (as object, but as value). Null is a not existing object. EVEN if you could manage to save a row filled with null-values - how would you assign this to ANY guest again?
Also this does not mean that the user_id can be null. Since foreign rows are referenced by id (as of your definition) allowing null for this would mean there could be only ONE entry for that.
Nullable = true in this case means: You can save a game, with NO upto MANY users. - But every of the MANY users needs to be different to null and have an id different to null.
Thus, if you call game.setUsers(null) - you can save. If you call game.getUsers().add(null) - you can't.

Delete a record on an OneToMany association with Hibernate

I am working on an application using Hibernate and I want to delete some records in the database. The relevant Entities are:
#Entity
public class Product {
private String serialNumber;
private Set<Part> parts = new HashSet<Part>();
#Id
public String getSerialNumber() { return serialNumber; }
void setSerialNumber(String sn) { serialNumber = sn; }
#OneToMany
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
...
}
#Entity
public class Part implements Serializable {
#Id
#GeneratedValue
private Long part_id;
private String userCode = "";
//getters and setters
....
}
I have let Eclipse implement equals and hashCode in Entity Part based on part_id and userCode. There is also an Entity Factory from which 'begin' all the associations to the other Entities. Therefore, in order to save all the changes it only necessary to execute the comand:
session.update(factory);
All the changes are saved successfully except from the delete from parts. I do:
products.getParts.remove(part);
The issues comig out are:
1) In some cases is part from the Set not removed although the comparison to a part in the Set with equals true returns (the part is in Set according to equals but it is not removed)
2) Even if the remove in the Set succeeds, the record in the database is not deleted.
Based on the above ascertainments what is the best way to remove the records in this case using not loads of queries?
You need to explicitly remove the child:
session.delete(part);
From Hibernate Docs:
The following code:
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();
will not remove c from the database. In this case, it will only remove
the link to p and cause a NOT NULL constraint violation. You need to
explicitly delete() the Child.
Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();
When using hibernate to map relationships you must be aware of two main concerns:
Which is the owner of the relationship? The owner is the side of the relation whose changes will be persisted in database. In your case the owner is the Part object.
Is a true parent/child relationship or simply a composition relationship? In your case I think the answer is composition
If you want to manage the relation using the set, you have two options:
use #ElementCollection instead of #OnetoMany
change ownership. Something like this:
#OneToMany
#JoinColumn(name="part_id")
public Set<Part> getParts() { return parts; }
void setParts(Set parts) { this.parts = parts; }
However, the second option is not recommended here. See section 2.2.5.3.1.2.

Easiest way to add Vehicle to User with JPA/Hibernate using Spring

Let's say I had this in my UserDAO class:
#OneToMany(cascade=CascadeType.ALL, mappedBy="owner", fetch=FetchType.EAGER, orphanRemoval=true)
private Set<Vehicle> vehicles = new HashSet<Vehicle>();
Is this the recommended way to add a new vehicle to a User:
User user = userService.findByLoginName("MartinL");
Vehicle newVehicle = new Vehicle();
newVehicle.set(...) // setters omitted
newVehicle.setOwner(user) // is this needed in any case?
user.getVehicles().add(newVehicle) // add the new vehicle to the Set in User class
userService.save(user); // persist the modified user object to database
Is this the best practice or do I miss on anything?
I prefer the direct approach of saving the newVehicle (after setting the owner attribute, of course).
Advantages:
1) Your approach will force the framework to check all the attributes of user and write them back at the DB, and will also write again all of the vehicles.
2) It is more readable, in the sense that it makes more clear that your intent is just to add a new vehicle
3) If you are doing this change, you may not even need to retrieve the user object from database, if you use EntityManager.getReference()
You normally want to manage bidirectional associations from the (JPA defined) dependent side of the relationship, and within the dependent class. Pseudocode:
class User {
private Set<Vehicle> vehicles;
public void addVehicle(Vehicle vehicle) {
if(vehicle == null) return;
vehicle.setOwner(this);
vehicles.add(vehicle);
}
public void removeVehicle(Vehicle vehicle) {
if(vehicle == null) return;
if(vehicles.remove(vehicle)) {
vehicle.setOwner(null);
}
}
}
Managing the relationship outside of the entities leads to bugs and duplicated code.

Categories

Resources