I'm encountering a problem when merging an existing parent entity with a new child entity.
Here's the scenario:
Create two new parent's and persist in db.
In parent's table we will have two new entries as follows
ID = 0, NAME = A
ID = 1, NAME = B
Using entitymanager.find() , fetch the parent with ID=0, and create a new child for the parent.
In the child's table, we will have the following entry
ID=0, NAME = CHILD of A, PARENT_ID = 0
Using entitymanager.find() , fetch the parent with ID=1, and create a new child for the parent.
After merging the parent object using entitymanager.merge(), the newly created child get's merge with the existing child with ID = 0.
The entries in child table, becomes ID=0 , CHILD OF PARENT B, PARENT_ID =0.
Why does this happen? Shouldn't it create a new entry for Child ?
The parent entity:
public class ParentEntity implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="PARENT_SEQ")
#SequenceGenerator(sequenceName="PARENT_SEQ",schema="MURTAZA YAHYA", name = "PARENT_SEQ", initialValue=-1)
//#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID",unique=true, nullable=false)
private Integer id;
#Column(name="NAME" , length=150)
private String name;
#OneToMany(mappedBy="parentEntity",cascade={CascadeType.MERGE,CascadeType.PERSIST})
List<ChildEntity> childEntities;
public Integer getId() {
if(id == null)
return null;
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<ChildEntity> getChildEntities() {
return childEntities;
}
public void setChildEntities(List<ChildEntity> childEntities) {
this.childEntities = childEntities;
}
}
The Child Entity
public class ChildEntity implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE,generator="CHILD_SEQ")
#SequenceGenerator(sequenceName="CHILD_SEQ",schema="MURTAZA YAHYA", name = "CHILD_SEQ" , initialValue=0)
//#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="ID",unique=true, nullable=false)
private Integer id;
#Column(name="NAME" , length=150)
private String name;
#ManyToOne
#JoinColumn(name="PARENT_ID")
private ParentEntity parentEntity;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ParentEntity getParentEntity() {
return parentEntity;
}
public void setParentEntity(ParentEntity parentEntity) {
this.parentEntity = parentEntity;
}
}
public String AddChildToAnExistingParent(Parent p) {
// TODO Auto-generated method stub
ParentEntity parent = em.find(ParentEntity.class, p.getId());
ChildEntity childEntity = new ChildEntity();
childEntity.setName("INISYA");
childEntity.setParentEntity(parent);
List<ChildEntity> list = new ArrayList<ChildEntity>();
list.add(childEntity);
parent.setChildEntities(list);
em.merge(parent);
return "Success";
}
Any help will be greatly appreciated. Thank you,
As the Parent->Child association is annotated with CascadeType.PERSIST the following code should resolved the described scenario.
public String AddChildToAnExistingParent(Parent p)
{ // TODO Auto-generated method stub
ParentEntity parent = em.find(ParentEntity.class, p.getId());
ChildEntity childEntity = new ChildEntity();
childEntity.setName("INISYA");
childEntity.setParentEntity(parent);
parent.getChildEntities().add (childEntity);
em.persist(parent);// the cascade.persist cause new childs persistence/insert
return "Success";
}
For more details in this answer explains the behavior of EntityManager method merge and persist.
Related
Please be so kind to help me understand why, by running attached code, I have multiple records been written in parent table from my data base (MySQL) with corresponding to a record in related table, although the set up in Java code is for one to one relation (i.e. one records in parent corresponding to only one record in other related table.
I am at your disposal for any questions you might have on the subject.
many thank for support
petrisor
public class Main {
public static void main(String[] args) {
createParkingLot();
AddEmployees(1);
AddEmployees(2);
}
public static void createParkingLot(){
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("java2curs4e1PU");
EntityManager em = emf.createEntityManager();
ParkingLot p1= new ParkingLot();
p1.setParking_lot_no(1);
try{
em.getTransaction().begin();
em.persist(p1);
em.getTransaction().commit();
} finally {
em.close();
emf.close();
}
}
public static void AddEmployees(int employee_number){
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("java2curs4e1PU");
EntityManager em = emf.createEntityManager();
ParkingLot p2 = em.find(ParkingLot.class,1);
Employee a1 = new Employee();
a1.setEmployee_name("Employee_name_#"+employee_number);
a1.setParking_lot(p2);
try{
em.getTransaction().begin();
em.persist(a1);
em.getTransaction().commit();
} finally {
em.close();
emf.close();
}
}
}
+++++++++++++++++++++++++++++
related entities description:
+++++++++++++++++++++++++++++
Entity Employee
+++++++++++++++++++++++++++++
#Entity
#Table(name = "employees")
public class Employee implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String employee_name;
#OneToOne(cascade = CascadeType.ALL,optional = false,fetch =
FetchType.LAZY)
#JoinColumn(name = "id_parking_lot")
private ParkingLot parking_lot;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getEmployee_name() {
return employee_name;
}
public void setEmployee_name(String employee_name) {
this.employee_name = employee_name;
}
public ParkingLot getParking_lot() {
return parking_lot;
}
public void setParking_lot(ParkingLot parking_lot) {
this.parking_lot = parking_lot;
}
#Override
public String toString() {
return "Employee{" + "id=" + id + ", name=" + employee_name + ",
parking lot=" + parking_lot + '}';
}
}
++++++++++++++++++++++++++++
Entity ParkingLot:
++++++++++++++++++++++++++++
#Entity
#Table(name = "parking_lots")
public class ParkingLot implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY )
private int id;
private int parking_lot_no;
#OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY,optional =
false,mappedBy = "parking_lot")
private Employee employee;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getParking_lot_no() {
return parking_lot_no;
}
public void setParking_lot_no(int parking_lot_no) {
this.parking_lot_no = parking_lot_no;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
#Override
public String toString() {
return "ParkingLot{" + "id=" + id + ", parking lot number=" +
parking_lot_no + '}';
}
}
Normally I would expect the program to through an exception as I tried to insert another Employee (Employee_name_#2) with the same parking lot (id 1).
Instead in table employees I found that I have 2 employees with same parking lot.
Need make a relationship between same table. Example: the object "category" have subcategories and the subcategorie have other subcategorie.
In MySQL make a column and point to primary key of same table, but, howto make in JPA?
My code is:
#Entity
#Table(name = "objects")
public class JObject {
private long id;
private String name;
private JObject parentJObject;
private Set<JObject> jObjects;
public JObject(){
}
public JObject(long id){
this.id = id;
}
public JObject(String name){
this.name = name;
}
public JObject(String name, JObject parentJObject){
this.name = name;
this.parentJObject = parentJObject;
}
#Null
#JoinColumn(name="parent_object_id", referencedColumnName="id")
#ManyToOne(cascade=CascadeType.ALL)
public JObject getParentJObject() {
return parentJObject;
}
public void setParentJObject(JObject parentJObject) {
this.parentJObject = parentJObject;
}
#Null
#OneToMany(mappedBy = "parentJObject", cascade = CascadeType.ALL)
public Set<JObject> getJObjects() {
return jObjects;
}
public void setJObjects(Set<JObject> jObjects) {
this.jObjects = jObjects;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#NotNull
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And the making objects:
JObject jObjectcategories = new JObject("Demo 1");
Set categoriesJObjects = new HashSet<JObject>(){{
add(new JObject("Demo 1.1", jObjectcategories));
}};
jObjectcategories.setJObjects(categoriesJObjects);
jObjectDao.save(new HashSet<JObject>() {{
add(jObjectcategories);
}});
But does not works. The log says:
List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='It has to be null', propertyPath=JObjects, rootBeanClass=class a.b.c.models.JObject, messageTemplate='{javax.validation.constraints.Null.message}'} ]
You need to be consistent in where you place your JPA annotations: either all on fields, or all on getters. But not mixed as you're doing.
The OneToOne should be a ManyToOne according to your description, since several objects share the same parent.
And the cascade ALL doesn't make sense: you don't want to delete a parent when a child is deleted.
I am getting a foreign key violation when I try to delete a record.
I have this record:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long parentId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "childId")
private Child child;
and
#Entity
#Table(name = "Child")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
public Long getOperatoryId() {
return id;
}
When I try to delete the child, I get a key violation because there are some parent records that point to the children. I thought I could delete the parent first, then go delete the children as:
parentRepository.delete(parent)
but I get an error that the property id doesn't exist on child. Is this because the child id is named childId and not id?
Here I worked on similar example what did you asked for. Customize it based on your need.
SQL Server table create query
CREATE TABLE "DBO"."Parent"(
Parent_Id INT PRIMARY KEY NOT NULL,
Parent_Name VARCHAR(255) NOT NULL)
CREATE TABLE "DBO"."Child"(
Child_Id INT PRIMARY KEY NOT NULL,
Child_Name VARCHAR(255) NOT NULL,
Parent_Ref_Id int not null,
CONSTRAINT FK_Parent_Ref_Id FOREIGN KEY (Parent_Ref_Id) REFERENCES Parent(Parent_Id)
)
Spring data JPA code
Parent Entity
#Entity(name = "Parent")
#Table(name = "Parent")
public class Parent {
#Id
#Column(name = "Parent_Id")
private long parentId;
#Column(name = "Parent_Name")
private String parentName;
//cascade = {CascadeType.REMOVE} OR orphanRemoval = true
#OneToOne(cascade = {CascadeType.ALL},orphanRemoval = true,fetch = FetchType.LAZY,mappedBy="parentInfo")
private Child childInfo;
public long getParentId() {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public Child getChildInfo() {
return childInfo;
}
public void setChildInfo(Child childInfo) {
this.childInfo = childInfo;
}
}
Child Entity
#Entity(name = "Child")
#Table(name = "Child")
public class Child {
#Id
#Column(name = "Child_Id")
private int childId;
#Column(name = "Child_Name")
private String childName;
#OneToOne
#JoinColumn(name = "Parent_Ref_Id", referencedColumnName = "Parent_Id")
private Parent parentInfo;
public int getChildId() {
return childId;
}
public void setChildId(int childId) {
this.childId = childId;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
public Parent getParentInfo() {
return parentInfo;
}
public void setParentInfo(Parent parentInfo) {
this.parentInfo = parentInfo;
}
}
Child Repo Code
public interface ChildRepo extends CrudRepository<Child,Long> {
}
Parent Repo Code
public interface ParentRepo extends CrudRepository<Parent,Long> {
Parent findByParentId(long id);
}
Controller Code
#Autowired
private final ParentRepo parentRepo;
....
private void save() {
//Save the parent
Parent p = new Parent();
p.setParentId(1);
p.setParentName("Parent1");
Child c = new Child();
c.setChildId(1);
c.setChildName("Child1");
c.setParentInfo(p);
p.setChildInfo(c);
parentRepo.save(p);
//delete the parent and child as well
Parent p1 = parentRepo.findByParentId(1);
parentRepo.delete(p1);
}
Let's say I have 2 JPA entites
#Entity
public class MyComplexEntity implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
#GeneratedValue(strategy = GenerationType.IDENTITY)
private MyComplexEntityId id;
private String text;
public MyComplexEntity() {
}
public MyComplexEntity(String text) {
this.text = text;
}
public MyComplexEntityId getId() {
return id;
}
public void setId(MyComplexEntityId id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
#Override
public String toString() {
return "MyComplexEntity{" + "id=" + id + ", text=" + text + '}';
}
}
and
#Embeddable
public class MyComplexEntityId {
#Column
private long id;
public MyComplexEntityId(long id) {
this.id = id;
}
public MyComplexEntityId() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
#Override
public String toString() {
return "MyComplexEntityId{" + "id=" + id + '}';
}
}
Now I'm trying to persist a new MyComplexEntity object like this
MyComplexEntity entity = new MyComplexEntity("something");
em.persist(entity);
System.out.println(entity);
and what I get is this:
MyComplexEntity{id=null, text=something}
Why is the id null there? If I do the same thing with a primitive primary key type, the id is correctly set after persisting. I've also tried the following things:
Calling em.flush(); after the persist: does nothing
Calling em.refresh(entity); after the persist: Exception telling me that the entity is no longer in the database (which is wrong)
Trying em.merge() instead of em.persist(): Same result, as expected
Calling em.contains(entity); returns true, so the entity is actually attached
Putting the em.persist(); in a separate transaction by executing it in a separate method using #TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
There has to be some kind of problem with persist(); when using an #EmbeddedId, but I just don't get it.
You cannot put GeneratedValue(strategy = GenerationType.IDENTITY) on a complex mapping. It needs to go within the embeddedId class, and you need to instantiate a MyComplexEntityId instance for your new Entity. You will also need to flush to the database to ensure that values are assigned as identity values are assigned by the insert.
My domain has a Category entity which has a biderectional relationship on itself. Each category can have a parent and children.
#Entity
public class Category implements DomainObject {
private Long id;
private Integer version;
private String name;
private Category parent;
private Set<Category> children;
#Override
#Id
#GeneratedValue
public final Long getId() {
return id;
}
#Version
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
public void setId(Long id) {
this.id = id;
}
#Column(unique=true, nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#ManyToOne
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
#OneToMany
#JoinColumn(name = "parent_id")
public Set<Category> getChildren() {
return children;
}
public void setChildren(Set<Category> children) {
this.children = children;
}
}
I have created the following query to fetch the "root" categories with their direct (level 1) children.
select distinct c from Category c left join fetch c.children where c.parent is null order by c.name
This actually works. My question is: why do I need the "JoinColumn" annotation on getChildren() to make this work and why can't I just make a "foin fetch" query, without "distinct"? If I remove "distinct" I get a multiplication. For each child of a parent, the entire parent is copied in the result set.
Is there a better way to do this? It just feels... a bit crappy.
In JPA you need to set distinct when you join a OneToMany, otherwise it will return duplicates.
This is required.
The JPA spec requires this, but it is an odd default, but relates to what happens in database joins.