How to use JPQL join on unidirectional one to many relationship - jpa

Here are my entities:
#Entity
public class Author {
#Id
private Long id;
//...
}
and
#Entity
public class Book {
#Id
private Long id;
#ManyToOne(optional = true)
#JoinColumn(name = COLUMN_AUTHOR_ID, referencedColumnName = "id")
private Author author;
//...
}
I don't want to declare the Set<Book> books field in the Author entity to avoid unnecessary data fetch. I already know about fetch = FetchType.LAZY, but I have some cases in which even the lazy fetching is triggered.
Here is my question: How can I use JPQL join to retrieve the relationship in a custom query?
Look at ??books?? in the below query:
entityManager.createQuery("SELECT new " + AuthorWithBooks.class.getName() +
"(a.id, ..., group_concat(b.name)) FROM Author a LEFT JOIN a.??books?? b GROUP BY a.id", AuthorWithBooks.class);
You should consider that there might be some Author with no book and I want to include them in my query! So I can not start my join from Book.
Thanks

Simply switch the from and join clause:
entityManager.createQuery("SELECT new " + AuthorWithBooks.class.getName() +
"(a.id, ..., group_concat(b.name)) FROM Book b " +
"RIGHT JOIN b.author a GROUP BY a.id", AuthorWithBooks.class);

Related

Hibernate Postgresql select for update with outer join issue

I have faced with issue trying to select for update row using Spring data with Hibernate as JPA implementation and Postgresql.
Suppose we have entities:A,B,C.
public class A{
#Id
private Long id;
#OneToMany(fetch = FetchType.EAGER)
private Set<B> bSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<C> cSet;
}
Suppose we want to select A with all related B and C entities for update i.e. with locking row related to A table.
#Query(SELECT a FROM A a
LEFT JOIN FETCH a.bSet
LEFT JOIN FETCH a.cSet
WHERE a.id=?)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public A selectAndLockA(Long Aid);
The query will look like
SELECT a.column1, ... from tableA a LEFT JOIN tableB b ... FOR UPDATE of a,c
FOR UPDATE of a,c
The query will try to lock two tables what leads to exception like :
org.postgresql.util.PSQLException: ERROR: FOR UPDATE cannot be applied to the nullable side of an outer join
What I try to archive is locking only first table "FOR UPDATE OF a"
Is it possible to configure somehow or tell Hibernate to lock only first table.
This is not supported by PostreSQL. If you do an outer SELECT nothing can prevent somebody from inserting a row into the LEFT JOINED table thereby modifiying the result set you are looking at (e.g. the columns would not be NULL anymore on a repeated read).
For a detailed explanantion see here
It's been a long time since question was created, but I have a similar problem and hope my answer will help somebody.
Suppose that we have this JPA entities:
#Entity
#Table(name = "card_transactions")
public class CardTransactionsEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
#SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
#JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
private ReceiptsEntity receipt;
#Column
#Enumerated(EnumType.STRING)
private CardTransactionStatus requestStatus;
...
}
#Entity
#Table(name = "receipts")
public class ReceiptsEntity {
#EmbeddedId
private OfdReceiptId id;
...
}
#Embeddable
public class OfdReceiptId implements Serializable {
#Column(name = "ofd_id")
#Enumerated(EnumType.STRING)
private OfdId ofdId;
#Column(name = "receipt_id")
private String receiptId;
...
}
And we want select CardTransactionsEntity with fetched ReceiptsEntity for pessimistic update only CardTransactionsEntity. This can be done using Hibernate and Spring Data JPA repository as
public interface CardTransactionRepository extends JpaRepository<CardTransactionsEntity, Long> {
#Query("select ct from CardTransactionsEntity ct left join fetch ct.receipt r where ct.requestStatus = :requestStatus")
#Lock(value = LockModeType.PESSIMISTIC_WRITE)
#QueryHints(value = {
#QueryHint(name = "javax.persistence.lock.timeout", value = "-2"), // LockOptions.SKIP_LOCKED
#QueryHint(name = "org.hibernate.lockMode.r", value = "NONE") // "r" is alias for ct.receipt and will excluded from PESSIMISTIC_WRITE
})
List<CardTransactionsEntity> loadCardTransactions(#Param("requestStatus") CardTransactionStatus requestStatus, Pageable pageable);
}
This repository method will execute query like
SELECT ct.*, r.* from card_transactions ct LEFT OUTER JOIN receipts r ON ct.ofd_id = r.ofd_id and ct.receipt_id = r.receipt_id WHERE ct.request_status=? LIMIT ? FOR UPDATE OF ct SKIP LOCKED
You can bypass this error with joining the tables with FetchType.LAZY. This fetch type is the default one and it is not required to specify for #OneToMany joins.
public class A{
#Id
private Long id;
#OneToMany
private Set<B> bSet;
#OneToMany
private Set<C> cSet;
}

Querying EXISTS in many to many relations in Criteria API

Having two entity classes with a many to many relation, I am trying to create query which tests the existence of any relationship for all entities from one table. I am stuck because it seems that there is no way to refer to the JoinTable through the Criteria API.
Example entities:
#Entity
#Table(name="man")
public class Man {
#Id
#GeneratedValue
private Long id;
}
#Entity
#Table(name="woman")
public class Woman {
#Id
#GeneratedValue
private Long id;
#ManyToMany
#JoinTable(
name="man_woman",
joinColumns=
#JoinColumn(name="woman_id", referencedColumnName="id"),
inverseJoinColumns=
#JoinColumn(name="man_id", referencedColumnName="id")
)
private Set<Man> men;
}
I would like to create a query using criteria API which would result in SQL such as:
select m.id,
case when exists(select * from man_woman mw where mw.man_id=m.id) then 1 else 0
from man;
The best I have come up with so far is the following:
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Man> from = criteriaQuery.from(Man.class);
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<Woman> sub_from = subquery.from(Woman.class);
SetJoin<Woman, Man> setJoin = sub_from.join(Woman_.men);
subquery.select(sub_from.get(Woman_.id));
subquery.where(from.in(setJoin.get(Man_.id)));
criteriaQuery.multiselect(from.alias("man_entity"),
criteriaBuilder.selectCase()
.when(
criteriaBuilder.exists(subquery)
, true)
.otherwise(false)
.alias("knows_any_women")
);
return em.createQuery(criteriaQuery).getResultList()
which results in SQL containing extra joins:
select m.id,
case when exists(select w.id
from woman w
inner join man_woman mw on w.id = mw.woman_id
inner join man m2 on m2.id = mw.man_id
where m.id in (m2.id)
)
then 1 else 0
from man;
I guess this statement would eventually be optimized to look like my desired one - but is there a way to make it simpler from the beginning?

JPA CRITERIA QUERY with order by joined columns

How to invoke order by on a joined entity? I am trying to achieve the following with:
select * from person p inner join telephone t on p.id=t.person_id join sim s on s.id=t.sim_id order by s.name DESC
#Entity
public class Person implements Serializable{
#Id
private Long id;
#OneToMany(orphanRemoval = true, mappedBy = "person", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Telephone> telephonesNumber;
#Entity
public class Telephone implements Serializable {
#Id
private String number;
#Id
#ManyToOne()
#JoinColumn(name = "person_id")
private Person person;
#Id
#ManyToOne(cascade = {})
#JoinColumn(name = "sim_id")
private Sim sim;
#Entity
public class Sim implements Serializable {
#Id
private Long id;
#Column(unique = true)
private String name;
I use specification interface, in this example sorting is on the field person.id and it works
public class PersonSpecification implements Specification<Person> {
#Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// there is many different conditions for example
// if(someCondition!=null) {
// predicates.add(builder.like(root.get("someProperty"), someValue));
// }
query.groupBy(root.get("id"));
//there I want to order by Sim.name i dont know how
query.orderBy(builder.asc(root.get("phone")));//this works
return builder.and((predicates.toArray(new Predicate[predicates.size()])));
}
I want to order by Sim.name but i dont know how.
In JPA specification you can use:
query.orderBy(builder.asc(root.join("telephonesNumber").get("sim").get("name")));
to sort by sim name.
For more details:
https://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship
If you using JPA Query:
#Query("select s from Person p
join p.telephonesNumber t
join t.sim s order
by t.sim.id desc")
It will produce this:
select * from person p
inner join telephone t on p.id=t.person_id
inner join sim s on t.sim_id=s.id
order by t.sim_id desc
For more details:
https://github.com/abhilekhsingh041992/spring-boot-samples/blob/master/jpa/src/main/java/example/springboot/jpa/repository/PersonRepository.java
another way for that would be using Query method:
List<Telephone> findAllByOrderBySimIdAsc();
Look at this findAllByOrderBySimIdAsc
With the code before, you can get all rows from Telephone ordered by Sim Id.

My JPQL query with Outer Join returns empty list while using GroupBy, OrderBy and Sum Function

This is my Product entity:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotBlank
private String name;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Category category;
...
}
And this is the productOrder junction table for the relation product-order:
#Entity
public class ProductOrder {
#EmbeddedId
private ProductOrderId pk;
#Min(value = 1)
private int quantity;
...
}
And this is the embeddedId class:
#Embeddable
public class ProductOrderId {
#ManyToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "orderr_id")
private Order order;
...
}
I have this JPQL Query works perfectly which finds all the products from a category, sorting them based on their total sold quantity. Since it is a outer join, i get also the ones which haven't been sold yet:
#Query(value = "select p, sum(po.quantity) as total_quantity " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1) " +
"group by p.id, p.name " +
"order by total_quantity desc nulls last")
Page<Object[]> findBestSellerProductsByCategory(Category category, Pageable pageable);
The problem is, when there is not any sold product in a category, i get an empty page. So in order to use this query, my category has to have at least 1 sold product, then i get also the others which haven't been sold yet. What is the problem here?
Since i make a "select from junctionTable (ProductOrder)", when there is not any element from the category in this table, i get an empty list as i understand. But i could not make it the other way around "select from product", since product has no reference of productOrder table, i cannot make a join relationship in that case..
INFO: When i remove the groupBy, it works. I get my products from a category even if none of them is sold yet.
#Query(value = "select p " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1)")
Page<Object[]> findProductsByCategory(Category category, Pageable pageable);

How to make a CriteriaBuilder join with a custom "on" condition?

I want make a query where I join 2 tables, using the CriteriaBuilder. In MySQL the query I'm trying to make would look like this:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
AND item.type_id = 1
I want to get all orders and if they have an item of type #1, I want to join with this item. However, if no item of type #1 is found, I still want to get the order. I can't figure out how to make this with the CriteriaBuilder. All I know how to make is:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
Join<Item, Type> type = order.join(Item_.type, JoinType.LEFT);
cq.select(order);
cq.where(cb.equal(type.get(Type_.id), 1));
This query is broke, since it results in something like this in MySQL:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
WHERE item.type_id = 1
The result will only contain orders with items of type #1. Orders without are excluded. How can I use the CriteriaBuilder to create a query like in the first example?
It is possible starting from the version 2.1 of JPA using the on method Join<Z, X> on(Predicate... restrictions);
Here is how:
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
item.on(cb.equal(item.get(Item_.type), 1));
I think this is the same problem as posed in this question. It looks like it is not possible in CriteriaBuilder. It is possible in Hibernate Criteria API, but that probably won't help you.
JPA Criteria API: Multiple condition on LEFT JOIN
I know this question was made a long time a go, but recently a had the same problem and i found this solution from an Oracle forum, i copied and pasted just in case the link is not longer available.
MiguelChillitupaArmijos 29-abr-2011 1:41 (en respuesta a 840578) Think
you should use something like:
em.createQuery("SELECT DISTINCT e.Id" +
" from Email e " +
" left join e.idEmailIn e2 *with* e2.responseType = 'response'" +
" where e.type = 'in' and e.responseMandatory = true").getSingleResult();
An this is the link.
JPA Criteria : LEFT JOIN with an AND condition
There is a workaround if you are using Hibernate 3.6 with JPA 2.0
It is not the better solution, however it works perfect for me.
I´ve duplicate the entity with the #Where hibernate annotation.It means that everytime you use the join with this entity, hibernate will add the extra condition on the join statement at generated SQL.
For instance, initially we have the follow example:
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
}
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
In order to add extra conditions on criteria Join, we need duplicate the Address #Entity mapping , adding the #Where annotation #Where(clause = " ADDRESS_TYPE_ID = 2").
#Entity
#Table(name = "ADDRESS")
#Where(clause = " ADDRESS_TYPE_ID = 2")
public class ShippingAddress {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#OneToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
Also, we need to add the duplicate mapping association for the new entity.
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
#OneToOne(mappedBy = "person")
private ShippingAddress shippingAddress;
}
Finally, you can use a join with this specific Entity in your criteria :
PersonRoot.join(Person_.shippingAddress, JoinType.LEFT);
The Hibernate Snippet SQL should seems like this :
left outer join
address shippingadd13_
on person11_.person_id=shippingadd13_.person_id
and (
shippingadd13_.ADDRESS_TYPE_ID = 2
)
ON clause is supported in Hibernate 4.3 version, anyone is aware if there is a parameter indexing issue between the parameter index of the additional custom conditions with the index of the existing mapping filters when doing an outer join with ON clause?
Using the Person entity class below as an example, say I am adding this filter to limit the address types and the filter is enabled to populate the IN clause. The parameter index for the IN clause will cause the issue [2] when I add additional conditions (such as using 'street' column) part of the ON clause. Is is a known issue?
[1] #Filter(name = "addressTypes", condition = "ADDRESS_TYPE in (:supportedTypes)")
[2]
Caused by: ERROR 22018: Invalid character string format for type BIGINT.
private Set addresses;