QueryDSL dynamic predicates - spring

I need help with QueryDSL querying. I'm using this library with Spring Data JPA.
My service class:
#Service("tblActivityService")
public class TblActivityService implements AbstractService<TblActivity> {
#Resource
private TblActivityRepository tblActivityRepository;
#Override
public List<TblActivity> findAll(Predicate predicate) {
return (List<TblActivity>) tblActivityRepository.findAll(predicate);
}
}
I have dynamic list of filters:
#Entity
#Table(name = "sys_filters")
public class SysFilter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "filter_id")
private Integer filterId;
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(fetch = FetchType.EAGER)
private SysUser userId;
#Size(max = 45)
#Column(name = "table_name")
private String tableName;
#Size(max = 45)
#Column(name = "column_name")
private String columnName;
#Size(max = 45)
#Column(name = "condition")
private String condition;
#Size(max = 100)
#Column(name = "value")
private String value;
// getters & setters
}
I have column name (e.g. title)
I have condition (e.g. ==, !=, >= etc.) - I can store it as symbols or words (equals etc.)
And finally I have value.
The question is "how to dynamically generate predicate for my service?"
Table has about 25 fields.
Predicate looks like that:
public BooleanExpression buildFilteredResult(List<SysFilter> filters) {
//TODO do it!
return QTblActivity.tblActivity.title.eq("Value");
// I need to do it dynamically for each filter in the list
}
The problem is how to invoke columnName by its string value.
Do you have any suggestions?

It might be easier to use a mapping filter conditions to operators
Map<String, Operator> operators = ImmutableMap.of(
"==", Ops.EQ, "!=", Ops.NE, ">", Ops.GT, "<", Ops.LT,
">=", Ops.GOE, "<=", Ops.LOE);
Expressions.predicate(operators.get(condition),
stringPath, Expressions.constant(filterValue));
Also make sure you combine your predicates properly
predicates.and(...)
returns a new predicate and leaves predicates untouched.
Maybe BooleanBuilder is what you are after?

A newer solution was released with spring data Gosling/Fowler. If you are creating a web app, you can use the querydsl web support that does the work for you -it reads the get parameters into a predicate and then you can use this predicate from your controller - no need to manually do that -
You can customize your repository based on the search criteria (equal, like ...) needed for a particular datatype or particular entity's field.
see the documentation here

I found the solution:
public BooleanExpression buildFilteredResult(List<SysFilter> filters) {
//TODO do it!
QTblActivity qTblActivity = QTblActivity.tblActivity;
BooleanExpression expression = qTblActivity.recordState;
for (SysFilter filter : filters) {
StringPath stringPath = new StringPath(qTblActivity, filter.getColumnName());
switch (filter.getCondition()) {
case "==":
expression.and(stringPath.eq(filter.getValue()));
break;
case "!=":
expression.and(stringPath.ne(filter.getValue()));
break;
case ">":
expression.and(stringPath.gt(filter.getValue()));
break;
case "<":
expression.and(stringPath.lt(filter.getValue()));
break;
case ">=":
expression.and(stringPath.goe(filter.getValue()));
break;
case "<=":
expression.and(stringPath.loe(filter.getValue()));
break;
default:
break;
}
}
return expression;
}

Related

Spring Data Jpa One To One mapping with where clause

I have two tables and I need OneToOne mapping with where clause.
select * from person_details inner join address_details
on address_details.pid=person_details.pid AND person_details.exist_flag = 'Y' AND address_details.address_exist_flag = 'Y'
Table 1
public class PersonDetails {
#Id
private String pid;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "exist_flag")
private String existFlag;
#OneToOne(mappedBy = "personDetails", cascade = CascadeType.ALL)
#Where(clause = "addressExistFlag = 'Y'")
private AddressDetails addressDetails;
}
Table 2
#Data
#NoArgsConstructor
#Entity
#Table(name = "address_details")
public class AddressDetails {
#Id
private String pid;
private String street;
#Column(name = "address_exist_flag")
private String addressExistFlag;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "pid", insertable = false, updatable = false)
private PersonDetails personDetails;
}
I need data to be fetched if both addressExistFlag = 'Y' and existFlag = 'Y'.
With current scenario If I am trying to fetch data via spring batch read repository as below, only existFlag = 'Y' is considered. Is it because of incorrect mapping or the way I have used in spring batch
ReadRepository looks like below
public interface PersonDetailsRepository extends JpaRepository<PersonDetails, String> {
Page<PersonDetails> findByExistFlag(String existFlag, Pageable pageable);
}
Spring batch read repository looks like below
#Bean
RepositoryItemReader<PersonDetails> personDetailsItemReader() {
Map<String, Sort.Direction> sort = new HashMap<>();
sort.put("ExistFlag", Sort.Direction.ASC);
return new RepositoryItemReaderBuilder<PersonDetails>()
.repository(personDetailsRepository)
.methodName("findByExistFlag")
.arguments("Y")
.sorts(sort)
.name("personDetailsItemReader")
.build();
}
You are only querying for existsFlag.
You have to add the other Flag too:
public interface PersonDetailsRepository extends JpaRepository<PersonDetails, String> {
Page<PersonDetails> findByExistFlagAndAddressDetailsAddressExistFlag(
String existFlag, String addressExistFlag, Pageable pageable);
}
#Bean
RepositoryItemReader<PersonDetails> personDetailsItemReader() {
Map<String, Sort.Direction> sort = new HashMap<>();
sort.put("ExistFlag", Sort.Direction.ASC);
return new RepositoryItemReaderBuilder<PersonDetails>()
.repository(personDetailsRepository)
.methodName("findByExistFlagAndAddressDetailsAddressExistFlag")
.arguments("Y", "Y")
.sorts(sort)
.name("personDetailsItemReader")
.build();
}

combining #NamedQuery from JPA and #Filter from Hibernate

I have #NamedQuery. I want to add a lot of different filters to the query as per condition at runtime. There is another concept of #Filter in Hibernate. can this concept be a merge to have a combined result?
suppose I have
#NamedQuery(name="Users.someUsers",query="select u from Users where firstname='bob'")
suppose I want to filter the result according to some other parameter.
can I add #Filter that can do the trick?
supposed I want to an add age filer or a place filter over the existing Users.someUsers by enabling the corresponding filter on the underlying hibernate session?
I suppose you want to define named queries and filters at entity level and expect named queries to have filters which you defined.
I wrote a test for it:
#Entity
#Table(name = "DEPARTMENT")
#NamedQueries({#NamedQuery(name=DepartmentEntity.GET_DEPARTMENT_BY_ID, query=DepartmentEntity.GET_DEPARTMENT_BY_ID_QUERY),})
#FilterDef(name="deptFilter", parameters={#ParamDef( name="name", type="string")})
#Filters( {#Filter(name="deptFilter", condition=":name = name")})
public class DepartmentEntity implements Serializable {
static final String GET_DEPARTMENT_BY_ID_QUERY = "from DepartmentEntity d where d.id = :id";
public static final String GET_DEPARTMENT_BY_ID = "GET_DEPARTMENT_BY_ID";
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
#Column(name = "NAME", unique = true, nullable = false, length = 100)
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now you can use both like this:
Session session = (Session) entityManager.getDelegate();
Filter filter = session.enableFilter("deptFilter");
filter.setParameter("name", name);
return (DepartmentEntity) session.getNamedQuery(DepartmentEntity.GET_DEPARTMENT_BY_ID)
.setParameter("id", id)
.uniqueResult();
Query generated by hibernate:
select department0_.ID as ID1_3_, department0_.NAME as NAME2_3_ from DEPARTMENT department0_ where ? = department0_.name and department0_.ID=?
You will need to add filters to session and then create named query. If this doesn't cover your use case then post example exactly what you want to acheive.
This is what CriteriaQuery is built for. It allows you to create queries at runtime.
Avoid using named queries for queries which you want to build at runtime based on user input.
Example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<EntityType> criteria = builder.createQuery(EntityType.class);
Root<EntityType> root = criteria.from(EntityType.class);
criteria.where(
builder.equal(root.get("owner"), "something")
);
// Any conditions can be added to criteria here ar runtime based on user input
List<Topic> topics = entityManager
.createQuery(criteria)
.getResultList();
Named queries are precompiled at EntityManagerFactory startup, so they add performance benefits as long as queries are static, but for dynamic queries consider using CriteriaQueries

Spring webFlux infinite recursion

So I'am trying Spring webFlux and have following code:
#Override
public Flux<? extends AnimalDatabaseEntity> queryAnimals() {
return async(animalsRepository.findAll().stream());
}
private <T> Flux<T> async(Stream<T> stream) {
return Flux.fromStream(stream).publishOn(scheduler);
}
The problem that I get infinite recursion, because "AnimalDatabaseEntity" has field animalFeatures.
#Entity(name = "animals")
public class AnimalDatabaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Enumerated(EnumType.STRING)
#Column(name = "animal_type")
private AnimalType animalType;
#Column(name = "number_of_legs")
private Integer numberOfLegs;
#Column(name = "is_pet")
private boolean isPet;
#OneToMany(mappedBy = "animalDatabaseEntity", fetch = FetchType.EAGER)
private List<AnimalFeatureEntity> animalFeatures;
}
I don't really get why this does not work???
My best guess is AnimalFeatureEntity contains varaible of type AnimalDatabaseEntity which references back to List<AnimalFeatureEntity> while serializing thus causing an infinite recursion. You can prevent it by adding #JsonIgnore to AnimalDatabaseEntity if it suits your use case. However, if you wish to preserve the bidirectional relationship, then #JsonManagedReference/#JsonBackReference or #JsonIdentityInfo is the way to go.
For example usage, refer this article

Spring Data JPA Specification Predicate for a #OneToMany Collection not working

For background:
I have built a module that captures a list of a historical events that occur against an asset over its life and using JPA specifications using spring-data-jpa with hibernate to run the dynamic query using the JPA SpecificationExecutor interface. I have the following historical event JPA object with a many to one asset this historical event is directly against and other associated assets this historical event is also associated with defined in a many-to-many relationship. I am trying to write a JPA Specification predicate that pulls all historical events for a given asset that the asset is either directly against or associated too by using the includeAssociations flag in the predicate. When I try to execute the predicate I am not getting the correct results when I have the includeAssociations flag set to true. I would expect it would by default return at a minimum all the historical events they are directly as if the includeAssociations was false plus any ones they are indirectly associated with. I need help figuring out why this predicate is not returning back what I would expect. Any help is much appreciated!
Here is my Historical Event JPA object:
#Entity
#Table(name = "LC_HIST_EVENT_TAB")
public class HistoricalEvent extends BaseEntity implements Comparable<HistoricalEvent>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(targetEntity = Asset.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "ASSET_ID")
private Asset asset;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Asset.class)
#JoinTable(name = "LC_HIST_EVENT_ASSETS", joinColumns =
{
#JoinColumn(name = "HIST_EVENT_ID", referencedColumnName = "id")
}, inverseJoinColumns =
{
#JoinColumn(name = "ASSET_ID", referencedColumnName = "id")
}, uniqueConstraints =
{
#UniqueConstraint(columnNames =
{
"HIST_EVENT_ID", "ASSET_ID"
})
})
#BatchSize(size=10)
#OrderBy("partCatalogItem.partID, serialNumber ASC")
private Set<Asset> associatedAssets;
#Column(name = "START_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar startDate;
#Column(name = "END_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar endDate;
}
JPA Metamodel for Historical Event:
#StaticMetamodel(HistoricalEvent.class)
public class HistoricalEvent_ extends BaseEntity_
{
public static volatile SingularAttribute<HistoricalEvent, Asset> asset;
public static volatile SetAttribute<HistoricalEvent, Asset> associatedAssets;
public static volatile SingularAttribute<HistoricalEvent, Calendar> startDate;
public static volatile SingularAttribute<HistoricalEvent, Calendar> endDate;
public static volatile SingularAttribute<HistoricalEvent, String> type;
public static volatile SingularAttribute<HistoricalEvent, String> description;
public static volatile SingularAttribute<HistoricalEvent, HistoricalEvent> triggeringEvent;
public static volatile SetAttribute<HistoricalEvent, HistoricalEvent> associatedEvents;
public static volatile MapAttribute<HistoricalEvent, String, HistoricalEventMap> data;
}
Here is my Asset JPA Object:
#Entity
#Table(name = "LC_ASSET_TAB")
public class Asset extends BaseEntity implements Comparable<Asset>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = PartCatalog.class)
#JoinColumn(name = "PART_CATALOG_ID", nullable = false)
private PartCatalog partCatalogItem;
#Column(name = "SERIAL_NO", nullable = false)
private String serialNumber;
#Column(name = "DATE_INTO_SERVICE", nullable = false)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar dateIntoService;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "asset", targetEntity = AssetMap.class)
#MapKey(name = "fieldName")
#BatchSize(size=25)
private Map<String, AssetMap> data;
}
Asset Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Here is my Part Catalog JPA object:
#Entity
#Table(name = "LC_PART_CATALOG_TAB")
public class PartCatalog extends BaseEntity implements Comparable<PartCatalog>, Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "PART_ID", length=100, nullable = false)
private String partID;
#Column(name = "NSN", length=100, nullable = true)
private String nsn;
#Column(name = "DESCRIPTION", length=250, nullable = false)
private String description;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "partCatalogItem", targetEntity = PartCatalogMap.class)
#MapKey(name = "fieldName")
private Map<String, PartCatalogMap> data;
}
Part Catalog Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Specification Predicate for returning historical events by a given Part Number and Serial Number:
PROBLEM: If includeAssociations is false, it returns fine however soon as it is true, it returns the wrong list of associations and never returns any results from the events the asset is directly tied too like if the includeAssociations was false. This is where I need help how to best write the criteria builder query to properly pull the data.
These are the two JPQL queries I am trying to combine into the Predicate using the Criteria API:
Normal:
#Query("SELECT he FROM HistoricalEvent he WHERE he.asset.partCatalogItem.partID =:partID AND he.asset.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate")
Association:
#Query("SELECT he FROM HistoricalEvent he INNER JOIN he.associatedAssets associated WHERE associated.partCatalogItem.partID =:partID AND associated.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate");
/**
* Creates a specification used to find historical events by a given asset part number and serial
* parameter.
*
* #param partID - part identifier
* #Param serialNumber
* #return Historical Event Specification
*/
public static Specification<HistoricalEvent> hasPartAndSerial(final String partID, final String serialNumber, final Boolean includeAssociations)
{
return new Specification<HistoricalEvent>() {
#Override
public Predicate toPredicate(Root<HistoricalEvent> historicalEventRoot,
CriteriaQuery<?> query, CriteriaBuilder cb) {
if (partID == null || partID == "")
{
return null;
}
if(serialNumber == null || serialNumber =="")
{
return null;
}
Path<Asset> assetOnEvent = historicalEventRoot.get(HistoricalEvent_.asset);
Path<PartCatalog> partCatalogItem = assetOnEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatch = partCatalogItem.get(PartCatalog_.partID);
Expression<String> serialToMatch = assetOnEvent.get(Asset_.serialNumber);
if(includeAssociations)
{
SetJoin<HistoricalEvent, Asset> assetsAssociatedToEvent = historicalEventRoot.join(HistoricalEvent_.associatedAssets);
Path<PartCatalog> partCatalogItemFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatchFromAssociatedAsset = partCatalogItemFromAssociatedAsset.get(PartCatalog_.partID);
Expression<String> serialToMatchFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.serialNumber);
return cb.or(cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase())),
cb.and(cb.equal(cb.lower(partIdToMatchFromAssociatedAsset), partID.toLowerCase()), cb.equal(cb.lower(serialToMatchFromAssociatedAsset), serialNumber.toLowerCase())));
}
else
{
return cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase()));
}
}
};
}
Finally I am calling this to find the historical events:
#Override
public Page<HistoricalEvent> getByCriteria(String type, String partID,
String serialNumber, Calendar startDate, Calendar endDate,
Boolean includeAssociations, Integer pageIndex, Integer recordsPerPage)
{
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Searching historical event repository for type of " + type + " , part id of " + partID +
" , serial number of " + serialNumber + " , start date of " + startDate + " , end date of " + endDate + ", include associations flag of " + includeAssociations
+ " , pageIndex " + pageIndex + " and records per page of " + recordsPerPage);
Page<HistoricalEvent> requestedPage = historicalEventRepository.findAll(Specifications
.where(HistoricalEventSpecifications.hasType(type))
.and(HistoricalEventSpecifications.greaterThanOrEqualToStartDate(startDate))
.and(HistoricalEventSpecifications.lessThanOrEqualToEndDate(endDate))
.and(HistoricalEventSpecifications.hasPartAndSerial(partID, serialNumber, includeAssociations)),
DatabaseServicePagingUtil.getHistoricalEventPagingSpecification(pageIndex, recordsPerPage));
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Found " + requestedPage.getTotalElements() + " that will comprise " + requestedPage.getTotalPages() + " pages of content.");
return requestedPage;
} UPDATE: i have been able to get the specification if the historical event was either directly or indirectly associated working however using the following Predicate 1 = cb.equals(cb.lower(partIDToMatch, partID.toLowercase()); Predicate2 = cb.equals(cb.lower(serialToMatch), serialNumber.toLowercase(); Predicate3 = cb.or(Predicate1, Predicate2 ); Predicate4 = cb.equals(cb.lower(partIDToMatchFromAssociatedAsset), partIDToMatch.toLowercase()); Predicate5 = cb.equals(cb.lower(serialNumberFromAssociatedAsset), serialNumberToMatch.toLowercase()); Predicate6 = cb.and(Predicate4, Predicate5); Predicate7 = cb.or(Predicate3,Predicate6); When i return Predicate I only get results matching Predicate6 not either one as i would expect. I want it to pull events where either predicate condition returns a record. Each predicate returns the right data but when i use the cb.or it doesnt combine results as i would expect. What am I missing?
You have to start printing the query and parameters value that are bean generated, just enable this properties.
After that you have to analyze your query and make some tests with different combinations to check your jpa specification are falling.
There is no magic way to do that and it's hard and painful :(
Good look

Hibernate Search not returning results

I am building an application with Hibernate Search 4.5.1 and Spring 4.0.5.RELEASE. I am trying to index the following class:
#Entity
#Indexed
#Analyzer(impl= org.apache.lucene.analysis.standard.StandardAnalyzer.class)
#Table(name="SONG")
#XmlRootElement(name="song")
public class Song
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", updatable = false, nullable = false)
private Long id;
#Field(store = Store.YES)
#Column(name="NAME", length=255)
private String name;
#Field(store = Store.YES)
#Column(name="ALBUM", length=255)
private String album;
#Field(store = Store.YES)
#Column(name="ARTIST", length=255)
private String artist;
#NotNull
#Column(name="PATH", length=255)
private String path;
#NotNull
#Column(name="PATH_COVER", length=255)
private String cover;
#NotNull
#Column(name="LAST_VOTE")
private Date date;
#Field(store = Store.YES)
#NotNull
#Column(name="N_VOTES")
private int nvotes;
#NotNull
#Column(name="ACTIVE", nullable=false, columnDefinition="TINYINT(1) default 0")
private boolean active;
#OneToOne(fetch=FetchType.LAZY)
#JoinColumn(name="IMAGE_ID",insertable=true,updatable=true,nullable=false,unique=false)
private Image image;
#IndexedEmbedded
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PLAYLIST_ID", nullable = false)
private PlayList playList;
#OneToMany(mappedBy = "song")
private Set<UserVotes> userServices = new HashSet<UserVotes>();
I am building a junit test case which looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = {"classpath:jukebox-servlet-test.xml"})
#Transactional
public class SongDaoTest {
#Autowired
public I_PlaceDao placeDao;
#Autowired
public I_PlayListDao playListDao;
#Autowired
public I_SongDao songDao;
#Before
public void prepare() throws Exception
{
Operation operation = sequenceOf(CommonOperations.DISABLE_CONTRAINTS, CommonOperations.DELETE_ALL,CommonOperations.INSERT_SONG_DATA, CommonOperations.ENABLE_CONTRAINTS);
DbSetup dbSetup = new DbSetup(new DriverManagerDestination("jdbc:mysql://localhost:3306/jukebox", "root", "mpsbart"), operation);
dbSetup.launch();
FullTextSession fullTextSession = Search.getFullTextSession(placeDao.getSession());
fullTextSession.createIndexer().startAndWait();
}
#Test
#Rollback(false)
public void searchTest()
{
PlayList playList = playListDao.read(1l);
List<Song> songs = songDao.search(playList, "offspring", 1, 10);
assertEquals(10, songs.size());
}
The search method implementation is:
#SuppressWarnings("unchecked")
public List<Song> search(PlayList playlist, String searchTerm,int page,int limit)
{
FullTextSession fullTextSession = Search.getFullTextSession(getSession());
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Song.class).get();
BooleanQuery luceneQuery = new BooleanQuery();
luceneQuery.add(queryBuilder.keyword().onFields("name","album","artist").matching("*"+searchTerm+"*").createQuery(), BooleanClause.Occur.MUST);
luceneQuery.add(queryBuilder.phrase().onField("playList.place.id").sentence("\""+playlist.getPlace().getId()+"\"").createQuery(), BooleanClause.Occur.MUST);
luceneQuery.add(queryBuilder.phrase().onField("playList.id").sentence("\""+playlist.getId()+"\"").createQuery(), BooleanClause.Occur.MUST);
// wrap Lucene query in a javax.persistence.Query
FullTextQuery query = fullTextSession.createFullTextQuery(luceneQuery, Song.class);
org.apache.lucene.search.Sort sort = new Sort(new SortField("n_votes",SortField.INT));
query.setSort(sort);
List<Song> songs = query.setFirstResult(page*limit).setMaxResults(limit).list();
return songs;
}
The test result fails, it does not find any matching object. When using luke lucene I can see that there are results, if I try the query generated by hibernate on luke it does return elements. The query generated by hibernate is: +(name:metallica album:metallica artist:metallica) +playList.place.id:"1" +playList.id:"1"
I have also noticed on luke lucene that some index terms have a length up to six characters, for an instance, one song's artist it's "The Offspring" and the terms stored in the index are "the" and "offspr". The first one it's ok, but shouldn't the second term be "offspring". Why is it truncating the name?
In case it helps anybody, I was able to fix it by changing the query to this:
FullTextSession fullTextSession = org.hibernate.search.Search.getFullTextSession(getSession());
QueryBuilder qb = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(Song.class).get();
if(searchTerm==null || searchTerm.equals(""))
searchTerm="*";
else
searchTerm="*"+searchTerm+"*";
Query luceneQuery1 = qb.bool()
.should(qb.keyword().wildcard().onField("name").matching(searchTerm).createQuery())
.should(qb.keyword().wildcard().onField("album").matching(searchTerm).createQuery())
.should(qb.keyword().wildcard().onField("artist").matching(searchTerm).createQuery()).createQuery();
Query luceneQuery2 = qb.bool()
.must(qb.keyword().wildcard().onField("playList.place.id").matching(playlist.getPlace().getId()).createQuery())
.must(qb.keyword().wildcard().onField("playList.id").matching(playlist.getId()).createQuery())
.createQuery();
BooleanQuery finalLuceneQuery=new BooleanQuery();
finalLuceneQuery.add(luceneQuery1, BooleanClause.Occur.MUST);
finalLuceneQuery.add(luceneQuery2, BooleanClause.Occur.MUST);
FullTextQuery query = fullTextSession.createFullTextQuery(finalLuceneQuery, Song.class);
org.apache.lucene.search.Sort sort = new Sort(new SortField("nvotes",SortField.INT,true));
query.setSort(sort);
List<Song> songs = query.setFirstResult(page*limit).setMaxResults(limit).list();
in case of you have check that field value is null or not null then you must add following line on field where indexing field in class
#Field(index=Index.YES,analyze=Analyze.NO,store=Store.YES,indexNullAs=Field.DEFAULT_NULL_TOKEN)
Search on field
if you want null value then
booleanQuery.must(qb.keyword().onField("callReminder").matching("null").createQuery());
if you don't want null value
booleanQuery.must(qb.keyword().onField("callReminder").matching("null").createQuery()).not();
refrence document:http://docs.jboss.org/hibernate/search/4.1/reference/en-US/html/search-mapping.html#search-mapping-entity

Resources