i am new to JPA and i have a problem with it.
suppose that we have two tables which are related
by a ManytoOne association, which means that
table A stores a primary key of table B within it.
when these two tables are mapped to JPA entities
i have a problem for search on this situation.
i have used an existing code from richfaces demo, to handle filtering and sorting by using
JPA. this code is using input parameters to create criteria query.
this is the code:
private CriteriaQuery<T> createSelectCriteriaQuery() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
Root<T> root = criteriaQuery.from(entityClass);
if (arrangeableState != null) {
List<Order> orders = createOrders(criteriaBuilder, root);
if (!orders.isEmpty()) {
criteriaQuery.orderBy(orders);
}
Expression<Boolean> filterCriteria = createFilterCriteria(criteriaBuilder, root);
if (filterCriteria != null) {
criteriaQuery.where(filterCriteria);
}
}
return criteriaQuery;
}
protected Expression<Boolean> createFilterCriteriaForField(String propertyName, Object filterValue, Root<T> root, CriteriaBuilder criteriaBuilder) {
String stringFilterValue = (String) filterValue;
if (Strings.isNullOrEmpty(stringFilterValue)) {
return null;
}
stringFilterValue = stringFilterValue.toLowerCase(arrangeableState.getLocale());
Path<String> expression = root.get(propertyName);
Expression<Integer> locator = criteriaBuilder.locate(criteriaBuilder.lower(expression), stringFilterValue, 1);
return criteriaBuilder.gt(locator, 0);
}
private Expression<Boolean> createFilterCriteria(CriteriaBuilder criteriaBuilder, Root<T> root) {
Expression<Boolean> filterCriteria = null;
List<FilterField> filterFields = arrangeableState.getFilterFields();
if (filterFields != null && !filterFields.isEmpty()) {
FacesContext facesContext = FacesContext.getCurrentInstance();
for (FilterField filterField : filterFields) {
String propertyName = (String) filterField.getFilterExpression().getValue(facesContext.getELContext());
Object filterValue = filterField.getFilterValue();
Expression<Boolean> predicate = createFilterCriteriaForField(propertyName, filterValue, root, criteriaBuilder);
if (predicate == null) {
continue;
}
if (filterCriteria == null) {
filterCriteria = predicate.as(Boolean.class);
} else {
filterCriteria = criteriaBuilder.and(filterCriteria, predicate.as(Boolean.class));
}
}
}
return filterCriteria;
}
the code is okay, when i try to filter columns(not joined columns), but when i try to
query on joined column, the produced query is not correct and it throws exception.
so my question is that, how could i use JPA criteria query api, to filter rows by both
joined columns and non-joined coulmns.
thanks
I don't believe you can treat join columns like regular ones.
for example if you want to filter on id of B, you would have to create a join from A to B , then use B_.id to match values.
Shay
Related
In a spring boot 3 application I try to use specification
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
...
return pre;
};
return findAll(specification, page);
}
I need for every value in splittedValues to add a global specification
for (String splittedName: splittedValues) {
specification.or(splittedName);
}
and pass it to findAll
I don't understand how to do it
Edit
you solution seem to work but
that generate
where
1=1
or like e1_0.name "%bob%"
or like e1_0.name "%jame%"
It's there a way to get
where
1=1
and (
like e1_0.name "%bob%"
or like e1_0.name "%jame%"
)
Using or operator , you combine multiple conditions, checking whether the name of User contains one of the splittedValues. Predicate is then returned from specification.
I hope that was what you wanted, in case I didn't understand, please correct me. Here is an example if so, adapt to your own code.
Edit: To get 1=1 and (like e1_0.name "%bob%" or like e1_0.name "%jame%"), you can use cb.or method to combine the conditions, resulting nameConditions is then combined with pre using cb.and.
public Page<User> advancedSearch(UserSearch search, Pageable page) {
String[] splittedValues = search.name.split(" ");
Specification<User> hasPersonWithName = (Root<User> root, CriteriaQuery<?> cq, CriteriaBuilder cb) -> {
Predicate pre = cb.conjunction();
Predicate nameConditions = null;
for (String splittedName: splittedValues) {
if (nameConditions == null) {
nameConditions = cb.like(root.get("name"), "%" + splittedName + "%");
} else {
nameConditions = cb.or(nameConditions, cb.like(root.get("name"), "%" + splittedName + "%"));
}
}
if (nameConditions != null) {
pre = cb.and(pre, nameConditions);
}
return pre;
};
return findAll(hasPersonWithName, page);
}
I need a query which will return only results which are having all values from the list.
Something like:
SELECT *
FROM ads_tags
WHERE tag_value("a", "b", "c");
I know that query is not good, but the point is that I want to filter ads but only ads which are having all tags from the list.
With my code, I am getting all ads that have at least one tag from the list. That is because I am using IN interface.
#Override
public List<AdsDTO> findAll(AdsSubGroup adssubgroup, Long userId, String status, String adsType,
String businessType, Long adsGroupId, String region, Integer fromPrice,
Integer toPrice, Boolean fixedPrice, Boolean freeDelivery, Boolean productWarranty,
Boolean urgentSales, Boolean hasImage, Integer pageNumber, Integer pageSize, List<String> tags) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Ads> query = builder.createQuery(Ads.class);
Root<Ads> ads = query.from(Ads.class);
// query.orderBy(builder.desc(ads.get("adsDate")));
List<Predicate> predicates = new ArrayList<>();
Join<Ads, JwtUser> adsUsersJoin = ads.join("users");
Join<Ads, AdsSubGroup> adsAdsSubGroupJoin = ads.join("adssubgroup");
Join<Ads, Tag> tagsJoin = ads.join("adsTags");
In<List<String>> in = builder.in(tagsJoin.get("name"));
if (tags != null && tags.size() > 0) {
// in.value(tags);
tags.forEach(tag - > in.value(tags));
/*
* for (String tag : tags) { in.value(tag);
*
* }
*/
predicates.add(in);
}
query.select(ads);
query.distinct(true);
query.where(predicates.toArray(new Predicate[0]));
if (!(pageNumber == null && pageSize == null)) {
TypedQuery<Ads> typedQuery = em.createQuery(query);
typedQuery.setFirstResult((pageNumber - 1) * pageSize);
typedQuery.setMaxResults(pageSize);
List<Ads> adsList = typedQuery.getResultList();
return AdsConverter.convertToAdsDTO(adsList);
} else {
List<Ads> adsList = em.createQuery(query).getResultList();
return AdsConverter.convertToAdsDTO(adsList);
}
}
What I need to write instead of In interface to get all ads which have ALL tags from the list, not at least one?
You need a join for each element of type Tag in your list.
It's hard to fix your code because it's not a minimal example, however the following should help:
CriteriaQuery<Ads> query = builder.createQuery(Ads.class);
Root<Ads> ads = query.from(Ads.class);
List<Predicate> predicates = new ArrayList<>();
for (String tag : tags) {
Join<Ads, Tag> tagsJoin = ads.join("adsTags");
predicates.add(builder.equal(tagsJoin.get("name"), tag));
}
Had to write a jpql query, based on the input need to add and condition and for some input had to need JOIN queries.
#Override
public List<IncidentHdr> fetchIncidents(IncidentHdrDto incidentHdrDto) {
StringBuilder query = new StringBuilder();
query.append(ReposJPQL.GET_INCIDENT_DETAILS);
Map<String, Object> parameters = new HashMap<String, Object>();
List<String> criteria = new ArrayList<String>();
if(incidentHdrDto.getIncidentId() > 0) {
criteria.add("inc.incidentId = :incidentId");
parameters.put("incidentId", incidentHdrDto.getIncidentId());
}
if(incidentHdrDto.getCatCode() > 0) {
criteria.add("inc.catCode = :catCode");
parameters.put("catCode", incidentHdrDto.getCatCode());
}
if(incidentHdrDto.getType != null) {
//here i need to generate a join query
//SELECT * FROM INCIDENT JOIN CATEGORY_MAST ON(INCIDENT.CAT_CODE = CATEGORY_MAST.CAT_CODE) WHERE CATEGORY_MAST.TYPE_CODE = 16
}
Query q = em.createQuery(query.toString());
logger.info("Get Incidents Query : "+query.toString());
for (Entry<String, Object> entry : parameters.entrySet()) {
q.setParameter(entry.getKey(), entry.getValue());
}
List<IncidentHdr> incidentHdrs = q.getResultList();
return incidentHdrs;
}
where as ReposJPQL is the base query which had a where condition.
public interface ReposJPQL {
public String GET_INCIDENT_DETAILS = "SELECT inc FROM IncidentHdr inc WHERE 1 = 1" ;
}
I have created two new fields named "Price" for quote and quote product and I want to update the second every time I update the first.
Here is my code:
protected void ExecutePostAccountUpdateContacts(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
string oldPrice = "";
string newPrice = "";
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
var ServiceContext = new OrganizationServiceContext(service);
ITracingService tracingService = localContext.TracingService;
if (context.InputParameters.Contains("Target") &&
context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
Entity preImageEntity = (context.PreEntityImages != null && context.PreEntityImages.Contains(this.preImageAlias)) ? context.PreEntityImages[this.preImageAlias] : null;
// get the post entity image
Entity postImageEntity = (context.PostEntityImages != null && context.PostEntityImages.Contains(this.postImageAlias)) ? context.PostEntityImages[this.postImageAlias] : null;
if (preImageEntity.Attributes.Contains("Price"))
{
oldPrice = (string)preImageEntity.Attributes["Price"];
}
if (postImageEntity.Attributes.Contains("Price"))
{
newPrice = (string)postImageEntity.Attributes["Price"];
}
if (newPrice != oldPrice)
{
try
{
//Create query to get the related contacts
var res = from c in ServiceContext.CreateQuery("Products")
where c["parentQuoteid"].Equals(entity.Id)
select c;
foreach (var c in res)
{
Entity e = (Entity)c;
e["Price"] = newPrice;
ServiceContext.UpdateObject(e);
}
ServiceContext.SaveChanges();
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("An error occurred in the plug-in.", ex);
}
}
}
}
Although you haven't asked a question, your query isn't quite right. So I am assuming your plugin fails when querying for product with a parentquoteid.
Not all linq operators are implemented, also , pass the entity logical name to the create query as a parameter, so instead of Products, just product. There is no out of the box field called parentquoteid, are you missing your custom attribute prefix?
var res = from c in ServiceContext.CreateQuery("product")
where c.GetAttributeValue<Guid>("new_parentquoteid") == entity.Id
select c;
After poking around Stack Overflow I found the following solution for counting problem. My requirement is to get the total number of matching rows, and return the first ten for pagination purposes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(counterCq.from(clazz)));
Predicate predicate= null;
Predicate predicate1 = null;
Root<T> root = cq.from(clazz);
for (Map.Entry<String, String> e : filters.entrySet()){
predicate = cb.and(cb.like(root.<String>get(e.getKey()), e.getValue()+ "%"));
}
if(predicate != null){
cq.where(predicate);
counterCq.where(predicate);
}
int pn = ( em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
if(sortField !=null && !sortField.trim().equals("")){
if(sortOrder == SortOrder.DESCENDING){
cq.orderBy(cb.desc(root.get(sortField)));
} else{
cq.orderBy(cb.asc(root.get(sortField)));
}
}
Query q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> cats= (List<T>)q.getResultList();
This snippet makes hibernate to through
java.lang.IllegalArgumentException: org.hibernate.hql.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.title' [select count(generatedAlias0) from Media as generatedAlias0 where generatedAlias1.title like :param0]
It seems like cq.from(clazz) cannot be applied for the other query.
Now my question: Is there a way to use the same predicate in both queries?
Your predicate list isn't assembled correctly. You have to 'and' predicates together into a single expression. I also prefer to build my predicate before performing the select for better readability.
Here's a refactor of your code to achieve the correct results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(clazz);
Root<T> root = cq.from(clazz);
// build predicate list - conjuction starts us with an empty 'and' predicate
Predicate predicate = cb.conjunction();
for (Map.Entry<String, String> e : filters.entrySet()) {
predicate = cb.and(predicate, cb.like(root.get(e.getKey()), e.getValue() + "%"));
}
// query total count
CriteriaQuery<Long> counterCq = cb.createQuery(Long.class);
counterCq.select(cb.count(root)).where(predicate);
int pn = (em.createQuery(counterCq).getSingleResult()).intValue();
logger.debug("number of pages is {}", pn);
setRowCount(pn);
// query results
cq.select(root).where(predicate);
if(sortField !=null && !sortField.trim().equals("")) {
if(sortOrder == SortOrder.DESCENDING) {
cq.orderBy(cb.desc(root.get(sortField)));
}
else {
cq.orderBy(cb.asc(root.get(sortField)));
}
}
TypedQuery<T> q = em.createQuery(cq);
q.setFirstResult(first);
q.setMaxResults(first+ps);
List<T> list = q.getResultList();