Spring Data+JPA: enforce inner join for OneToOne relationship - postgresql

I have an Entity with OneToOne relation, which is used just to sort results:
#Entity
public class Document {
#Id
Long id;
#OneToOne()
SortProperty sortProp;
...
}
Then I have repository (using QueryDSL predicates):
public interface DocumentRepository
implements PagingAndSortingRepository<Document, Long>,
QueryDslPredicateExecutor<Document> {
#EntityGraph(value = "Document.forceJoins")
Page<Document> findAll(Predicate queryDslPredicate, Pageable pageable);
...
}
As you see above, I use #EntityGraph to control joining relations in the main query. All this work well, the only problem is performance - #OneToOne is fetched vith left outer join which means that DB index is not used:
select * from
document document0_
left outer join
sortproperty sortproper3_
on document0_.documentid=sortproper3_.documentid
...
Is there any way how to enforce using inner join instead of left outer join?
I have tried several things - #OneToOne(optional = false), #org.hibernate.annotations.Fetch, but no success ... Parts generated from QueryDSL predicate(s) use properly inner joins for the property, but the main part of query use always left outer join. I was trying also use annotation with this method:
#Query("select doc from Document doc inner join doc.sortProperties props")
but I was unable to use it properly together with paging and QueryDSL predicates.
Any idea?

Try this with #Query annotation.
#Query("select doc from Document doc join doc.sortProp props")

Related

QueryDSL Left Join not mapping properly with JPA entity

QueryDSL is not working properly with JPA for left join.
I am using queryDSL version 4.2.1 and mapping the response directly to javax.persistence entity.
For Left join/Right join, the joining condition it's not working. It fetches every entity disregarding the joining condition(here it's name = "testName") when entity1.getEntity2() is being called.
Is there any other way applicable for this case to map the result after JOIN tables ?
JPAQuery<Entity1> query = new JPAQuery<>(entityManager);
query.from(table1);
query.leftJoin(table2).on(table2.id.eq(table1.id).and(table2.name.eq("testName"));
List<Entity1> list = query.fetch();
#Entity
public class Entity1{
private Integer id;
#OneToMany(mappedBy = "entity1", fetch = FetchType.LAZY)
private List<Entity2> entity2;
}
An left or right join is an outer join.
So if you use left join all records from the left (in your case table1) will be selected.
If you only want records from table1 if there are corresponding records on table2 you have to use innerJoin.
JPAQuery<Entity1> query = new JPAQuery<>(entityManager);
query.from(table1);
query.innerJoin(table2).on(table2.id.eq(table1.id).and(table2.name.eq("testName"));
List<Entity1> list = query.fetch();
Read more about the join types here:
https://www.diffen.com/difference/Inner_Join_vs_Outer_Join

JPQL query delete not accept a declared JOIN?

I'm trying to understand why the Hibernate not accepts this follow JPQL:
#Modifying
#Query("delete from Order order JOIN order.credit credit WHERE credit.id IN ?1")
void deleteWithListaIds(List<Long> ids);
The error that I receive is:
Caused by: java.lang.IllegalArgumentException: node to traverse cannot be null!
at org.hibernate.hql.internal.ast.util.NodeTraverser.traverseDepthFirst(NodeTraverser.java:46)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:284)
But accepts this:
#Modifying
#Query("delete from Order order WHERE order.credit.id IN ?1")
void deleteWithListaIds(List<Long> ids);
The entity Order (the entity Credit does not map the Orders):
#Entity
public class Order {
#Id
#Setter
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
#SequenceGenerator(name = SEQUENCE, sequenceName = SEQUENCE, allocationSize = 1)
#Column(name = "id", nullable = false)
private Long id;
#JoinColumn(name = "credit_id", foreignKey = #ForeignKey(name = "fk_order_credit"))
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Credit credit;
}
In select statements, the two approaches are accepted, but I don't understand why Hibernate have this limitation or if I'm doing something wrong in my DELETE Jpql. I would like to declare the JOIN in the query.
The only way that I know to resolve this problem in more complex queries is create a subselect:
delete from Order order WHERE order.id IN (
SELECT order.id FROM Order order
JOIN order.credit credit
WHERE credit.id in ?1)
Is this the right approach for more complex delete queries?
I'm using the Spring Jpa Repository in the code above and Spring Boot 1.5.10.RELEASE.
I don't understand why Hibernate have this limitation.
It is specified as such in the JPA Spec in section 4.10:
delete_statement ::= delete_clause [where_clause]
delete_clause ::= DELETE FROM entity_name [[AS] identification_variable]
So joins aren't allowed in delete statements.
Why this was decided this way is pure speculation on my side.
But the select_clause or delete_clause specify what the query operates on. While it is totally fine for a select statement to operate on a combination of multiple entities a join for a delete doesn't really make much sense.
It just forces you to specify which entity to delete.
The only way that I know to resolve this problem in more complex queries is to create a subselect:
Is this the right approach for more complex delete queries?
If you can't express it using simpler means then yes, this is the way to go.

JPA CriteriaBuilder find entity which has elements with certain attributes in collection

I have an entity which contains a list of elements and now I want to search over attributes of these elements. This constraint should be "and" connected. Please see these simple example:
#Entity
public class Parent {
#Column
#Enumerated(EnumType.STRING)
private City city;
#OneToMany(...)
private List<Children> childrens;
}
#Entity
public class Children {
#Column
#Enumerated(EnumType.STRING)
private School school;
#Column
private Integer yearInSchool;
}
Now I want to find Parents in a certain city, lets say "BigCity" with children in School "AwesomeSchool" which are in class/ year 6. I want to get the search result only via CriteriaBuilder.
So far I got:
final CriteriaBuilder c = getCriteriaBuilder();
final CriteriaQuery<Parent> query = c.createQuery(Parent.class);
final Root<Parent> r = query.from(Parent.class);
query.select(r)
.where(c.and(c.equal(r.get("city"), City.BigCity)),
c.equal(r.get("childrens").get("school"), School.AwesomeSchool),
c.equal(r.get("childrens").get("yearInSchool"), 6));
Unfortunately there are two problems here:
- it looks like I can't call get("school") on the list attribute
- this will return all parents with children which are either in "AwesomeSchool" or are 6 years in the school.
Can you help me please? I thought about using a join, but there the same question is: how can I define the where part of the join so that it considers that both attributes (school and yearInSchool) have to be fulfilled at the same time.
I found similar posts about querying for objects whose children fulfill one condition - but here the children has to fulfill two conditions at the same time.
Update 1
If I use a join to assert e.g. the "school" of one child, I get so far concerning the predicate:
Predicate predicate = r.join("childrens").get("school").in(School.AwesomeSchool)
How can I reuse this joined object to assert is also for the second filter condition?
You need to JOIN and then use the JOIN object you got when forming the join when forming the WHERE clauses.
Join childrenJoin = r.join("childrens");
query.where(c.and(c.equal(r.get("city"), City.BigCity)),
c.equal(childrenJoin.get("school"), School.AwesomeSchool),
c.equal(childrenJoin.get("yearInSchool"), 6));
Perhaps you mean your JPQL to be :
SELECT p FROM Parent p JOIN p.childrens c
WHERE p.city = :theCity AND c.school = :theSchool AND c.yearInSchool = 6

Finding an inherited entity over a jointable with NamedQuery

How can I use a NamedQuery to find an entity over a jointable?
I have an abstract parent class/entity with #Inheritance(strategy=InheritanceType.JOINED) and two subclasses/subentities.
Hence, in the database I have a parent table (sdrs) and two subtables (xSdrs and ySdrs).
There is another table reservations which shall have a Many-to-Many relationship to table sdrs. That's why I created a jointable between reservations and sdrs.
I intend to have a NamedQuery in the parent entity Sdr to be able to find the key for a record/entity of XSdr or YSdr respectively over the jointable.
In class Sdr I have:
#NamedQuery(name="Sdr.findBySdrId", query="SELECT s FROM Sdr s "
+ "INNER JOIN s.reservations res WHERE res.sdrs = :transactionId")
and
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
#JoinTable(name="sdrs_has_reservations",
joinColumns = {
#JoinColumn(name="sdrs_id", referencedColumnName="sdrId", nullable=false)
}, inverseJoinColumns={
#JoinColumn(name="reservations_id", referencedColumnName="reservationId", nullable=false)
})
private List<Reservation<T>> reservations;
But, of course, this sets the transactionId to reservationId which consequentially fails.
On the inverse side I have:
#ManyToMany(mappedBy="reservations", fetch=FetchType.EAGER)
private List<Sdr<T>> sdrs;
So, how do I have to implement the named query in class/entity Sdr to be able to get the proper Sdr (and its related reservations) with an Sdr ID to be set as query parameter?
SELECT sdr FROM Reservation r JOIN r.sdrs sdr WHERE sdr.id=:id

JPA OneToMany relations and performace

I have two entities: parent Customer and child Order.
Each Customer has 1,000,000 Orders for example, so it is not needed in any given time to load a Customer with all Orders but I want to have this ability to make join query on these two entities in JPA.
So because of this, I must create #OneToMany relationship for making join queries.
My question is: how to get query without making joinColumn because even in Lazy mode it is possible to load 1,000,000 objects!
I just want to get query on these object with where restrictions like native join.
If you don't want the #OneToMany relationship implicitly set in your Customer class than you don't have to. You can execute JPQL queries (in very precise manner) without the marked relationship.
Assume you have:
#Entity
public class Customer {
// all Customer-related fields WITHOUT #OneToMany relationship with Order
}
#Entity
public class Order {
#ManyToOne
private Customer owner;
}
Then if you want to get all Orders for particular Customer you can execute a simple JPQL query like that:
// Customer customer = ...
// EntityManager em = ...
String jpql = "SELECT o FROM Order o WHERE o.owner = :customer";
TypedQuery<Order> query = em.createQuery(jpql, Order.class);
query.setParameter("customer", customer);
List<Order> orders = query.getResultList();
In this way you can execute the code only when you're really sure you want to fetch Customer's orders.
I hope I've understood your problem correctly.
EclipseLink has support for QueryKeys, that allow you to define fields or relationships for querying that are not mapped. Currently there in no annotation support for query keys, but you can define them using the API and a DescriptorCustomizer.
Also you do not need the OneToMany to query on it, just use the inverse ManyToOne to query,
i.e.
Select distinct c from Customer c, Order o where o.customer = c and o.item = :item
Or,
Select distinct o.customer from Order o join o.customer c where o.customer = c and o.item = :item