Query on distant/not directly related entity - java

I'm trying to create a query with a where clause on a not directly related table.
My table/entity structure looks like this:
I have got an entity Entity with a ManyToOne relation to Relation. Relation has a ManyToMany relation to DistantRelation.
I have a JpaSpecificationExecutor<Entity> on which I call findAll() with a Specification<Entity>.
How do I setup my entity and/or my specification so I can filter on one of the fields of DistantRelation?

Entities definition:
#javax.persistence.Entity
#Data
public class Entity {
#Id
private Long id;
#ManyToOne
private Relation relation;
}
#javax.persistence.Entity
public class Relation {
#Id
private Long id;
#ManyToMany
private Set<DistantRelation> distantRelation;
}
#Entity
public class DistantRelation {
#Id
private Long id;
private String name;
#ManyToMany
private Set<Relation> relation;
}
Solution 1. Subquery with optimal joins
public class EntityDistantRelationSpecification implements Specification<Entity> {
private String value;
public EntityDistantRelationSpecification(String value) {
this.value = value;
}
#Override
public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<DistantRelation> subQuery = query.subquery(DistantRelation.class);
Root<DistantRelation> subRoot = subQuery.from(DistantRelation.class);
Expression<Collection<Relation>> relations = subRoot.get("relation");
Predicate relationPredicate = builder.isMember(root.get("relation"), relations);
Predicate distantFiledPredicate = builder.equal(subRoot.get("name"), value);
subQuery.select(subRoot).where(relationPredicate, distantFiledPredicate);
return builder.exists(subQuery);
}
}
Generated query:
select
entity0_.id as id1_9_,
entity0_.relation_id as relation2_9_
from
entity entity0_
where
exists (
select
distantrel1_.id
from
distant_relation distantrel1_
where
(
entity0_.relation_id in (
select
relation2_.relation_id
from
distant_relation_relation relation2_
where
distantrel1_.id=relation2_.distant_relation_id
)
)
and distantrel1_.name=?
)
Solution 2. Subquery with all relation joins
public class EntityDistantRelationSpecification implements Specification<Entity> {
private String value;
public EntityDistantRelationSpecification(String value) {
this.value = value;
}
#Override
public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
Subquery<Relation> subQuery = query.subquery(Relation.class);
Root<Relation> subRoot = subQuery.from(Relation.class);
Join<Relation, DistantRelation> distantRelationJoin = subRoot.join("distantRelation", JoinType.INNER);
Predicate relationPredicate = builder.equal(root.get("relation"), subRoot.get("id"));
Predicate distantFiledPredicate = builder.equal(distantRelationJoin.get("name"), value);
subQuery.select(subRoot).where(relationPredicate, distantFiledPredicate);
return builder.exists(subQuery);
}
}
Generated query:
select
entity0_.id as id1_9_,
entity0_.relation_id as relation2_9_
from
entity entity0_
where
exists (
select
relation1_.id
from
relation relation1_
inner join
relation_distant_relation distantrel2_
on relation1_.id=distantrel2_.relation_id
inner join
distant_relation distantrel3_
on distantrel2_.distant_relation_id=distantrel3_.id
where
entity0_.relation_id=relation1_.id
and distantrel3_.name=?
)

Related

Spring boot jpa specification filter records of child entity from parent entity

I have two entities A and B where A is the parent and B is the child. I want to filter records using JPA specifications like Specification.
Their relation is Entity B is having reference to A. I want to apply left join on Entity A using JPA Criteria Builder API. Is it possible to achieve that ?
Here is my Parent entity class
#Entity
public class Parent {
#Id
private Long parentId;
private String name;
private String description;
// constructor, getter and setters
}
Here is my child entity class
#Entity
public class Child {
#Id
private Long childId;
private String childName;
private String email;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
// constructor, getter and setters
}
I want to achieve something like this below code, just want to filter each record based on the childName of the Child entity from Parent Specification.
#Component
public class ParentSpecification {
public Specification<Parent> getParentSpecification(Map<String, String> filterValues) {
return (root, query, criteriaBuilder) -> {
Root<Child> from = query.from(Child.class);
from.join("parent", JoinType.LEFT);
List<Predicate> predicates = new ArrayList<>();
filterValues.forEach((attribute, value) -> {
if (attribute.equalsIgnoreCase("childName") && value != null) {
predicates.add(criteriaBuilder.like(from.get("childName"), "%" + value + "%"));
}
});
return query.where(criteriaBuilder.and(predicates.toArray(new Predicate[0]))).getRestriction();
};
}
}
There are many resources online on this topic.
Example:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Author> root = cq.from(Author.class);
Join<Object, Object> book = root.join(Author_.BOOKS, JoinType.LEFT);
cq.multiselect(root, book);
ParameterExpression<String> pLastName = cb.parameter(String.class);
cq.where(cb.equal(root.get(Author_.LAST_NAME), pLastName));
TypedQuery<Tuple> q = em.createQuery(cq);
q.setParameter(pLastName, "Janssen");
List<Tuple> authorBooks = q.getResultList();
Reference: https://thorben-janssen.com/hibernate-tip-left-join-fetch-join-criteriaquery/#Defining_a_LEFT_JOIN_or_RIGHT_JOIN_clause

Spring JPA Criteria API Query on OneToMany List Field

I want to load all the CustomerRequests for a specific Customer with the JPA/Hibernate Criteria API.
In specific: I want to load all the CustomerRequest for which a CustomerRequest2Customer entry with a specific customerId exists.
#Entity
public class CustomerRequest {
#Id
private int id;
private int priority;
#OneToMany(mappedBy = "customerRequestId")
private List<CustomerRequest2Customer> listCustomerRequestToCustomer; // <- Query this field
}
#Entity
public class CustomerRequest2Customer {
#Id
private int id;
#ManyToOne
private Customer customer; // <- Query this field
#ManyToOne
private CustomerRequest customerRequest;
}
#Entity
public class Customer {
#Id
private int id; // <- Query this field
private String name;
}
How I currently query other fields:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<CustomerRequest> cq =
cb.createQuery(CustomerRequest.class);
Root<CustomerRequest> root = cq.from(CustomerRequest.class);
cq.where(cb.equal(root.get("priority"), 1));
return entityManager.createQuery(cq).getResultList();
You have to join the entities like this:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<CustomerRequest> cq = cb.createQuery(CustomerRequest.class);
Root<CustomerRequest> root = cq.from(CustomerRequest.class);
Join<CustomerRequest, CustomerRequest2Customer> customerRequest2Customer = root.join("listCustomerRequestToCustomer");
Join<CustomerRequest2Customer, Customer> customer = customerRequest2Customer .join("customer");
cq.where(cb.equal(customer .get("id"), 1));
return entityManager.createQuery(cq).getResultList();

java springframework.data.jpa generates same join twice using JpaSpecificationExecutor

i'am using JpaSpecificationExecutor so i create my sql dynamically ,using specification Api from org.springframework.data.jpa.domain.Specification and the probleme is that it generates the same join twice ,which tears down performances ,here is the code :
the caller method is :
private Specification<EntityA> toSpecEntityBAndEntityC(boolean withEntityBAndEntityC, Specification<EntityA> specs) {
if (withEntityBAndEntityC) {
specs = Specification.where(specs).and(EntityASpecs.withEntityB());
specs = Specification.where(specs).and(EntityASpecs.withEntityC());
}
return specs;
}
here is the EntityASpecs class
public interface EntityASpecs{
static Specification<EntityA> withEntityB() {
return (root, query, builder) -> {
if (!isCountQuery(query)) {
root.fetch(EntityA_.EntityB);
} else {
root.join(EntityA_.EntityB,JoinType.INNER);
}
return null;
};
}
static Specification<EntityA> withEntityC() {
return (root, query, builder) -> {
if (!isCountQuery(query)) {
root.fetch(EntityA_.EntityC);
} else {
root.join(EntityA_.EntityC,JoinType.INNER);
}
return null;
};
}
}
here are the entities:
#Data
#Entity
public class EntityA{
#Id
#GeneratedValue(generator = "seq_entityB")
#SequenceGenerator(name = "seq_entityB", sequenceName = "SEQ_entityB", allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
private EntityB entityB;
#ManyToOne(fetch = FetchType.LAZY)
private EntityC entityC ;
}
it generates this query
select *
from (select entityA0_.id as id1_4_0_,
entityC2_.filedOne as filedOne1_3_1_,
entityB3_.filedTwo as filedTwo1_1_2_,
entityA0_.filedFive_id as filedFive_id7_4_0_,
entityA0_.entityB_filedTwo as entityB_filedTwo8_4_0_,
entityA0_.DATE_TRAITEMENT as DATE_TRAITEMENT2_4_0_,
entityA0_.filedFour as filedFour3_4_0_,
entityA0_.entityC_filedOne as entityC_filedOne9_4_0_,
entityA0_.filedSix as filedSix4_4_0_,
entityA0_.filedSeven as filedSeven5_4_0_,
entityA0_.TEMPS_REPONSE as TEMPS_REPONSE6_4_0_,
entityC2_.NB_MOYEN_TRAITEMENTS as NB_MOYEN_TRAITEMEN2_3_1_,
entityC2_.filedSeven as filedSeven3_3_1_,
entityC2_.filedSixteen as filedSixteen4_3_1_,
entityB3_.filedFifteen as DATE_TRAITEMENT_SA2_1_2_,
entityB3_.filedEight as filedEight11_1_2_,
entityB3_.filedTen as filedTen3_1_2_,
entityB3_.filedNine as filedNine4_1_2_,
entityB3_.filedFourteen as filedFourteen5_1_2_,
entityB3_.filedThirteen as filedThirteen6_1_2_,
entityB3_.NUMERO_AUTORISATION as NUMERO_AUTORISATIO7_1_2_,
entityB3_.filedEleven as filedEleven8_1_2_,
entityB3_.filedSeven as filedSeven9_1_2_,
entityB3_.filedTwelve as filedTwelve10_1_2_
from entityA entityA0_
inner join entityB entityB1_ on entityA0_.entityB_filedTwo = entityB1_.filedTwo
inner join entityC entityC2_ on entityA0_.entityC_filedOne = entityC2_.filedOne
**inner join entityB entityB3_ on entityA0_.entityB_filedTwo = entityB3_.filedTwo**
where entityA0_.filedSix = 'Value'
and (entityB1_.filedFifteen between TO_DATE('2018-11-08 14:00:00', 'YYYY-MM-DD HH24:MI:SS') and TO_DATE('2018-11-08 15:00:00', 'YYYY-MM-DD HH24:MI:SS'))
order by entityA0_.DATE_TRAITEMENT desc)
;
NB:
duplicate join is between two stars
**inner join entityB entityB3_ on entityA0_.entityB_filedTwo = entityB3_.filedTwo**

crudrepository findBy method signature for list of tuples

I have an Entity Class like this:
#Entity
#Table(name = "CUSTOMER")
class Customer{
#Id
#Column(name = "Id")
Long id;
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
How to write findBy method for the below query using crudrepository spring data jpa?
select * from customer where (email, mobile) IN (("a#b.c","8971"), ("e#f.g", "8888"))
I'm expecting something like
List<Customer> findByEmailMobileIn(List<Tuple> tuples);
I want to get the list of customers from given pairs
I think this can be done with org.springframework.data.jpa.domain.Specification. You can pass a list of your tuples and proceed them this way (don't care that Tuple is not an entity, but you need to define this class):
public class CustomerSpecification implements Specification<Customer> {
// names of the fields in your Customer entity
private static final String CONST_EMAIL_ID = "emailId";
private static final String CONST_MOBILE = "mobile";
private List<MyTuple> tuples;
public ClaimSpecification(List<MyTuple> tuples) {
this.tuples = tuples;
}
#Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// will be connected with logical OR
List<Predicate> predicates = new ArrayList<>();
tuples.forEach(tuple -> {
List<Predicate> innerPredicates = new ArrayList<>();
if (tuple.getEmail() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_EMAIL_ID), tuple.getEmail()));
}
if (tuple.getMobile() != null) {
innerPredicates.add(cb.equal(root
.<String>get(CONST_MOBILE), tuple.getMobile()));
}
// these predicates match a tuple, hence joined with AND
predicates.add(andTogether(innerPredicates, cb));
});
return orTogether(predicates, cb);
}
private Predicate orTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.or(predicates.toArray(new Predicate[0]));
}
private Predicate andTogether(List<Predicate> predicates, CriteriaBuilder cb) {
return cb.and(predicates.toArray(new Predicate[0]));
}
}
Your repo is supposed to extend interface JpaSpecificationExecutor<Customer>.
Then construct a specification with a list of tuples and pass it to the method customerRepo.findAll(Specification<Customer>) - it returns a list of customers.
It is maybe cleaner using a projection :
#Entity
#Table(name = "CUSTOMER")
class CustomerQueryData {
#Id
#Column(name = "Id")
Long id;
#OneToOne
#JoinColumns(#JoinColumn(name = "emailId"), #JoinColumn(name = "mobile"))
Contact contact;
}
The Contact Entity :
#Entity
#Table(name = "CUSTOMER")
class Contact{
#Column(name = "EMAIL_ID")
String emailId;
#Column(name = "MOBILE")
String mobile;
}
After specifying the entities, the repo :
CustomerJpaProjection extends Repository<CustomerQueryData, Long>, QueryDslPredicateExecutor<CustomerQueryData> {
#Override
List<CustomerQueryData> findAll(Predicate predicate);
}
And the repo call :
ArrayList<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("a#b.c","8971"));
contacts.add(new Contact("e#f.g", "8888"));
customerJpaProjection.findAll(QCustomerQueryData.customerQueryData.contact.in(contacts));
Not tested code.

Spring Data JPA Subquery with Criteria API

I am trying to create a dynamic query with Specification with two entities which have bidirectional relation. The entities are:
#Entity
#Table("SUPPLIERS")
public class Supplier implements Serializable {
#Id
Column("ID")
private Long id;
#Id
Column("COMPANY_ID")
private Long companyId;
}
#Entity
#Table("EMPLOYEES")
public class Employee implements Serializable {
#Id
private Long id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "FIRM_ID", referencedColumnName = "ID"),
#JoinColumn(name = "FIRM_COMPANY_ID", referencedColumnName = "COMPANY_ID")
})
private Supplier supplier;
}
When I want to select employees based on their supplier,
return new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Long[] supplierCodes = {1L, 2L};
Subquery<Supplier> supplierBasicSubquery = query.subquery(Supplier.class);
Root<Supplier> supplierBasicRoot = supplierBasicSubquery.from(Supplier.class);
Join<Employee, Supplier> sqTfV = root.join("supplier", JoinType.INNER);
supplierBasicSubquery.select(sqTfV).where(sqTfV.<Long>get("id").in(supplierCodes));
return root.<Supplier>get("supplier").in(supplierBasicSubquery);
}
};
When its executed, it generates SQL like:
SELECT ....
FROM EMPLOYEES E
INNER JOIN ....
WHERE (E.FIRM_ID, E.FIRM_COMPANY_ID) in
(SELECT (s.ID, s.COMPANY_ID) FROM SUPPLIERS WHERE SUPPLIER.ID in (1, 2))
As you can see, the inner select columns are surrounded by parenthesis which causes Oracle to throw exception:
java.sql.SQLSyntaxErrorException: ORA-00920: invalid relational operator
How can I fix this issue, any suggestions?
Thanks a lot.

Categories

Resources