Category tree structure with node count using JPA - jpa

I have a category tree structure, using JPA with eclipse link.
Each category in the tree includes items, and I want to select all categories along with their items count.
The code I inherited used to calculate the number of items offline, and update the category, and use the cool JPA trick for selecting the categories like so:
#Entity
#Table(name = "categories")
#NamedNativeQuery(name = "topLevel", query = "SELECT categories.* FROM categories WHERE categories.parent_id IS NULL")
public class Category {
#Id
private Long id;
private String name;
private Integer item_count;
private Long parent_id;
#JoinColumn(name = "parent_id")
private List<Categories> categories;
}
When running the native query - I get all the categories populated in the tree structure.
However, now I have a new filter that doesn't allow the user to see all the items in the category. So I can no longer calculate the items offline.
I tried to use a join so I can calculate it online, like this:
#NamedNativeQuery(name = "topLevel", query = "SELECT categories.*, count(*) as item_count FROM categories, items_to_categories WHERE categories.id = items_to_categories.category_id and some_user_specific_query and categories.parent_id IS NULL GROUP BY categories.id")
but this doesn't populate the lower levels with the right items count.
Since I am using Eclipse Link, I can't use a formula field that is not supported in this JPA implementation.
Any ideas on how to change my model in order to get it to work?
Thanks in advance!

If you want an Item count you can either query for it, such as using JPQL. This will return an Object[] including the Category and its Integer item count. If you want to use a native SQL query, then you will need to use a SqlResultSetMapping.
If you want the item count in Category, then you could define a database VIEW that makes this appear as a column and map to the view.
Another option is to map the items of the Category, then to get the item count, just access the size() of the items in Java (you could use join or batch fetching to load the items efficiently).

Related

Spring Data JPA Select Distinct dynamic columns based on list of string input

I want to select columns based on my input (List < String >)
#Entity
#Table
public class Product {
#Id
private Long id;
private String name;
private String price;
}
I understand that to select distinct specific columns, I can use #Query("SELECT DISTINCT name FROM TABLE")
However, I want to give users the flexibility to select the columns they want. e.g. List < String > columns = Arrays.asList(["name", "price"]). This will then select distinct from both name and price columns.
For this create a custom method implementation and use one of the following (or of the many variants thereof)
construct a SQL or JPQL query using String concatenation (be careful not to introduce SQL injection vulnerabilities).
use some kind of criteria API like
JPA Criteria API
Querydsl
JOOQ

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

Troubles with JPA criteria API and multiple subqueries

I am struggling with the JPA Criteria API for formulating a query for my data structure. Ok, my entities are as follows. I have users and groups (both share a common base class OrgEntity). Logically, users can be members in multiple groups of course. Finally, I have an entity representing a task, which has a list of potential owners (that can be either single users or whole groups). The domain model is summarized below and is given, so I cannot change it.
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
abstract public class OrgEntity {
#Id
public String name;
...
}
#Entity
public class User extends OrgEntity {
public String displayName;
#ManyToMany(mappedBy="members")
public List<Group> groups;
...
}
#Entity
public class Group extends OrgEntity {
#ManyToMany
public List<User> members;
...
}
#Entity
public class Task {
#Id
public String uuid;
#ManyToMany
public List<OrgEntity> potentialOwners;
...
}
The starting point for my query is a single instance of User. I want to know all the tasks where the user is a potential owner (regardless if the user is directly contained in the potentialOwners collection or member of a group that is contained in potentialOwners).
My first attempt using a named query was as follows
SELECT DISTINCT t FROM Task AS t JOIN t.potentialOwners po
WHERE (po IN (SELECT g FROM User u JOIN u.groups g WHERE u = :user)
OR po IN (SELECT u FROM User u WHERE u = :user))
It works, but I don't know if this is the most efficient way to do this. Any suggestions?
However, I have no idea how to implement this using the criteria API. Can somebody please help me with that.
Thanks
Ok, I finally figured out how to do it. If you are interested in my solution, here it is. u is the User object, basically the query parameter and em is the EntityManager instance.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
// specifies the result value of the query
CriteriaQuery<Task> cq = criteriaBuilder.createQuery(Task.class);
// start with the navigation at the task entity
Root<Task> from = cq.from(Task.class);
// join the potential owner organizational entities
Join<Task,OrgEntity> potentialOwners = from.join("potentialOwners");
// select the tasks but remove duplicates
CriteriaQuery<Task> select = cq.select(from).distinct(true);
// definition for subquery1: fetch the user instance
Subquery<User> subquery1 = cq.subquery(User.class);
// start at the User entities
Root<User> from1 = subquery1.from(User.class);
// select the whole user
subquery1.select(from1);
// based on the specified user
subquery1.where(criteriaBuilder.equal(from1, u));
// definition for subquery2: fetch all groups for given user
Subquery<Group> subquery2 = cq.subquery(Group.class);
// we start at the User entity
Root<User> from2 = subquery2.from(User.class);
// join to Group entities via the groups collection
Join<User, Group> groups = from2.join("groups");
// select the group entities only
subquery2.select(groups).distinct(true);
// and finally restrict to all groups of the specified user
subquery2.where(criteriaBuilder.equal(from2, u));
// order in descending order based on the unique task id
select.orderBy(criteriaBuilder.desc(from.get("uuid")));
// here we restrict to those tasks that have the potential
// owners either in the result set of subquery2 or subquery1
// additionally I've tried to filter for another restriction
// in the task (based on a like statement of the uuid)
select.where(criteriaBuilder.and(
criteriaBuilder.or(
criteriaBuilder.in(potentialOwners).value(subquery2),
criteriaBuilder.in(potentialOwners).value(subquery1)),
criteriaBuilder.like(from.<String>get("uuid"), "1%")));
TypedQuery<Task> typedQuery = em.createQuery(select);
List<Task> resultList = typedQuery.getResultList();

JPA: How to filter an association?

My model:
#Entity
class Person {
#Id
long id;
#OneToMany
Set<Employment> employments = new HashSet<Employment>();
}
#Entity
class Employment {
#Id
long id;
#ManyToOne
Company company;
Date from;
Date until;
}
It's an association between a Person and a Company that's restricted by a time interval.
I'm looking for a JPA criteria query to select all Persons and their employments at a given time.
The expected result is a List<Person> containing all people, where the collection employments of each Person contains only employments that match certain criteria (or no emplyments at all).
#Where is not a sensible approach because the filter criteria for employments is variable, e.g. I would want to select all employments of a person at any given time.
Is this even a reasonable thing to do? Any suggestions how to do this differently?
No, it's not a reasonable thing to do. An entity is supposed to represent the data that is in the database, and not the date returned by a particular query.
I would simply add a ManyToOne association from Employment to Person, and search for employments. If you need the set of persons, just iterate through the employments and add each employment's person to a Set. If you want the persons associated with their employments at this date, you could use a custom class, or a MultiMap<Person, Employment>.
String hql = "select employment from Employment employment"
+ " left join fetch employment.person"
+ " where :date between employment.startDate and employment.endDate";
List<Employment> employments = session.createQuery(hql)
.setDate("date", date)
.list();

named native query and mapping columns to field of entity that doesn't exist in table

I have a named native query and I am trying to map it to the return results of the named native query. There is a field that I want to add to my entity that doesn't exist in the table, but it will exist in the return result of the query. I guess this would be the same with a stored proc...
How do you map the return results of a stored proc in JPA?...
How do you even call a stored proc?
here is an example query of what I would like to do...
select d.list_id as LIST_ID, 0 as Parent_ID, d.description from EPCD13.distribution_list d
The Result will be mapped to this entity...
public class DistributionList implements Serializable {
#Id
#Column(name="LIST_ID")
private long listId;
private String description;
private String owner;
private String flag;
#Column(name="PARENT_ID", nullable = true)
private long parentID;
}
parent ID is not in any table in my database. I will also need to use this entity again for other calls, that have nothing to do with this call, and that will not need this parent_id? Is there anything in the JPA standard that will help me out?
If results from database are not required for further manipulation, just for preview, you can consider using database view or result classes constructor expression.
If entities retrieved from database are required for further manipulation, you can make use of multiple select expression and transient fields.
Replace #Column annotation with #Transient annotation over parentID.
After retrieving multiple columns from database, iterate over results and manually set parentID.