How to query based on a collection of search texts - spring-data-jpa

I have a search function which can handle multiple search texts. Now I like to query my db for items which contain at least one of the search texts.
For one search text, I am using the following:
import org.springframework.data.jpa.repository.Query;
#Query("SELECT m FROM Material m WHERE m.productName LIKE %:searchText%")
For a collection of search texts, I tried:
#Query("SELECT m FROM Material m WHERE m.productName IN (:searchTexts)")
Problem I'm facing is, that now only the exact productName matches the query. But I'm looking for partial matches as well. Something like:
#Query("SELECT m FROM Material m WHERE m.productName IN (%:searchTexts%)")
Is there any way using JPQL or do I have to use QueryDSL?

After reading the possible duplication and many other threads I came across the "RLIKE" function of MySQL.
First of all, this is a MySQL function and as far as I know not part of the standard JPQL or HQL. To be able to use it, I created a custom MySQLDialect:
public class CustomMySqlDialect extends MySQLDialect {
public CustomMySqlDialect() {
super();
registerFunction("REGEX_LIKE", new SQLFunctionTemplate(BOOLEAN, "?1 RLIKE (?2)"));
}
}
And this is the #Query:
#Query("SELECT m FROM Material m WHERE function('REGEX_LIKE', m.productName, :searchTexts) = 1 "
List<Material> findByProductName(#Param("searchTexts") String searchTexts);
As a parameter one can use the search text strings joined by pipes.
List<String> searchTexts = Arrays.asList("abc", "xyz");
String joinedSearchTexts = String.join("|", searchTexts);
List<Material> searchResult = findByProductName(joinedSearchTexts);

Related

How to query for empty collection in hibernate search

We are using hibernate-search with ES back end. Parent class has multiple collection of different children objects. We can filter parents based on the value of the child as the collections are annotated with #IndexedEmbedded.
We want to be able to filter parents based on if the collection of children is empty or not. We have tried using #IndexedEmbedded(indexNullAs = "null"), and then filtering on queryBuilder.phrase().onField("parent.children").sentence("null").createQuery(), which has made no difference. Using the ES console we can show the parent, but when the collection is empty it isn't listed at all, leading us to believe it hasn't been indexed due to it being empty.
Another option would be to filter on parent.collection.field using a wildcard, however this would not be recommended by the hibernate search docs due to the performance.
If you upgrade to Hibernate Search 6, you will be able to use the exists predicate:
List<MyEntity> hits = Search.session(entityManager)
.search(MyEntity.class)
.where(f -> f.matchAll().except(f.exists().field(“parent.children”))
.fetchHits(20);
That would solve your problem, and then you can start worrying about performance in your specific case.
Still in Hibernate Search 6, if your tests show that performance of the first solution really is problematic, I would suggest using a custom bridge on children that indexes whether the collection is empty or not. Something like this:
#SuppressWarnings("rawtypes")
public class MyCollectionEmptyBridge implements ValueBridge<Collection, Boolean> {
#Override
public Boolean toIndexedValue(Collection value, ValueBridgeToIndexedValueContext context) {
return value == null || value.isEmpty();
}
}
public class MyParentEntity {
// ...
#GenericField(
name = "childrenAreEmpty",
valueBridge = #ValueBridgeRef(type = MyCollectionEmptyBridge.class),
// Apply the bridge directly to the collection and not to its elements
// See https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#_disabling_container_extraction
extraction = #ContainerExtraction(extract = ContainerExtract.NO)
)
private List<Child> children = new ArrayList<>();
}
List<MyEntity> hits = Search.session(entityManager)
.search(MyEntity.class)
.where(f -> f.match().field(“parent.childrenAreEmpty”).matching(true))
.fetchHits();
That second solution can be implemented with Hibernate Search 5 as well, though Hibernate Search 5's custom bridges are somewhat harder to work with.

JPA search query using Criteria Builder for multiple columns

I am trying to make a universal search for my entity with criteria builder where a given text is matched with all the columns in the entity.
String likeSearchText = "%" + searchText + "%";
List<Customer> searchedCustomers = null;
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Customer.class);
Root <Customer> root = query.from(Customer.class);
ArrayList<Predicate> conditions = new ArrayList<>();
conditions.add(builder.like(root.<String>get("firstName"), likeSearchText));
conditions.add(builder.like(root.<String>get("lastName"), likeSearchText));
conditions.add(builder.like(root.<String>get("middleName"), likeSearchText));
conditions.add(builder.like(root.<String>get("companyName"), likeSearchText));
conditions.add(builder.like(root.<String>get("industry"), likeSearchText));
query.where(builder.or(conditions.toArray(new Predicate[conditions.size()])));
query.select(root);
searchedCustomers = entityManager.createQuery(query).getResultList();
return searchedCustomers;
When I run this method I always get an empty list. I tried changing the like to notLike and that works perfectly fine by giving me a list containing elements which are not like the given search text so I am really confused as to what's wrong with my like method.
Any kind of help would be appreciated!
I had similar problems when I made some testing and had entities with the same (simple)name in the classpath. So for example there were entities like:
org.example.one.Customer
org.example.two.Customer
If you do not have explicitly set different table names like:
package org.example.one;
#Entity("customer_one")
public class Customer { ...
and
package org.example.two;
#Entity("customer_two")
public class Customer { ...
hibernate might:
mix stuff in the same table in db
try to find field from wrong table when constructing the query
Also I thibk you do not need this:
query.select(root);

JPA eclipselink global batch fetch?

when joining I get one select per row. Solution is batch fetch but I dont want that annotation everywhere...
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_batchfetch.htm
Why do I even need this? One select per row is awful... How can I set this globally? Cheers
Maybe not the ideal solution, but you may try to use JPA hints along with Java generics:
public <T> TypedQuery<T>
createBatchQuery(String ql, Class<T> clazz, String type, String size, String relation) {
return em.createQuery(jpql, clazz)
.setHint(QueryHints.BATCH_TYPE, type)
.setHint(QueryHints.BATCH_SIZE, size)
.setHint(QueryHints.BATCH, relation);
}
The above query may then be used globally and extended with concrete query implementations according to you needs, i.e.
String jpql = "SELECT c FROM Country c WHERE c.name = :name"; // or #NamedQuery
TypedQuery<Country> q = createBatchQuery(jpql, Country.class, "JOIN", "64", "c.cities");
q.setParameter("name", "Australia");
Country c = q.getSingleResult();
Articles on this topic:
Batch fetching - optimizing object graph loading
EclipseLink/Examples/JPA/QueryOptimization

JPA Entity with multiple resultsets having different columns

I am using JPA with derby database. I want to retrieve two different result sets from the same table onto two entirely different screens.
Screen one displays values using "ScannerReport" bean.
Screen two will display values from ScannerSummaryReport bean.
Both beans needs to have data from same Entity "Scanner" as mentioned in code below.
How to define that result set mapping onto the entity for two different result sets, having different columns in it
A ---> Query query = em.createNativeQuery(<query for scanner report goes here >,"ScannerReport");
query.getResultList();
Now A will execute and instantiate an object of ScannerReport class , fill it up with data .
B ---> Query query = em.createNativeQuery(<query for scanner summary report goes here>,"ScannerSummaryReport");
query.getResultList();
I want somehow to let JPA know that when i execute B, now it needs to instantiate an object of a different class lets say ScannerSummaryReport fill it up with data from A DIFFERENT QUERY ( written to calcuate averages and totals ) and return the result.Again please not both queries will be from same entity Scanner..
#SqlResultSetMapping(
name="ScannerReport",
classes={
#ConstructorResult(
targetClass=com.beans.ScannerReport.class,
columns={
#ColumnResult(name="scanYear", type=Integer.class),
#ColumnResult(name="julianDay", type=Integer.class),
#ColumnResult(name="scannerId", type=String.class),
#ColumnResult(name="startTime", type=Long.class),
#ColumnResult(name="endTime", type=Long.class),
#ColumnResult(name="scanTime", type=Long.class),
}
)
}
)
#Entity
public class Scanner {
// Class implementation goes here
}
So just define two SqlResultSetMappings, using the #SqlResultSetMappings annotation, one for each query. See http://www.datanucleus.org/products/accessplatform_4_0/jpa/annotations.html#SqlResultSetMappings for an example

How to call Named Query

I wrote a named query in the entity class Voter
NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password),
I want to call this named query and I also need to set voterID and password.
Can you help me. Thank you
I assume you've missed the # symbol on your NamedQuery annotation?
In the code, you'd call it like this:
List results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", "blah")
.setParameter("password","blahblahblah")
.getResultList();
There are two obvious issues with your named query that would cause a problems:
It is an annotation so it should be #NamedQuery not just NamedQuery
Your query is currently:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password.
The problem is that you terminate your String after :voterID, instead of after :password and you have "where" twice and you have a space between ":" and "password". Your query should look like this:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID and v.password= :password"
(I have just moved the " to the end and removed the second "where" and the space after the ":")
The common steps are (named query or otherwise)
Create a query - em has five create methods.
Set the query up with parameters if needed - the query interface has these methods.
Execute the query - the query interface has 3 execution related methods.
with the above three steps you can run any JPA query.
Actually brent is right your NameQuery should be something like this,
#NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID AND where v.password = :password")
#Entity
public class Voter implements Serializable{ ... }
and somewhere else you should try this one (which Dick has already said)
public class VoterFasade{
public List<Voter> findVoter(long id,String password){
List<Voter> results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", id)
.setParameter("password",password)
.getResultList();
return result;
}
}
then you could use it like
#Inject
VoterFasade voterFasade;
///
long id=12;
voterFasade.findVoter(id);
should actually working.(its an uncompiled code).
you could also do it with Repository, check the link below, part Repository Listing23.Example repository
enter link description here