How to retrieve selected items from a list of items? - java

In my application, user is able to select few items and see the details of all.
I know that I can use the Criteria and using its Restrictions.disjunction(), I can define the
"OR"; however, I need to retrieve the id of selected items from a list which makes it difficult to create the query using criteria.
Criteria cre = session.createCriteria(Category.class,"category");
cre.add(Restrictions.disjunction()) //?????
for(int i=0;i<selection.size();i++){
cre.add(Restrictions.eq("category.items",selection.get(i));
}
....
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#LazyCollection(LazyCollectionOption.FALSE)
public List<CategoryItem> getItems() {
return this.items;
}
Another method is to send separate queries which I reckon thats an inefficient approach.

As Ean mentioned just use the following:
from Category c join c.items i where i.id in :yourList

Related

How to remove an element from a Set inside a Page of items based on a Condition using Java 8

I have a Page of Farmers coming from a JPA repository call findAll method. Each Farmer consists of a Set of Farms which are also coming in the repository call like so:
public class Farmer {
private String name;
#ManyToMany
#JoinTable(name = "farmer_asset", joinColumns = #JoinColumn(name = "farmer_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "asset_id", referencedColumnName = "id"))
private Set<Asset> assets;
}
Asset has a property called isActive which indicates that the asset is active or not;
In my Page of Farmers I want to select only farmers' Assets which are active;
I tried with
#WhereJoinTable(clause = "is_active ='true'")
However, I learnt that the is_active property is supposed to be in the intermediate ManyToMany relation Table farmer_asset
Now I am not in a position to change the entity structure, and use #WhereJoinTable(clause = "is_active ='true'").
So, I thought of trying to filter the values after making the JPA call.
However, iterating through each Farmer, then removing the inActive Assets and then adding it back to the Page and maintaining Page elements seemed too performance heavy operation.
I tried with normal Java Code which looks something like this:
Page<Farmer> farmers = farmerRepository.findAll(pageable);
List<Farmer> farmersList = new LinkedList<>();
for (FarmerDto eachFarmer : farmers.getContent()) {
Set<Asset> eachFarmerAssets =
eachFarmer.getAssets();
eachFarmerAssets.removeIf(e ->
!e.getIsActive());
eachFarmer.setAssets(eachFarmerAssets);
farmersList.add(eachFarmer);
}
return new PageImpl<FarmerDto>(farmersList, pageable, farmers.getTotalElements());
I would like to do this is Java 8 using streams but I am not able to iterate a list and change its element which is in turn a Set<Asset>
Came here looking for some suggestions.
Thanks in advance.
You actually don't to reassign the same reference to Set<Asset> because even the set has been modified, it's still the same object which is "known" to a particular farmer.
And definitely you don't need streams for this task. Documentation suggests avoiding stateful operations and side-effects while designing stream pipe-lines.
Instead, you can use forEach() method on a list:
Page<Farmer> farmers = farmerRepository.findAll(pageable);
List<Farmer> farmersList = new farmers.getContent();
farmersList.forEach(farmer -> farmer.getAssets()
.removeIf(e -> !e.getIsActive()));
You have to follow these steps:
retrieve the list of farmer entities
transform the list of entities to the list of farmer dtos
for each farmer dto
fill the data
put the list of assert that are active
List<Farmer> farmers = farmerRepository.findAll(pageable).getContent(); //1
List<FarmerDto> farmersList = farmers.stream() // 2
.map(farmer -> { // 3
FarmerDto farmerDto = new FarmerDto();
// fill farmerDto 3.1
farmerDto.setAssets(farmer.getAssets().stream().filter(Farmer::getIsActive()).toList()); //3.2
return farmerDto;
}).toList();
return new PageImpl<FarmerDto>(farmersList, pageable, farmers.getTotalElements());

Query using specifications gives error when ordering on joined column that is not in SELECT DISTINCT

I get following error when doing a rather complicated query: for SELECT DISTINCT, ORDER BY expressions must appear in select list
In the query I need to find all distinct Requests that have an ExploitationSite that contains a search term in their dutch or french name. The result has to be ordered by the Activity's dutch name and limited to the first 10 for pagination.
To do this query I use the Page <T> findAll(Specification<T> spec, Pageable pageable) method of JpaSpecificationExecutor.
This will result in a SELECT DISTINCT query which has to be ORDERed BY a property that is not in SELECT. (details below)
I tried to fetch the activities eagerly in the hope it would place those differently in the SELECT. I did my best trying to get the DISTINCT in a subquery and then have the ORDER BY + LIMIT around that, but I did not succeed in that.
Has someone an idea how I can get this query to work?
The (simplified) Request entity
#Entity
#Table(name = "request_requests")
#History("Request")
public class Request extends EqualByStateObject {
#GeneratedValue
#Id
private int id;
#Embedded
private RequestNumber requestNumber;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "fk_request")
private List<ExploitationSite> exploitationSites = new ArrayList<>();
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(unique = true, name = "fk_activity")
private Activity activity;
...
}
The Specification (I have to use distinct here because since a Request contains a List of ExploitationSites it was possible I got the same request multiple times back if multiple ExploitationSites contained the search term)
public class ExploitationSiteSpecification extends EqualByStateObject implements Specification<Request> {
private final String exploitationSiteName;
protected ExploitationSiteSpecification(String exploitationSiteName) {
this.exploitationSiteName = exploitationSiteName;
}
#Override
public Predicate toPredicate(Root<Request> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
query.distinct(true);
ListJoin<Object, Object> exploitationSites = root.joinList("exploitationSites");
return criteriaBuilder.or(
criteriaBuilder.like(
criteriaBuilder.lower(exploitationSites.get("name").get("dutchName")), "%" + exploitationSiteName.toLowerCase() + "%"),
criteriaBuilder.like(
criteriaBuilder.lower(exploitationSites.get("name").get("frenchName")), "%" + exploitationSiteName.toLowerCase() + "%")
);
}
}
The Pageable
public Pageable getPageable() {
Sort sort = Sort.by(Sort.Order.asc("activity.name.dutchName"));
PageRequest.of(0, 10, sort);
}
This results in a generated query like this one
select distinct request0_.id as id1_23_,
request0_.fk_activity as fk_acti15_23_,
request0_.request_number as request12_23_
from request_requests request0_
inner join request_exploitation_sites exploitati1_ on request0_.id=exploitati1_.fk_request
left outer join request_activity activity2_ on request0_.fk_activity=activity2_.id
where lower(exploitati1_.dutch_name) like $1
or lower(exploitati1_.french_name) like $2
order by activity2_.dutch_name asc limit $3
which then gives the for SELECT DISTINCT, ORDER BY expressions must appear in select list error
Assuming you put the distinct because the join with exploitationSites would return multiple rows, the following two options would work without using distinct.
right after the join you could do an additional fetch
ListJoin<Object, Object> exploitationSites = root.joinList("exploitationSites");
root.fetch("exploitationSites")
this would result in hibernate to create an additional join of ExploitationSites as well as selecting additional columns
select request0_.id as id1_23_,
request0_.fk_activity as fk_acti15_23_,
request0_.request_number as request12_23_,
exploitati3_.id as exploitati3_id,
exploitati3_.name as exploitati3_name,
...
from request_requests request0_
inner join request_exploitation_sites exploitati1_ on request0_.id=exploitati1_.fk_request
left outer join request_activity activity2_ on request0_.fk_activity=activity2_.id
inner join request_exploitation_sites exploitati3_ on request0_.id=exploitati3_.fk_request
where lower(exploitati1_.dutch_name) like $1
or lower(exploitati1_.french_name) like $2
order by activity2_.dutch_name asc limit $3
use fetch in the first place and cast it to Join
Join<Object, Object> exploitationSites = (Join<Object, Object>) root.fetch("exploitationSites");
By casting the Fetch to a Join you can still use where clauses.
Note that this will also select additional columns, but won't do an additional join in the resulting query.
In both cases the fetch will result in a join fetch which hibernate internally will remove duplicates from the parent entity (see https://stackoverflow.com/a/51177569)

Returning result from 3 tables using JPA criteria

I have the following situation:
#Entity
public class Period
{
String Name;
}
#Entity
public class Bill
{
Period period;
#OneToMany(mappedBy = "bill", fetch = FetchType.LAZY)
private List<Entry> entry = new ArrayList<Entry>(0);
}
#Entity
public class Entry
{
#NotNull
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BILL_ID", nullable = false)
Bill bill;
String text;
BigDecimal amount;
}
So what I need is to fetch all the data in a single query, either with the root being the Bill or the Entry using JPA 2.0 criteria (with Hibernate behind). I've read few posts about this problem HERE and HERE and it seems that I can't use subqueries in the result or fetch data two levels deep.
EDIT: To make my problem more clear: When I use Entry as root, I can't fetch Period and when I use Bill as root I can't fetch all other tables in Entry. Also I can't use eager fetch because there are other use cases that need those tables.
Are there any other ways to do this?
Thanks!
To fetch data from association, you use left join fetch clauses:
select distinct b from Bill b
left join fetch b.period
left join fetch b.entry
where b...
or
select distinct e from Entry e
left join fetch e.bill b
left join fetch b.period
where e...
Regarding Criteria, its fetch() method returns a Fetch, which itself has a method fetch() returning a Fetch(), which itself has a method fetch() returning a Fetch, etc. So yes, its supports as many levels you want.

how to return criteria Distinct Result

I have Bill and Bill_Details with onetomany and manytoone Relationship. I need a help in getting Bill List.
Pojo
Bill
#OneToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
#JoinColumn(name = "bill_id")
private Set<BillDetails> billDetails = new HashSet<BillDetails>();
BillDetails
#ManyToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
#JoinColumn(name = "bill_id")
private Bill billId;
I am using Projections to take Bill value from List.
DaoHibernate
#Transactional
public List<Bill> getbillDetailsByBillId(String billId) {
Criteria cr = null;
try {
cr = getSession().createCriteria(Bill.class,"bill")
.createAlias("bill.billDetails","billDetails")
.setProjection(Projections.projectionList()
// I tried .setProjectionProjections.distinct(Projections.projectionList()
.add(Projections.property("billNo"),"billNo")
.add(Projections.property("billDetails.amount"),"billDetails.amount")
.add(Projections.property("billDetails.rate"),"billDetails.rate"))
.add(Restrictions.eq("id", billId))
.setResultTransformer(new AliasToBeanNestedResultTransformer(Bill.class));
} catch (Exception e) {
System.out.println("Get bill DetailsByBillId Error----------"+e);
e.printStackTrace();
}
System.out.println(cr.list().size());
return cr.list();
}
Note:
-- > Bill table contains single row
-- > BillDetails table contains four row for this BillId
My criteria Query Returns four Objects instead of single Object. I also tried with distinct feature.
Expected Output :
I need single object that contains BillDetails Objects(4 Values).
ie.I explained with sample Json format below
{billNo:231,
billDetails[{amount:100,rate:1}{amount:200,rate:2}
{amount:300,rate:30}{amount:400,rate:4}] }
How to get this by Hibernate criteria Query ? Please Help
First of all, your mapping isn't correct. You have a bidirectional association, and one of the side (the one side) must be the inverse of the one side:
#OneToMany(mappedBy = "billId", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<BillDetails> billDetails = new HashSet<BillDetails>();
You should also rename the field billId to bill, given that what it contains is a bill, and not a bill ID.
Now, the problem with your query is that you're using projections for no reason. When using projections, you deliberately choose to return rows containing individual columns. And since the SQL query returns 4 rows you get back 4 bills: one for each row.
You're also making your life unnecessarily complicated by using a Criteria query instead of HQL, which is much more suited for such simple static queries.
But even a HQL query is useless here, since you simply want to get a bill from its ID. All you need is
Bill bill = (Bill) session.get(Bill.class, billId);
This will get the bill, and since you chose to make the OneToMany association as EAGER, it will also immediately load its bill details.
If you hadn't made the association EAGER (and you should really leave it as LAZY), you could use this simple HQL query to load a bill with its details:
select distinct b from Bill bill
left join fetch bill.billDetails
where bill.id = :billId

How to set large collection to entity with best performance by JPA

Let's say if there is a Owner class and an Item class, they are in a #ManyToMany relation through an association table.
public class Owner{
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name = "OWNERITEM",
joinColumns = #JoinColumn(name = "USERID", referencedColumnName="USERID"),
inverseJoinColumns = #JoinColumn(name = "ITEMID", referencedColumnName="ITEMID")
)
private List<Item> items= new ArrayList<Item>();
}
Assume there are other fields/mapping relations under Item class, and now if there is case require to add all items(say 2000 or 10000 items) to a specific Owner object, then the code must be
// we have to to question the performance here...
Owner owner = someService.getOwner(Parameters... someParameters);
List<Item> all_items = itemService.findAll();
owner.setItems(all_items);
See above process, it is not very efficient, especially when Item class has(even lazy fetch) relations to other class, then there will be many, many unnecessary queries to pull those data into 10 thousands Item objects. But it is just the Item.id that we need to insert into Owner-Item relations to map all items to the user.
What I can think of is to register Item itemId instead of Item object into Owner class, but that seems make the coding a lot more complicated as we have to develop method to pull all item information.
Is there any better alternative to guarantee a good performance?
Use modifying repository query, like this:
http://docs.spring.io/spring-data/jpa/docs/1.0.0.M1/reference/html/#jpa.modifying-queries
for example:
#Query("update Item i set i.owner = ?1")

Categories