I'm using Eclipselink and have a tricky problem regarding JPA NamedQueries.
My database table contains a column which is from type VARCHAR and stores a comma separated list of keywords as one String.
How can I create a NamedQuery in JPA to search theese keywords?
I'd like to give a list of Strings as a parameter and as a result I'd like to have a list of objects which keyword list contain one of the Strings from the parameter list.
Maybe like the following:
List<String> keywordList = new ArrayList<String>();
keywordList.add("test");
keywordList.add("car");
List<Object> result = em.createNamedQuery("findObjectByKeywords", Object.class)
.setParameter("keywords", keywordList)
.getResultList();
Unfortunately I'm not such a big database/SQL expert. Maybe someone of you can help me?
I hope you understand my problem.
Edit:
I am developing on Weblogic 10.3.6, which means I am not able to use JPA 2.0 features.
Edit2:
I managed to activate JPA 2.0 in my Weblogic Server with the help of Oracle Enterprise Pack for Eclipse. Problem solved, I think.
VALID FOR JPA2.0
As Bhesh commented a simple JPQL won't make it. The resulting SQL has to contain a where clause similar to following:
where keywords like '%keyword1%' or keywords like '%keyword2%' or ... or keywords like '%keywordN%'
This means: We need a loop here!
You could try to build a JPQL by yourself like Bhesh suggested in his first comment, though as he also stated it is not a brilliant idea. But don't worry - JPA provides also a Criteria API which comes handy in such situations. So, although you're not going to have a named query, you can still make it with JPA this way:
public List<YourEntity> findAllByKeywords(List<String> keywords){
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
List<Predicate> predicates = new LinkedList<>();
for (String keyword : keywords) {
predicates.add(builder.like(root.<String>get("keywords"), "%" + keyword + "%"));
}
return entityManager.createQuery(
query.select(root).where(
builder.or(
predicates.toArray(new Predicate[predicates.size()])
)
))
.getResultList();
}
or (always slightly better with Guava)
public List<YourEntity> findAllByKeywords(List<String> keywords){
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
final Root<YourEntity> root = query.from(YourEntity.class);
return entityManager.createQuery(
query.select(root).where(
builder.or(
transform(keywords, toPredicateFunction(builder, root)).toArray(new Predicate[]{})
)
))
.getResultList();
}
private Function<String, Predicate> toPredicateFunction(final CriteriaBuilder builder, final Root<YourEntity> root) {
return new Function<String, Predicate>() {
#Override
public Predicate apply(String input) {
return builder.like(root.<String>get("keywords"), "%" + input + "%");
}
};
}
Related
How do I dynamically create "OR" predicates if I have a List<List<String>>
I am using query dsl and spring data.
QOrder order = QOrder.order;
JPQLQuery<Order> query = from(order);
query.where(order.status.eq("ready"));
List<List<String>> filterTypes;
This is what I am trying to do:
for(List<String> types : filterTypes) {
query.where(order.type.in(types));
}
So the result should be something like
select * from order o where o.status='ready' and (o.type in(t1,t2) or o.type in(t3,t4))
To directly answer your question: assuming you're using a relatively modern version of QueryDSL, you should be able to use a BooleanBuilder:
QOrder order = QOrder.order;
SQLQuery<Order> query = from(order);
query.where(order.status.eq("ready"));
// Example data
List<List<String>> filterTypes = ImmutableList.of(
ImmutableList.of("t1", "t2"),
ImmutableList.of("t3", "t4"));
BooleanBuilder builder = new BooleanBuilder();
for (List<String> types : filterTypes) {
builder.or(order.type.in(types));
}
query.where(builder);
Backing up, assuming your actual application data model is similar to the example you provided, this:
o.type in (t1,t2) or o.type in (t3,t4)
Is equivalent to:
o.type in (t1,t2,t3,t4)
You could translate your List<List<String>> into List<String> and do your type query update once:
QOrder order = QOrder.order;
SQLQuery<Order> query = from(order);
query.where(order.status.eq("ready"));
// Example data
List<List<String>> filterTypes = ImmutableList.of(
ImmutableList.of("t1", "t2"),
ImmutableList.of("t3", "t4"));
List<String> flatFilterTypes = filterTypes.stream().flatMap(List::stream).collect(Collectors.toList());
query.where(order.type.in(flatFilterTypes));
I suspect that your database's query optimizer would do the same thing for either query (you'd have to check a query execution plan to be sure), but it'd probably be more clear what's going on if you did simplified the query on the Java side rather than relying on the database query optimizer.
I want to get distinct values by a parameter:
#Transactional
public List<data> getAllFromColumn(String identifier) {
List<data> resultList = em.createQuery("SELECT DISTINCT p.market FROM data p", Data.class).getResultList();
return resultList;
}
My problem is that this only returns me a NullPointerException. Any recommendations what is wrong, or what I can do differently?
I appreciate your answer!
If HQL doesn't support select distinct (which it doesn't seem to according to the syntax), you can do this using group by:
SELECT p.market
FROM data p
GROUP BY p.market;
I am a newbie to hibernate search.
I am trying to implement a engine in which the results are exact matches.
My persistence class changes
#Field(index=Index.UN_TOKENIZED, store=Store.NO)
private String offerTitle;
Implementation changes
String[] offerFields = new String[] { "offerTitle"};
MultiFieldQueryParser parser = new MultiFieldQueryParser(Version.LUCENE_31,offerFields,new KeywordAnalyzer());
org.apache.lucene.search.Query query = parser.parse(queryString);
org.hibernate.Query offerHibQuery = fullTextSession.createFullTextQuery(query, Offer.class);
List<?> offerResults = offerHibQuery.list();
It works fine until the search term has space in it. When search term contains space, it gets no results.
For example: For the search term: "Comcast offer name" I get no results, and the query is broken into offerTitle:Comcast offerTitle:offer offerTitle:name
Is there any way to search for exact match with spaces?
I tried this:
QueryBuilder queryBuilder_1 = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Offer.class).get();
org.apache.lucene.search.Query offerCode_1 = queryBuilder_1.phrase().onField("offerTitle").sentence(queryString).createQuery();
org.hibernate.Query offerCodeHibQuery = fullTextSession.createFullTextQuery(offerCode_1);
List<?> offerCodeResults = offerCodeHibQuery.list();
It is still not working.
You are not tokenizing the field, so you need to search with a single, unanalyzed term. The simplest way to do that, I find, is to go straight to the Lucene APIs and just construct a TermQuery, like:
Query query = new TermQuery(new Term("offerTitle", queryString));
I think this should also work:
queryBuilder_1.keyword().onField("offerTitle").matching(queryString).createQuery();
queryBuilder_1.phrase().withSlop(0)
.onField("offerTitle").sentence(queryString).createQuery()
I came accross an interesting behaviour: probably criteria API puts single quotes around parameters in query.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<SomeClass> criteriaQuery = criteriaBuilder.createQuery(SomeClass.class);
Metamodel metamodel = em.getMetamodel();
EntityType<SomeClass> entityType_ = metamodel.entity(SomeClass.class);
Root<SomeClass> root = criteriaQuery.from( SomeClass );
criteriaQuery.select(root.get(entityType_.getSingularAttribute( "someField" ));
TypedQuery<SomeClass> q = em.createQuery(criteriaQuery);
List<SomeClass> result = (List<SomeClass>) q.getResultList();
this snippet results in a list with one column which is full of with "someField" in every single cells of the column. (select 'someField' from SomeClass; <--really works )
The select accepts this behaviour by criteria, however the group by fails, says: ORA-00979: not a GROUP BY expression.
I only think about criteria does the same substitution, like in case of select.
criteriaQuery.groupBy(root.get(entityType_.getSingularAttribute("someField") ));
How can I avoid those single qoutes in my queries?
Any suggests appreciated, thank You in advance.
I've created a Repository that extends CrudRepository,
this repository has a method with an #Query notation:
Code:
#Query("select itemType, count(*) as count from Item where User_id = :userId group by itemType")
List<Map<String, Long>> countItemsForUser(#Param("userId") Long userId);
The issue I'm having is that this return a ArrayList of Object(s) and not a List of Map.
I've read somewhere that JPA can't return a Map so that's why I stuff the result in a List>.
I don't know what's the best way to work around this issue or to quickly access the result data.
I've tried casting but that didn't work out either:
for(Object item: items) {
Map<String,Long> castedItem = (HashMap<String,Long>)item;
}
See this example in official documentation of Hibernate.Here
for (Object item:items) {
Object[] tuple = (Object[]) item;
String itemType = (String)tuple[0];
Long count = (Long) tuple[1];
}
Most simple way is to use interface. To let Spring wire query alias
to the interface getter. Example can be found here: https://www.baeldung.com/jpa-queries-custom-result-with-aggregation-functions
also there is #SqlResultSetMapping. See:
JPA- Joining two tables in non-entity class