I've encountered a strange problem in a simple Spring Boot application, where I update a parent entity, delete all of its children and then create new ones.
After saving, the resulting entity looks fine and has all of the new children, but when I query it again, I find that one of the child entities is lost!
The quick and dirty solution for this would be to split the code into two transactions, but I want to understand the cause the orphan removal to act like this.
Here's the service code:
#Service
public class ParentService {
private final EntityManager em;
public ParentEntity getParent(UUID parentId) {
return em.createQuery(
"SELECT p " +
"from ParentEntity p " +
"JOIN FETCH p.children " +
"WHERE p.id = :parentId", ParentEntity.class)
.setParameter("parentId", parentId)
.getSingleResult();
}
#Transactional
public ParentEntity resetChildren(UUID parentId) {
var parent = getParent(parentId);
parent.getChildren().clear();
addChildren(parent, 2);
em.persist(parent);
return parent;
}
private void addChildren(ParentEntity parent, int childCount) {
for (var i = 0; i < childCount; i++) {
parent.addChildren(new ChildEntity());
}
}
}
The SQL output after resetting children and the fetching the parent again is this:
select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
insert into child (parent_id, id) values (?, ?)
insert into child (parent_id, id) values (?, ?)
delete from child where id=?
delete from child where id=?
delete from child where id=? <-- One extra delete
select parententi0_.id as id1_1_0_, children1_.parent_id as parent_i2_0_1_, children1_.id as id1_0_1_, children1_.id as id1_0_2_, children1_.parent_id as parent_i2_0_2_ from parent parententi0_ left outer join child children1_ on parententi0_.id=children1_.parent_id where parententi0_.id=?
The entities look like this:
The parent
#Entity
#Table(name = "parent")
public class ParentEntity {
#Id
#GeneratedValue
private UUID id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<ChildEntity> children = new HashSet<>();
...
public void addChildren(ChildEntity child) {
this.children.add(child);
child.setParent(this);
}
...
}
And the child
#Entity
#Table(name = "child")
public class ChildEntity {
#Id
#GeneratedValue
private UUID id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
An important note to this, that in my use case, the id of these entities is a UUID. I can't get it working with any numeric ids
The code repository with a unit test can be found here
An interesting thing happens, if I decide to add 1 child, instead of two or more, the parent itself is deleted! Because of this, it feels like I'm looking at a bug in Hibernate.
The problem was that I had cascade on the child side, which lead to very very weird results. I don't want parent to be deleted on cascade, so I don't need that.
The fix was to change the child to this:
#Entity
#Table(name = "child")
public class ChildEntity {
#Id
#GeneratedValue
private UUID id;
#ManyToOne // <---- No more cascade!
#JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
Related
I have a Parent and Child entities like below -
class Parent {
Long id;
List<Child> children;
}
class Child {
Long id;
String status; // ACTIVE or INACTIVE
Parent parent;
}
I would like to fetch the parent along with all its children having status=ACTIVE property.
public interface ParentRepository<Parent, Long> {
Parent findByIdAndChildStatus(#Param("id") id, #Param("childStatus") status);
}
Question is -
What is the easiest way to fetch only ACTIVE children?
Is it possible to set ACTIVE query condition on the Child entity's
status property to fetch only active child entities by default?
Will something like above query method work?
See below
It's not possible with Spring annotations alone, but you can achieve it with Hibernate's #Where:
#Entity
#Getter
#Setter
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Where(clause = "status='ACTIVE'")
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children;
}
Your query method would not work, Spring Data wouldn't understand the method name. To make it work, the Child entity would need to be in the plural form:
public interface ParentRepository extends CrudRepository<Parent, Long> {
Parent findByIdAndChildrenStatus(Long id, String status);
}
Unfortunately, the meaning of the method would be a bit different than expected. It would mean: get Parent with the id id and having a Child with the status status. The generated query looks as follows:
SELECT parent0_.id AS id1_1_
FROM parent parent0_
LEFT OUTER JOIN child children1_ ON parent0_.id=children1_.parent_id
WHERE parent0_.id=? AND children1_.status=?
Here is my model:
#Entity
#Table(name = "parent")
public class Parent {
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, cascade = { CascadeType.REMOVE}, mappedBy = "parent")
#MapKeyColumn(name = "key")
private Map<String, Child> children;
...
}
#Entity
#Table(name = "child")
public class Child {
...
#Column(name = "key")
private String key;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
#Column(name = "property")
private String property;
...
}
That mapping is usefull and works on inserting, updating, deleting entites and selecting them.
Now I have to order Parent entites using their children property depending on map key, a something like order by parent.children[<particular key>].property asc clause(which I did not find in HQL). Also I have no idea how to achieve this using Criteria API. Any idea?
Only solution I have now is to select Children filtered by key and ordered by property and then fetch parents in Java code one by one which is not efficient even with "fetch parent" set.
The table created statements come out like this:
create table child (id bigint not null, key varchar(255), property varchar(255), parent_id bigint, primary key (id))
create table parent (id bigint not null, primary key (id))
with a constraint on parent_id to the parent table.
Working backwards, the sql seems pretty straight forward:
select p.* from parent p join child c on p.id = c.parent_id where c.key = 'c1' order by c.property asc;
And so the JPQL and Criteria query should also be straight forward:
List<Parent> po1 = em.createQuery("select p from Parent p join p.children c where c.key='c1' order by c.property asc", Parent.class).getResultList();
System.out.println(po1);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Parent> cq = cb.createQuery(Parent.class);
Root<Parent> p = cq.from(Parent.class);
Join<Parent, Child> c = p.join("children");
cq.select(p).where(cb.equal(c.get("key"), "c1")).orderBy(cb.asc(c.get("property")));
List<Parent> po3 = em.createQuery(cq).getResultList();
System.out.println(po3);
Create a toString method for Parent:
public String toString() {
return "Parent:"+id+":"+children;
}
and Child:
public String toString() {
return "Child:"+id+":"+key+":"+property;
}
And that gives me the following output:
Hibernate: select parent0_.id as id1_1_ from parent parent0_ inner join child children1_ on parent0_.id=children1_.parent_id where children1_.key='c1' order by children1_.property asc
[Parent:4:{c1=Child:5:c1:0}, Parent:1:{c1=Child:2:c1:1}]
Hibernate: select parent0_.id as id1_1_ from parent parent0_ inner join child children1_ on parent0_.id=children1_.parent_id where children1_.key=? order by children1_.property asc
[Parent:4:{c1=Child:5:c1:0}, Parent:1:{c1=Child:2:c1:1}]
Which looks to me to be the parents ordered by the child property where the key is a certain value.
I have a child entity and 2 parent entities, each parent entity has a list of the child entities. If I create the list of child entities for Parent_2 by first getting the children of Parent_1 and then assigning that list to Parent_2 hibernate deletes the "Parent_1-Child" join table records as per the following code.
Parent_1:
#Entity
#Table
public class Parent_1 extends BusinessObject {
List<Child> children = new ArrayList<Child>();
public Parent_1() {}
public Parent_1(List<Child> children) {
this.children = children;
}
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "parent1_children", joinColumns = #JoinColumn(name = "parent1_id"), inverseJoinColumns = #JoinColumn(name = "child_id"))
#OrderColumn(name = "child_order")
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
}
Parent_2:
#Entity
#Table
public class Parent_2 extends BusinessObject {
List<Child> children = new ArrayList<Child>();
public Parent_2() {}
public Parent_2(List<Child> children) {
this.children = children;
}
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "parent2_children", joinColumns = #JoinColumn(name = "parent2_id"), inverseJoinColumns = #JoinColumn(name = "child_id"))
#OrderColumn(name = "child_order")
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
}
Child:
#Entity
#Table
public class Child extends BusinessObject {
public Child() {}
}
And the code I run to experience the error:
#Test
public void demoTest() {
Child child_1 = new Child();
Child child_2 = new Child();
save(child_1, child_2);
List<Child> children = new ArrayList<Child>();
children.add(child_1);
children.add(child_2);
Parent_1 parent_1 = new Parent_1(children);
Parent_1 parent_1_1 = new Parent_1(children);
save(parent_1, parent_1_1);
Parent_1 existingParent = dataAccessService.getParentById(new Long(1));
List<Child> newChildren = existingParent.getChildren();
Parent_2 parent_2 = new Parent_2(newChildren);
save(parent_2);
}
This is the SQL being executed for the last save:
Hibernate:
insert
into
Parent_2
(created, deleted, hidden, lastModified, id)
values
(?, ?, ?, ?, ?)
Hibernate:
delete
from
parent1_children
where
parent1_id=?
Hibernate:
insert
into
parent2_children
(parent2_id, child_order, child_id)
values
(?, ?, ?)
Hibernate:
insert
into
parent2_children
(parent2_id, child_order, child_id)
values
(?, ?, ?)
The problem is with the delete statement. Why is hibernate executing this and what should I do to stop it from happening?
Rgds,
Phil
Note: BusinessObject just proves the id for each table as per:
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
public Long getId(){
return id;
}
The best answer I can give you is that it seems to be working OK as JPA on hibernate. I recreated your classes. I put the annotations on the fields instead of the methods, but I don't think that makes any difference. I ran it as JPA on Wildfly 9.0.2.Final. I see that you're using the Spring persistence API. I didn't try to reproduce that, but I assume your problem is reproducible.
Is there a way to get partial object tree on child objects with parent child structure so I can achive:
select distinct * from kb_event t0 inner join kb_event t1 on t1.parent_id = t0.id where t1.status = -1;
My eban query
Ebean.getServer("default").find(EventModel.class)
.select("children.id, name, status").fetch("children")
.where()
.eq("children.status", -1)
.findList();
Returns parent element but with all children listed under children node.
My model looks like:
#Entity
#Table(name = "kb_event")
public class EventModel extends Model implements Bean {
#Id
#JsonProperty("eventId")
Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
public List<EventModel> children;
#ManyToOne
public EventModel parent;
}
with proper foreign key on parent Id
I have an object with several #onetomany relationships, and I need to query for properties in the parent, as well as properties of the children. I can't seem to get it done.
For example, I need a query that lets me see the Parent objects where where the parent's name is "John" and the child's favorite color is blue. Hope that makes sense. The reason for the complication seems to be that children are in a list, not in a #onetoone relationship.
PARENT:
#Entity
#Table(name="Parent")
public class Parent {
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO, generator="parent_gen")
#SequenceGenerator(name="parent_gen", sequenceName="PARENT_SEQUENCE")
private int parentID;
#Column(name="name")
private String name;
#OneToMany(cascade=CascadeType.ALL)
#OrderBy("name ASC")
#JoinTable(name = "parent_to_child")
private List<Child> childList;
// and so forth
Child
#Entity
#Table(name="Child")
public class Child{
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.AUTO, generator="child_gen")
#SequenceGenerator(name="child_gen", sequenceName="CHILD_SEQUENCE")
private int childID;
#Column(name="favoriteColor")
private String favoriteColor;
// and so forth
select p from Parent p join p.childList c
where p.name = 'John' and c.favoriteColor = 'blue'
This will return a List<Parent>.
You can look all this in the hql reference
Try something as follows:
from Parent as parent
left join parent.childList as children
with children.favoriteColor = 'blue'
where parent.name = 'John'
you need to do--- parent "left join fetch" child with your condition.
"left join fetch" gives result in List< Parent>
Without Fetch it will be List where object[0] = parent and object[1] = child.
public class Clients implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(cascade = { CascadeType.ALL},orphanRemoval=true)
#JoinColumn(name="client_id")
List<SmsNumbers> smsNumbers;
}
#Table(name="smsnumbers")
public class SmsNumbers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
int id;
String number; //getter and setter
}
On the basis of child class i fetch the parent in unidirectional relation using the following criteria-
Session session = HibernateUtil.openSession();
try{
Criteria criteria=session.createCriteria(Clients.class);
criteria.createAlias("smsNumbers", "child");
criteria.add(Restrictions.eq("child.number", phone).ignoreCase());
Clients cli=(Clients) criteria.list().get(0);
System.out.println(cli.getId());
}catch (Exception e) {
// TODO: handle exception
}
JPQL provides a special syntax that, in these cases, makes things easier and helps you to think in an Oriented Object way:
SELECT p FROM Parent P, IN (P.childList) C
WHERE P.name='John' and C.favoriteColor='blue';
The operator IN iterates over lists, thus avoiding the need of using JOINs.
Criteria criteria=session.createCriteria(Parent.class);
criteria.add(Restrictions.eq("name", "John"));
criteria.createAlias("childList", "child");
criteria.add(Restrictions.eq("child.favoriteColor", "Blue").ignoreCase());
You can try with criteria API also.