How to prevent hibernate to save same object - sql

I have tables named Country, City and People. What can I prevent hibernate to save same people object? Here is my classes and the problem is when I try to save a country object, Hibernate tries to save or update same people objects as expected.(City's people object and Country's people object has same PK)
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session
Classes;
class Country{
.....
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private City city;
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private People people;
}
class City{
....
#JoinColumn(.....)
#ManyToOne(optional = false, fetch = FetchType.EAGER)
#Cascade(org.hibernate.annotations.CascadeType.SAVE_UPDATE)
private People people;
}
Here is my save method;
public void save(){
....
getCurrentSession().saveOrUpdate(customer); //Hibernate session
}

2 solutions here :
Use merge instead of save
or remove save cascade annotation.
Using both save and save cascade basically tells Hibernate "please persist this new object along with all his relationships which are also new". You don't want that.

Related

Fill JPA fill child array (one to many) with only certain children

I need to fill a Entity with the children entities and it works well when I need all children but now I only want to have a few ones.
For example, I have Owner 1-----n Pet
If I ownerRepositoty.findAll(); I get all owners with each one having an array of all he's pets. But let's say I want to get all the owners with the pet array having only the ones which name starts with N.
Example of the parent entity:
public class Owner {
#OneToMany(mappedBy="Owner")
private Set<Pet> pets; //This gets filled with all pets
//getters & setters
}
And the child entity:
public class Pet {
#ManyToOne
#JoinColumn(name="owner_id", nullable=false)
private Owner owner;
//getters & setters
}
I tried with a JOIN FETCH as explained here in the repository query but It just made a normal sql JOIN which is not what I'm looking for.
Any ideas?
I found the way to do this:
You can use the #Where annotation to filter the childs of a one to many or a many to many relationship.
public class Owner {
#OneToMany(mappedBy="Owner")
#Where(clause = "name like 'N%'")
private Set<Pet> pets; //This gets filled with pets with a name starting with N
//getters & setters
}

Hibernate: Problem with cascade delete for one to many relation

I have following entity model.
#Entity
#Table(name="product")
public class ProductEntity {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private UUID id;
...
#OneToMany(mappedBy = "productEntity", cascade = CascadeType.ALL)
private List<ProductAddonEntity> productAddonEntities;
}
#Entity
#Table(name="product_addon")
public class ProductAddonEntity {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
private UUID id;
...
#ManyToOne()
#JoinColumn(name = "addon_id")
private ProductEntity addonEntity;
}
I want to delete product, and that deletion should also delete all ProductAddon entities, connected with this product. So I declare one to many relation with all cascade types.
But when I try to delete some product, at the beginning Hibernate tries to set null addon_id in product_addon table. But this column have non-null constraint, so deletion fails.
So I added to annotation #ManyToOne parameters
#JoinColumn(name = "addon_id", nullable = false, updatable = false)
But now hibernate just tries to delete product, before deleting product_addon entities, connected with this product. And this deletion fails because of foreign key constraint (Cannot delete or update a parent row: a foreign key constraint fails).
What can be a problem here? This application also uses liquibase, so foreign keys generated not by hibernate. For example, foreign key for addon_id doesn't have actions on delete, but I'm thinking that hibernate does not need these actions, because it works on higher data layer
Orphan removal is aggressive remove cascading mode to remove child object whenever the parent object needs to be deleted(#OneToOne and #OneToMany relationships).
This feature added from JPA 2.0 version.JPA deletion operation
The difference between the two settings is in the response to disconnecting a relationship. For example, such as when setting the address field to null or to another Address object.
#Entity
class Employee {
#OneToOne(cascade=CascadeType.REMOVE)
private Address address;
}
If only cascade=CascadeType.REMOVE is specified no automatic action is taken since disconnecting a relationship is not a remove operation.
#Entity
class Employee {
#OneToOne(orphanRemoval=true)
private Address address;
}
If orphanRemoval=true is specified the disconnected Address instance is automatically removed. This is useful for cleaning up dependent objects (e.g. Address) that should not exist without a reference from an owner object (e.g. Employee).

Filter on relationship's field

I'm trying to fetch all entities for a given relationship's field match (I want my entity's relationships filled out in the result). Trying with Filter on session.loadAll() to filter on the relationship's field but I can't make it work.
My entities definition looks like:
#NodeEntity
class ClockAction {
#Id #GeneratedValue
private Long id;
private String description
private User user;
private Office office;
}
#NodeEntity
class User {
#Id #GeneratedValue
private Long id;
private String name;
private List<ClockAction> clockActions;
}
#NodeEntity
class Office {
#Id #GeneratedValue
private Long id;
private String name;
private List<ClockAction> clockActions;
}
From that I'm need to retrieve all ClockAction entities where User.id is in a given set of Ids.
Here is my try :
Filter filter = Filter("id", ComparisonOperator.IN, userIds);
filter.setNestedPropertyName("user");
filter.setNestedPropertyType(User.class);
filter.setNestedRelationshipEntity(true);
return session.loadAll(ClockAction.class, filter);
This always returns an empty result. Any idea of what I'm doing wrong?
Using a session.query like this
session.query(ClockAction.class, "MATCH p=(a:ClockAction)-[r]-() WHERE id(r) IN {ids} RETURN nodes(p), rels(p), a, r ORDER BY a.id", params)
works but only office field of ClockAction gets filled out on the result entity, user is always null...
Any help is appreciated :)
Some things first:
It is unfortunately currently not possible to filter for an id field because the filters only work with properties. Id fields are queried in cypher with the id function. (id(n) != n.id)
You are not looking for a relationship entity (remove filter.setNestedRelationshipEntity(true);)
Now you have the choices:
Query for another property of the User class with the filter.
Alter your cypher query with something like this: "MATCH p=(a:ClockAction)-[r]-(n) WHERE id(n) IN {ids} RETURN nodes(p), rels(p), a, r ORDER BY a.id" The changes are based on the assumption that the code snippets are correct and User is not a relationship.
Additional information (edit):
If no relationship is defined, Neo4j OGM will create them directed outgoing from the node you are saving. Your graph could look like this (ClockAction as root):
Or like this (User as root with multiple ClockActions):
You are not getting the Office because your current query path is (:User)-[r]-(:ClockAction) there is no information in the path about an Office.
MATCH (n:User)-[ur:CLOCK_ACTIONS]->(c:ClockAction)-[or:OFFICE]->(o:Office) WHERE id(n) IN {ids} RETURN c, n, ur, o, or is a pretty straight forward query you could use. It removes the path centric style but also loads all the data you need.
If the graph was stored through the User but this is just an example and can be applied however the data looks in your graph, you won't see any User information on the ClockActions because as it saves without any hint Neo4j OGM will also expect the data related in a outgoing direction from the class you want to load.
Now it is necessary, keeping the User example, to add a #Relationship(type="CLOCK_ACTION", direction = "INCOMING") to the user field in your ClockAction class.
This will give Neo4j OGM the needed hint to put the User data it has in your user field.
I ended up following advises from #meistermeier and annotate my relationships giving direction.
Below is my model entities :
#NodeEntity
class ClockAction {
#Id #GeneratedValue
private Long id;
private String description
#Relationship(direction = Relationship.OUTGOING)
private User user;
#Relationship(direction = Relationship.OUTGOING)
private Office office;
}
#NodeEntity
class User {
#Id #GeneratedValue
private Long id;
private String name;
#Relationship(direction = Relationship.INCOMING)
private List<ClockAction> clockActions;
}
#NodeEntity
class Office {
#Id #GeneratedValue
private Long id;
private String name;
#Relationship(direction = Relationship.INCOMING)
private List<ClockAction> clockActions;
}
What #meistermeier suggested for query did not work for me, but gave me inspiration and I found this working fine :
MATCH p((u:User)-[ur]-(c:ClockAction)-[or]-()) WHERE id(u) IN {ids} RETURN p, rels(p)

How to fetch data from two tables(one-to-many relations) using Hibernate Createcriteria

How to fetch data from two tables based upon User id ?
*****************Role Class***********************
#Entity
#Table(name = "IUC_CON_USER_ROLE_MAP")
public class Role {
#Id
#Column(name="F_ROLE_MAP_ID")
int rolemap;
#Column(name="F_ROLE_ID")
int roleid;
#OneToMany(mappedBy="role",fetch=FetchType.EAGER)
Set<User> F_USER_ID;
*********************User class*******************
#Entity
#Table(name = "IUC_CON_USER")
public class User implements Serializable {
#Id
#Column(name = "F_USER_ID")
private int id;
#Column(name = "F_USER_NAME")
private String name;
#Column(name = "F_USER_PWD")
private String pwd;
#ManyToOne
#JoinColumn(name="F_USER_ID",insertable=false,updatable=false)
private Role role;
----------setter and getter for properties
}
External edit:
Criteria creation code:
DetachedCriteria uCrit = DetachedCriteria.forClass(User.class, "user");
uCrit.add(Restrictions.eq("user.id", 5));
uCrit.setProjection(Projections.property("user.id"));
DetachedCriteria criteria = DetachedCriteria.forClass(Role.class, "role");
criteria.add(Property.forName("role.F_USER_ID").in(uCrit));
List lt1 = criteria.getExecutableCriteria(getSession()).list();
use createAlias
criteria.createAlias("propertiy_of_main_entity", "aliastName");
For me this fetches entities asociated with root entity via some entity property name;
in your case something like
createAlias("F_USER_ID", "roleUsers");
should do.
Anyway why ins't your field name following commong naming doctrine for Java? WHY_IS_IT_UPPERCASED_WITH_DASHES_LIKE_CONSTANTS_?
All in all everything is explained in Hibernate documentation. If alias won't work, than fetch associations like in examples from Hibernate documentation under link I provided.
You can use below series of statements to fetch User along with the Role:
int userId = 1;
Criteria criteria = session.createCriteria(User.class);
criteria.setFetchMode("role", FetchMode.JOIN);
criteria.add(Restrictions.eq("id", userId));
User user = (User) criteria.uniqueResult();
The default fetching strategy of Hibernate is to fetch the associations lazily, which you need to override at runtime in code, if you want the associations (here the Role) to be fetched along with User. The third statement is doing this overriding by seting the fetch mode to JOIN. This statement FetchMode.JOIN overrides the default behavior, so that Role will be fetched along with the User.

Hibernate: Adding a new element to association list does not persist

I have a ManyToMany association between two Entities: COURSE and STUDENT. They are associated through a STUDENT_COURSE_MAP association table.
Course.java:
#Entity
#Table(name="COURSE")
public class Course implements java.io.Serializable {
#ManyToMany(
mappedBy="courses",
cascade={CascadeType.PERSIST, CascadeType.MERGE}
)
private List<Student> students = Collections.emptyList();
// Rest of the class, getters, setters etc
}
Student.java:
#Entity
#Table(name="STUDENT")
public class Student implements java.io.Serializable {
#ManyToMany(
targetEntity=Course.class,
cascade = { CascadeType.PERSIST, CascadeType.MERGE }
)
#JoinTable(
name="STUDENT_COURSE_MAP",
joinColumns=#JoinColumn(name="STUDENT_REF"),
inverseJoinColumns=#JoinColumn(name="COURSE_REF")
)
private Set<Course> courses = Collections.emptySet();
// Rest of the class, getters, setters etc
}
I want to add a student (id=11) list for one particular course (id=77) as follows (transaction is already begun):
// Create new list and add a student to it
Student student_11 = (Student) session.get(Student.class.getName(), 11);
List<Student> studentsList = new ArrayList<Student>(1);
studentsList.add(student_11);
// Now, set the list of students in the course
Course course_77 = (Course) session.get(Course.class.getName(), 77);
course_77.setStudents(studentsList);
I save it using the following code:
session.flush();
session.getTransaction().commit();
However, the association - course_77 and student_11 - does not get persisted to the database. There are no insert statements generated in the hibernate log.
I even tried calling session.saveOrUpdate(course_77). Only the following is logged when saveOrUpdate is called:
allowing proxied method [saveOrUpdate] to proceed to real session
persistent instance of: org.whatra.tradecog.db.entity.Artist
ignoring persistent instance
object already associated with session: [Course#77]
Hints/tips/solutions are much appreciated.
Note 1:
The whole persistence thing works just fine for me when I create new Course and Student objects and save. What does not work is when I retrieve existing Course and existing Student objects and add a new association between them.
Here's what was wrong. It turns out I was saving only one side of the association. This is what I should have done:
// Create new list and add a student to it
Student student_11 = (Student)session.get(Student.class.getName(), 11);
Course course_77 = (Course) session.get(Course.class.getName(), 77);
List studentsList = new ArrayList(1);
studentsList.add(student_11);
Set coursesList = new HashSet(1);
coursesList.add(course_77);
course_77.setStudents(studentsList);
// THIS IS WHAT'S WRONG - I MISSED THIS NEXT LINE
student_11.setCourses(coursesList);
I found the answer at http://en.wikibooks.org/wiki/Java_Persistence/Relationships (look under 'Common Problems').