JPA2 Criteria queries on entity hierarchy - jpa-2.0

suppose i have the following entity domain:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
public abstract class Entity1 {
//some attributes
}
#Entity
#DiscriminatorValue("T1")
public class Entity2 extends Entity1 {
#OneToMany(fetch=FetchType.EAGER, cascade = { CascadeType.ALL }, mappedBy="parent")
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<Entity1Detail> details = new HashSet<Entity1Detail>();
}
#Entity
public class Entity1Detail {
#ManyToOne
#JoinColumn(name="REF")
private Entity2 parent;
#Basic
private Integer quantity;
}
#Entity
#DiscriminatorValue("T2")
public class Entity3 extends Entity1 {
//some other attributes
}
when i do a JPQL query:
select e from Entity1 e left join e.details d where d.quantity > 1
it runs well (left join ;P). however when i try to construct the same query using JPA2 Criteria API:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity1.class);
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
i get NPE in "join" because the attribute "details" doesn't belong to Entity1 (which is actually true, i have to select on Entity2.class instead). the thing is that when i have to construct my dynamic query using Criteria API i don't really know anything about hierarchy, i'm just passed a Class.
i understand that Criteria API is typesafe and all that, but is there a way to work around this? with aliases maybe (as before i used Hibernate Criteria API, traversing joins with aliases):
Criteria c = session.createCriteria(Entity1.class);
c.createAlias("details", "d");
c.add(Restrictions.ge("d.quantity", 1));

You need to base your query on entity2.details. Since the criteria API is type-safe, it catches that entity1 has no field named "details"
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery q = builder.createQuery();
Root r = q.from(Entity2.class); // Must use subclass as root
q.select(r);
q.where(builder.gt(r.join("details", JoinType.LEFT).get("quantity"), 1));
Since Entity2 extends Entity1, you can cast your results as the parent type safely. For example:
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity1
CriteriaQuery<Entity2> q = builder.createQuery(Entity2.class);
Root r = q.from(Entity2.class); // Must use subclass as root
will return a list of Entity2
EDIT:
I think I misunderstood the goal here. If you want all Entity1 UNLESS they are Entity2 with details.quantity <= 1, you need to do more.
You can't use a left join from Entity1Detail to Entity1, because that is not strictly type safe. Instead, you need to join Entity2 to Entity1Detail somehow. Probably the best tool to use here is a correlated subquery.
CriteriaQuery<Entity1> q = builder.createQuery(Entity1.class);
Root<Entity1> ent1 = q.from(Entity1.class);
SubQuery<Entity2> subq = q.subquery(Entity2.class);
Root<Entity2> ent2 = subq.from(Entity2.class);
Path<Integer> quantity = ent2.join("details", JoinType.LEFT).get("quantity");
Predicate lessThan = builder.lte(quantity,1);
Predicate correlatedSubqJoin = cb.equal(ent1,ent2)
subq.where(lessThan, correlatedSubqJoin);
q.select(ent1);
q.where(builder.exists(subq).not());
The criteria API does not know that you are single table inheritance, so you have to write your queries for all inheritance strategies, including a Joined inheritance strategy.

Related

How to determine the discriminator column value from within an entity with ZF2 and Doctrine

I would like to use Doctrine’s preflush features to automatically set the value of form elements based on the values of other elements. The preflush statements in my ZF2 entity might look like this:
/**
* set eventEndDate = eventStartDate for single-day events on pre flush.
*
* #ORM\PreFlush
* #return void
*/
public function onPreFlush(PreFlushEventArgs $args)
{
$currentEventType = $this->getEventType();
if ($currentEventType=='meeting') {
$this->eventEndDate = $this->getEventStartDate();
}
}
My challenge is that I don’t have a getEventType() getter because eventType is a discriminator column in my inheritance mapping. How can a preflush function in an entity evaluate a discriminator value from within the entity?
You can use instanceof php operator to check object's class. Like that:
if ($this instanceof MeetingEntityClass) {
//...
}

how to create query in Criteria api

I have a class:
#Entity
public class Resume {
private Long id;
#Embedded
private DesiredPositionAndSalary desiredPositionAndSalary;
}
and class:
#Embeddable
public class DesiredPositionAndSalary {
#ManyToMany
private Set<Specialization> specializations;
}
and class ;)
#Entity
public class Specialization {
private Long id;
}
now i have some Specializations that i need filtered by.
For example i need to select all resume with one of specialization like programmer or manager. Something like
select * from resume r inner join resume_to_specialization rts on r.id = rts.id inner join specialization s on rts.spec_id in(1,2)
how can i write this query in Criteria api? If i miss some major details i can give more.
Ok, i handle it with this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Resume> cq =cb.createQuery(Resume.class);
Root<Resume> root = cq.from(Resume.class);
cq.select(root);
Set<Specialization> filter = getFilter();
SetJoin<DesiredPositionAndSalary, Specialization> join = root.join(Resume_.desiredPositionAndSalary, JoinType.INNER).join(
DesiredPositionAndSalary_.specializations, JoinType.INNER);
cq.where(cb.and(cq.getRestriction(), join.in(filter)));
cq.distinct(true);/*it is major, or we get duplicate of resume for every
specialization overlap with filter*/
List<Resume> result = em.createQuery(cq).getResultList();
.

JPA OneToMany - List doesn't update after add/delete object, but is persisted in DB

I'm using JPA not Hibernate. I have a Problem with my Relationship as follows. I have a 1:N Relationship (Parent:Child).
On a JSP-Site, the Parent and the Children are displayed.
If I load the JSP-Site (for the first time) and save a Parent with a Child, all is ok. The Parent is saved and the Child is saved too to the DB with the foreign key.
After that, a second JSP-Site is displayed with the Parent and Children.
But if I go back to the first Site and save a second Child (adding Child) to existing Parent, the childEntityList is still the old one, I mean, the new Child is not inside this List, but inserted in the database.
Why the childEntityList is old and doesn't update?
The Parent:
#Entity
#Table(schema = "test", name = "Parent")
#XmlRootElement
#NamedQueries({...})
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "A_SEQ")
#Basic(optional = false)
#NotNull
#Column(name = "ID")
private int id;
#OneToMany(mappedBy = "p", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<ChildEntity> childEntityList = new ArrayList<ChildEntity>();
// ...
public List<ChildEntity> getChildEntityList() {
return childEntityList;
}
public void setChildEntityList(List<ChildEntity> childEntityList) {
this.childEntityList = childEntityList;
}
public void addChildEntityToParent(ChildEntity c) {
this.childEntityList.add(c);
if(c.getParent() != this) {
c.setParent(this);
}
}
public void removeChildEntityFromParent(ChildEntity c) {
childEntityList.remove(c);
}
// ...
}
The Child:
#Entity
#Table( name = "Child" )
#NamedQueries({...})
public class ChildEntity implements Serializable {
#Id
#GeneratedValue( strategy = GenerationType.IDENTITY )
#Column( name = "CHILD_ID" )
private Long childId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID")
private Parent p;
// ...
public Parent getParent() {
return p;
}
public void setParent(Parent p) {
this.p= p;
if(!p.getChildEntityList().contains(this)) {
p.getChildEntityList().add(this);
}
}
Save-Code:
Parent p = new Parent(...);
ChildEntity c = new ChildEntity(...);
p.addChildEntityToParent(c);
childEntityFacade.create(c);
View-Code:
// ...
public doSomething(Parent p) {
super(p);
}
// ...
List<ChildEntity> cList = p.getChildEntityList();
System.out.print("Size: " + c.size());
// ...
In the View-Code, the size of the childEntityList is every time the same even I add a Child.
Only when I restart the server, all Children of the Parent are displayed.
When is the childEntityList in the Parent Class filled?
Is it automatically, because of the #OneToMany?
Can someone explain it?
Update:
Should I merge the EntityManager every time when I add/remove a Child object from the Parent list or have I do some other things?
You need to maintain both sides of a bi-directional relationship. See here.
(I'm using JPA not Hibernate!)
I guess you meant that you are not using the Hibernate API.
JPA is only a persistence API specification and, by itself, does nothing.
So, if you're using JPA... you're using something to do the persistence.
JPA delegates the persistence work to a persistence engine such as Hibernate, EclipseLink, OpenJPA or something else.
You can configure your app to use JPA interfaces with a specific persistence provider, or if the app is running in a JEE container, then the JPA implementation should be provided for you: GlassFish (uses OpenJPA), WebLogic (uses EclipseLink), TomEE (uses OpenJPA).
Should i merge the EntityManager every time when i add/remove a Child
Object from the Parent List...
You don't merge the EntityManager; you use it to merge the parent entity:
parent = em.merge(parent);
Relating to your question, when you use merge() in this way... (and because the mapping specifies CascadeType.ALL) then upon reloading page ("Site 1") you should get the refreshed parent object with an updated object network containing changes you made prior to merge(parent), including any newly added children.
Yes, you need to merge parent entity. I was having same issue and it got resolved once I merged parent entity.
Also you need to maintain both sides of the relationship.

Deep clone Doctrine entity with related entities

I have created an entity A with OneToMany relation to B, which have relation OneToMany to C.
I have to clone this A entity and set it in database with new id. Also all deep relations should be cloned with new ids too.
What have I tried is to set A id to null:
$A = clone $A_original;
$A->setId(null);
$em->persist($A);
It creates new record in A table, but does not in B and C.
What should I do to make a full copy of A entity ?
You have to implement a __clone() method in your entities that sets the id to null and clones the relations if desired. Because if you keep the id in the related object it assumes that your new entity A has a relation to the existing entities B and C.
Clone-method for A:
public function __clone() {
if ($this->id) {
$this->setId(null);
$this->B = clone $this->B;
$this->C = clone $this->C;
}
}
Clone-method for B and C:
public function __clone() {
if ($this->id) {
$this->setId(null);
}
}
https://groups.google.com/forum/?fromgroups=#!topic/doctrine-user/Nu2rayrDkgQ
https://doctrine-orm.readthedocs.org/en/latest/cookbook/implementing-wakeup-or-clone.html
Based on the comment of coder4show a clone-method for a OneToMany relationship on A where $this->M is OneToMany and therefore an ArrayCollection:
public function __clone() {
if ($this->id) {
$this->setId(null);
// cloning the relation M which is a OneToMany
$mClone = new ArrayCollection();
foreach ($this->M as $item) {
$itemClone = clone $item;
$itemClone->setA($this);
$mClone->add($itemClone);
}
$this->M = $mClone;
}
}
There is also a module that will do this called DeepCopy:
https://github.com/myclabs/DeepCopy
$deepCopy = new DeepCopy();
$myCopy = $deepCopy->copy($myObject);
You can also add filters to customize the copy process.
I wasnt able to use DeepClone (it require php 7.1+), so I founded more simple way to clone relations in entity __clone method
$this->tags = new ArrayCollection($this->tags->toArray());
A clean way to clone a ArrayCollection:
$this->setItems(
$this->getItems()->map(function (Item $item) {
return clone $item;
})
);

Mapping Extra Attribute in a Join Table JPA 2

I am trying to model this relationship following this link http://www.javaworld.com/javaworld/jw-01-2008/images/datamodel.gif
Its the usual Many to Many relationship between Order and Products but I dont know how to add the extra columns in the Join table.
#Entity
#Table(name = "Orders")
public class Order {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "ORDER_LINES", joinColumns = { #JoinColumn(name = "ORDER_ID") }, inverseJoinColumns = { #JoinColumn(name = "PROD_ID") })
private Set<Product> products;
}
#Entity
#Table(name="PRODUCTS")
public class Product {
#ManyToMany(mappedBy="products")
private Set<Order> orders;
}
How to add Join Table extra attribute in JPA 2.0?
Thanks
There is no concept of having additional persistent attribute in relation in JPA (2.0). That's why relation with property is actually intermediate entity.
From both Order and Product entities, you need one-to-many relationship to new entity. Because of bidirectional relationship, new entity will have many-to-one relationships to Order and Product.
You need to go for something like this (shows only relationships, id's and other mappings stripped away):
#Entity
#Table(name="order_item")
public class OrderItem {
#ManyToOne
private Order order;
#ManyToOne
private Product product;
}
#Entity
public class Order {
#OneToMany (mappedBy = "order")
private Set<OrderItem> orderItems;
}
#Entity
public class Product {
#OneToMany(mappedBy = "product")
private Set<OrderItem> orderItems;
}