For my web app I have to implement full text search - as I'm already using Hibernate with Spring Data JPA (on top of Spring Boot) I decided to implement Hibernate Search for full text Lucene queries. Everything works as expected but after implementing Hibernate Search I started to receive warnings:
Hibernate's legacy org.hibernate.Criteria API is deprecated; use the
JPA javax.persistence.criteria.CriteriaQuery instead
I don't use Criteria API for my Hibernate Search queries (as it is discouraged by Hibernate Search documentation anyway), my code for querying basically looks as follows:
import org.apache.lucene.search.Query;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.jpa.Search;
import org.hibernate.search.query.dsl.BooleanJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
...
#Override
#Transactional
public List<Picture> fullTextSearchByCriteria(List<SearchCriteria> criteria, String pageString) {
final FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
final QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Picture.class).get();
final Query luceneQuery = buildFromCriteria(queryBuilder, criteria);
final FullTextQuery jpaQuery = fullTextEntityManager.createFullTextQuery(luceneQuery, Picture.class);
/* some fine tunning here with calls to .setFirstResult(), .setMaxResults() and
.getResultSize() for pagination. It's used for page wrapper that I omitted here for simplicity sake */
return jpaQuery.getResultList()
}
and buildFromCriteria(queryBuilder, criteria) is a method that builds boolean junction (also SearchCriteria class is just a regular POJO, nothing interesting there). This method looks as follows:
#Transactional
private Query buildFromCriteria(QueryBuilder queryBuilder, List<SearchCriteria> criteria) {
if (criteria == null)
return queryBuilder.all().createQuery();
BooleanJunction<BooleanJunction> junction = queryBuilder.bool();
for (SearchCriteria c : criteria) {
junction.must(
queryBuilder
.keyword()
.onField(c.getField())
.matching(c.getValue())
.createQuery()
);
}
return junction.createQuery();
}
Every use of this full text search results in Criteria API deprecation warning.
Does it use deprecated Criteria API under the hood? If so why Hibernate Search would use API that Hibernate deprecated? Actually I also got some deprecation warnings at application startup, when I call
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(entityManager);
fullTextEntityManager.createIndexer().startAndWait();
for initial indexing.
Notes: I'm using Springs Specification in other part of app, that I would expect may use Criteria API under the hood, but I don't get deprecation warnings when using it and also didn't get any warning before Hibernate Search was added.
EntityManager is obtained via #PersistanceContext injection.
I would like to get rid of deprecated API usages if possible but I don't understand where Criteria API is used in this case and if it is even possible to change it to JPA CriteriaQuery (as I don't use it explicitly).
My dependencies with versions that I think might be important:
hibernate-core: 5.2.5.Final
hibernate-search-orm: 5.7.0.Beta2
Also using some extra dependencies for Analyzers:
solr-analysis-extras: 5.5.2
lucene-analyzers-stempel: 5.5.2
The conversion of Hibernate Search to the latest changes of Hibernate ORM is a complex work in progress.
You're hitting HSEARCH-2381.
Related
My project involves using GraphQL within a Spring Boot app. For demonstration purposes, here is my GraphQL schema:
type Company{
name: String,
parentOrganization: String,
flag:Int
}
I'm still learning Spring Boot and JPA, so I use spring-boot-starter-data-jpa for all the JPA, Hibernate, etc.
My problem is, when someone queries only for name and organization, Hibernate queries for all the columns and GraphQL picks the columns requested.
#Repository
#Transactional
public interface CompanyRepository extends JpaRepository<Company,Long> {
}
The above code doesn't really give me any flexibility in limiting the columns that are queried. I've tried using Hibernate's Criteria API as well, but whichever way I go, I get this error:
Unable to locate appropriate constructor on class [packagee.entity.company]. Expected arguments are: java.lang.String, java.lang.String [select new package.entity.Company(generatedAlias0.company, generatedAlias0.organization) from package.entity.Company as generatedAlias0]
Below is the code for my Criteria implementation:
public static List<Company> get(EntityManager em, List<String> fieldsAsked){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Company> cq = cb.createQuery(Company.class);
Root<Company> root = cq.from(Company.class);
List<Selection<?>> selectionList = new LinkedList<Selection<?>>();
for(String name: fieldsAsked){
selectionList.add(root.get(name));
}
cq.multiselect(selectionList);
return em.createQuery(cq).getResultList();
}
How do I get limited columns from Hibernate? I've seen many answers online that ask to make appropriate constructor in the entity class, but that's not really possible for me because my entity parameters are mostly Strings and I cant make constructors for all the permutations possible (because I'm using GraphQL, the control of what to query really goes to the end user of my project).
What should I do? Thanks in advance!
What you want to do is not really possible with Hibernate directly, but you can checkout Blaze-Persistence Entity-Views which also has a GraphQL integration that supports exactly what you are looking for. See https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/#graphql-integration
Here is a sample project that shows how you can use this: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-graphql
Solution 1:
You can create a new DTO class which will be returned by your query.
The DTO class:
public class CompanyDTO(){
//fields,constructor
}
And in the repository:
#Query(value = "SELECT new com.example.dto.companyDTO" +
"(c.name,c.parentOrganization)" +
" FROM Company c")
List<CompanyDTO>findCompanySelectedColumns(PageRequest pageable);
Solution 2(clean solution):
You can use interface. Do not implement the interface.
interface customCustomer{
String getName();
String getParentOrganization();
}
In repository:
List<CustomCustomer>findAllByNameAndParentOrganization();
I have been trying to understand the difference between HQL and JPQL. The hibernate documentation at here
suggest that writing select is necessary for JPQL but not in HQL. But when I try writing HQL or JPQL inside Query annotation of spring data JPA , both HQL and JPQL works .
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
#Query(value = "from com.demo.spring.data.jpa.User u")
public List<User> findAllUsersHQL();
#Query(value = "select u from com.demo.spring.data.jpa.User u")
public List<User> findAllUsersJPQL();
}
My understanding is since the Query annotation is intended for JPA why is HQL supported ?
Or let me put in other words -
Can we write HQL in the Query annotation - Does spring data JPA supports both ?
I think it depends on which JPA impleemntation you use.
Even though Spring data says it supports JPQL, Spring data in itself isnt implememnting any functionality for validating and implementing queries with JPQL.
Spring will rely on underlying JPA impleemntation that will validate and convert given JPQL queries into plain SQL.
If the underlying adapter is a hibernate Adapter, There are fair chances that it will support HQL and will not limit the application to JPQL.
Moreover, as jdicker mentioned above, JPQL is a subset of HQL, or in the other words, hibernate provided mucm more functionalty than that of JPA.. so you shoudl be able to parse HQL in spring data as long as you are using Hibernate sa an underlying JPA engine.
DIY - You can try putting a breakpoint in QueryTranslatorImpl and confirm the behavior yourself.
I've just started integrating QueryDSL into a Spring Boot project and I'm looking for a way to build a query out of an existing entity bean. Using #ModelAttribute it's nice and easy to pass in an entity via a GET request from the controller as long as the parameters align with the bean:
public Page<Company> getLogins(#ModelAttribute Company company, Pageable pageable, #RequestParam(value = "page", required = false) String pageNumber){
return companyService.findbyParameters(company,pageNumber);
}
And in the service class, I can use the BooleanBuilder to build up a query:
public Page<Company> findbyParameters(Company companySearch,String pageNumber){
QCompany company = QCompany.company;
BooleanBuilder builder = new BooleanBuilder();
if (companySearch.getEmail() != null && !companySearch.getEmail().equals("")){
builder.and(company.email.eq(companySearch.getEmail()));
}
if (companySearch.getCompanyName() != null && !companySearch.getCompanyName().equals("")){
builder.and(company.companyName.eq(companySearch.getCompanyName()));
}
//add other clauses...
return loginRepository.findAll(builder.getValue(),pageableService.getPageRequest(pageNumber));
}
..and this works fine. But it seems like an unnecessary amount of plumbing since I'll have to write similar, longwinded conditional code for each entity I'm working with. I reckon that reflection might be an option, but I'm not sure if QueryDSL has something built in that handles this situation. I've looked at the QueryDSL docs and nothing jumped out at me.
So is there a nice, tidy way of handling this situation without clogging up my service classes with boilerplate?
You can use Spring Data's QueryDSL integration. Basically, you extend the QueryDslPredicateExecutor in your repository interface and it add a findAll method that gets a QueryDSL Predicate and filter all the results based on that Predicate. You see more details here.
It turns out that the exact thing I was looking for is Spring Data's query by example API.
https://www.baeldung.com/spring-data-query-by-example
It lets you create a query by providing a sample entity and a matcher which defines things like case sensitivity, partial 'like' matching and so on.
It's very useful in limited situations, and can drastically reduce boilerplate query code; but when you want to query a more complex graph of data you'll want to use a different approach.
I'm writing couchbase repository using Spring module and I'm trying to add my own implementation of count method using N1QL query:
public interface MyRepository extends CouchbaseRepository<Entity, Long> {
#Query("SELECT count(*) FROM default")
long myCount();
}
But it doesn't work:
org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to retrieve enough metadata for N1QL to entity mapping, have you selected _ID and _CAS?
So my question is: how can I write counting query using spring-data-couchbase?
I cannot find anything about this in spring documentation. link
This exception happens because the #Query annotation was designed with the use-case of retrieving entities in mind. Projections to a scalar like count are uncovered corner cases as of RC1. Maybe I can think of some way of adding support for it through explicit boolean flag in the annotation?
Unfortunately I was unable to find a workaround. I was trying to come up with a custom repository method implementation but it appears support for it is broken in 2.0.0-RC1 :(
edit:
The use case of simple return types like long, with a SELECT that only uses a single aggregation, should work so this is a bug/improvement. I've opened ticket DATACOUCH-187 in the Spring Data JIRA.
#Query("SELECT count(*) , META(default).id as _ID, META(default).cas as _CAS FROM default")
Change your query to this one.
Use this query :
#Query("SELECT count(*) as count FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} ")
long myCount();
i'm trying to convert a 'TableController'-Class we used (without ORM) to generate dynamic SQL (actually the order column and direction is appended to the SQL).
Think of this 'TableController' as a class that have a function to return a list of Entities of a given class (known at runtime), in a given order (String column/property name, boolean asc/desc, both at runtime).
The challenge is now, with JPA (Hibernate - but the customer requires to use JPA Interfaces only): How can i realize this without String concatenation, and in a type safe manner?
Thanks!
The challenge is now, with JPA (Hibernate - but the customer requires to use JPA Interfaces only): how can I realize this without String concatenation, and in a type safe manner?
If you're using a JPA 2.0 implementation, I think you should look at the Criteria API to build dynamic queries.
If you're using JPA 1.0, there is no standard way apart from String concatenation (and my suggestion would be to use Hibernate's proprietary Criteria Queries).
The following article might also give you some (concrete) ideas: Hibernate Querying 102 : Criteria API.
Imagine a method that has three parameters: Class entityClass, String orderByColumn, boolean ascending. How would i create a query without string concatenation that gives me all objects of the given class in the correct order?
With the Criteria API from JPA 2.0, you could do something like this:
public <T> List<T> findAllEntitiesOrderedBy(Class<T> entityClass, String orderByColumn, boolean ascending) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<T> criteria = builder.createQuery(entityClass);
Root<T> entityRoot = criteria.from(entityClass);
criteria.select(entityRoot);
javax.persistence.criteria.Order order = ascending ? builder.asc(entityRoot.get(orderByColumn))
: builder.desc(entityRoot.get(orderByColumn));
criteria.orderBy(order);
return em.createQuery(criteria).getResultList();
}