Convert specific query into JPQL or Criteria Builder query - spring-data-jpa

Here is the code for 2 entities (it generates three tables in the database). A Book entity:
#Entity
public class Book {
#Id
private long id;
private String name;
#ManyToMany
private List<Author> authors;
}
An Author entity:
#Entity
public class Author {
#Id
private long id;
#Column(unique=true)
private String name;
}
I'm trying to find books by the list of authors. Here is a sql query:
select book.id, ARRAY_AGG(author.name)
from book
join book_authors ba on book.id=ba.book_id
join author on ba.authors_id=author.id
group by book.id
having ARRAY_AGG(distinct author.name order by author.name)=ARRAY['a1', 'a2']::varchar[]
['a1', 'a2'] is a list of book authors, it must be passed as a parameter. The idea is to aggregate authors and then compare them with the list of passed parameters.
How to rewrite this SQL-query into either a JPQL or CriteriaBuilder query?

#Query("select distinct b from Book b join b.authors a where a.name in(:names)")
List<Book> findByAuthorsNames(#Param("names") List<String> names)
If you want to fetch b.authors use join fetch instead of join

If the exact match is necessary you can use Specification like this
public class BookSpecifications {
public static Specification<Book> byAuthorsNames(List<String> names) {
return (root, query, builder) -> {
Join<Book, Author> author = root.join("authors", JoinType.LEFT);
Predicate predicate = builder.conjunction;
for(String name : names) {
Predicate namePredicate = builder.and(author.get("name"), name);
predicate = builder.and(predicate, namePredicate);
}
return predicate;
}
}
}
BookRepository have to extend JpaSpecificationExecutor.
Usage:
BookRepository repository;
public List<Book> findByAuthorsNames(List<String> names) {
return repository.findAll(BookSpecifications.byAuthorsNames(names));
}

Related

Crieria API query using criteriabuilder.construct with a non existing relationship

Given this very simple DTO:
#Entity
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
#OneToOne
private Employee boss;
}
I'd like to make a query that gathers all employee names and their boss' id, put in a nice clean POJO:
public class EmployeeInfo {
private String name;
private Long bossId;
public EmployeeInfo(String name, Long bossId) {
this.name = name;
this.bossId = bossId;
}
}
This query should be of use:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<EmployeeInfo> query = cb.createQuery(EmployeeInfo.class);
Root<Employee> root = query.from(Employee.class);
query.select(
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
root.get("boss").get("id").as(Long.class)));
result = em.createQuery(query).getResultList();
When a bossId is present in the employee column this works just fine. But when no boss id is set the record will be completly ignored. So how do i treat this non existing boss relation as null or 0 for the construct/multiselect?
In pure SQL it is easy:
SELECT name, COALESCE(boss_id, 0) FROM EMPLOYEE;
But for the love of god i cannot make the criteria api do this.
cb.construct(EmployeeInfo.class,
root.get("name").as(String.class),
cb.coalesce(root.get("boss").get("id").as(Long.class), 0L)));
The problem is that root.get("boss") generate query with cross join like this from Employee employee, Employee boss where employee.boss.id=boss.id. So records where employee.boss.id is null are ignored.
To solve the problem you should use root.join("boss", JoinType.LEFT) instead of root.get("boss")

Spring Data JPA Projection nested list projection interface

I have a question about usage of nested list projection interface. I have two entity (Parent and child) (they have Unidirectional association)
Parent =>
#Table(name = "parent")
#Entity
public class ParentEntity {
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
// other fields........
}
Child =>
#Table(name = "child")
#Entity
public class ChildEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NonNull
private String name;
#NonNull
#ManyToOne(fetch = FetchType.LAZY)
private ParentEntity parent;
// other fields........
}
I have two projection interface for select specific columns.
ParentProjection =>
public interface ParentProjection {
String getName();
Set<ChildProjection> getChild();
}
ChildProjection =>
public interface ChildProjection {
String getId();
String getName();
}
I want to take list of ParentProjection which includes with list of ChildProjection.
Repository query like that =>
#Query("select p.name as name, c as child from ParentEntity p left join ChildEntity as c on p.id = c.parent.id")
List<ParentProjection> getParentProjectionList();
This query works, but it selects all columns of ChildEntity, and map only id, name propeties to ChildProjection. (generated query selects all columns, but i want to select only id and name columns)
How can i select only id and name columns (select specific columns for nested list projection interface) and map to ChildProjection fields (using with #Query) ?
Note: I don't need to use class type projection.
You need to add the OneToMany relation to ParentEntity and annotate with Lazy.
Hope it helps (i have tried this).

How to filter a OneToMany field using Spring Data JPA?

I'm trying to filter out posts associated with a category depending on whether the post is set as hidden or not.
I can do this with a post-query filter just fine (see below) but I was wondering if it's possible to construct the query using the JPA methods? (Specifically the query building methods like FindAllBy..., I'm hoping to keep database agnostic by sticking to these types of queries)
I could also probably call FindAllByCategory on the PostRepository and construct the return that way but it feels hacky and backwards.
So to summarize I'd like to find a way to declare FindAllAndFilterPostsByIsHidden(boolean isHidden)
Category Class
#Entity
public class Category {
public Category(String name, Post... posts) {
this.name = name;
this.posts = Stream.of(posts)
.collect(Collectors.toSet());
this.posts.forEach(post -> post.setCategory(this));
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "category")
private Set<Post> posts;
}
Post Class (stripped to basics for brevity )
#Entity
public class Post {
public Post(Category category, boolean isHidden) {
this.category = category;
this.isHidden = isHidden
}
#ManyToOne
#JoinColumn
private Category category;
private boolean isHidden;
}
Right now I'm doing this to filter the posts associated with categories in the CategoryController
#GetMapping
public List<Category> list(Authentication authentication) {
boolean canViewHidden = securityService.hasAuthority(Permissions.Post.VIEWHIDDEN, authentication.getAuthorities());
List<Category> categories = categoryRepository.findAll();
categories.forEach(
category -> {
Set<Post> filteredPosts = category.getPosts().stream()
.filter(post -> canViewHidden || !post.isHidden())
.collect(Collectors.toSet());
category.setPosts(filteredPosts);
}
);
return categories;
}
I'd try using a custom query in your JPA-Repository for the Post Class like this:
#Query(value = "SELECT p FROM Post p INNER JOIN Category c ON p.id = c.post.id "
+ "WHERE p.hidden = false AND c.id = :id")
List<Post> findViewablePostsByCategory(#Param("id") Long categoryId);
I know this might not be the exact approach you were looking for but as K.Nicholas pointed out there is no way to use joins with the query building methods of JPA-Repositories.

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.

JPA or Play Framework list from query joining 2 tables

Mainly I work with JSF so am totally new to this annotation subject
If anyone can help
I wanna a list from this query
SELECT f.CODE ,f.NAME || '-' || e.NAME
FROM FS.ELIGIBLE e RIGHT
OUTER JOIN FS.FINANCIAL_SUPPORT f ON e.CODE = f.CODE ;
The query above retrieves a list from 2 tables and concatenating the name field from both tables!!
How can i do this in JPA or in play with another query supported by Play Framework ???
Have a read of the Play Framework documentation, specifically the part about JPA and your Domain Model.
You can access the entity manager at any time by calling
EntityManager entityManager = JPA.em();
Using this you can create any query that you want, even a "Native" Query. For example:
List<Object> results = JPA.em().createNativeQuery(
"SELECT f.CODE ,f.NAME || '-' || e.NAME "+
"FROM FS.ELIGIBLE e RIGHT "+
"OUTER JOIN FS.FINANCIAL_SUPPORT f ON e.CODE = f.CODE").getResultList()
JPA is not like relational database system which you can do your queries like join, left join or outer joins it is a mapping technology of objects. You can also do the same fetch just like those RDBMS counterparts but with a different approach.
What you have to do is make an Object then relate your second Object to your first Object, that is the proper way to relate 2 or more Objects. The Objects I'm talking about is your Table. See my example below:
Table1: Items.java
...
// do your imports here ...
// do your annotations here like
#Entity
#Table(name="Items")
public class Items implements Serializable {
private String id;
private String itemno;
private String description;
private Set<Vendors> vendors; //this is the 2nd table (1:n relationship)
...
// don't forget your constructor
// in your setter and getter for Vendors do the ff:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="id")
public Set<Vendors> getVendors() {
return vendors;
}
public void setVendors(Set<Vendors> vendors) {
this.vendors = vendors;
}
...
}
Table2: Vendors.java
#Entity
#Table(name="Vendors")
public class Vendors implements Serializable {
private long id;
private String company;
private String contact;
private String sequence;
...
public Vendors() { }
...
// in your setter & getter
#Id
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
...
}
Now on your query, just do a regular select as in the ff:
public void makeQuery(String seq) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory(...);
EntityManager em = emf.createEntityManager();
TypedQuery<Items> query = em.createQuery("
SELECT i, j.contact, j.company, j.sequence FROM Items i LEFT OUTER JOIN i.vendors j WHERE i.vendors.sequence = :seq
ORDER BY i.id", Items.class);
List<Items> items = query.setParameter("sequence", seq).getResultList();
...
}
Now you can refer to your 2nd table Vendors by using items.vendors.company ... and so on.
One disadvantage with this one is that, JPA make its related objects in the form of Set, see the declaration in Table1 (Items) - private Set vendors. Since it was a Set, the sequence is not in order it was received, unlike using List.
Hope this will help ...
Regards, Nelson Deogracias