Hibernate Search Tuple Queries - hibernate-search

I have an entity Message with a one-to-many relation to an entity Header. How can I create a tuple based search query like
(message.headerKey="foo" and message.headerValue="123") and
(message.headerKey="bar" and message.headerValue="456")
My current logic would also match when I swap the header values in my search criteria
(message.headerKey="foo" and message.headerValue="456") and
(message.headerKey="bar" and message.headerValue="123")
How can I do a tuple based query using the Hibernate Search API?
This is my Message Entity:
#Entity
#Table(name="MESSAGE")
#Indexed
public class MessageEntity implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="message_timestamp")
private Date timestamp;
#Column(name="payload")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String payload;
#OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "message")
#IndexedEmbedded
private List<HeaderEntity> headers;
// Getters and Setters
}
This is my Header Entity:
#Entity
#Table(name="HEADER")
public class HeaderEntity implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Column(name="header_key")
#Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String headerKey;
#Column(name="header_value")
Field(index=Index.YES, analyze=Analyze.YES, store=Store.NO)
private String headerValue;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="message_id")
private MessageEntity message;
// Getters and Setters
}
This is my search logic:
public List<MessageEntity> search(Header[] headers) {
FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search.getFullTextEntityManager(mgr);
QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(MessageEntity.class).get();
TermMatchingContext onFieldKey = qb.keyword().onField("headers.headerKey");
TermMatchingContext onFieldValue = qb.keyword().onField("headers.headerValue");
BooleanJunction<BooleanJunction> bool = qb.bool();
org.apache.lucene.search.Query query = null;
for (Header header : headers) {
bool.must(onFieldKey.matching(header.getKey()).createQuery());
bool.must(onFieldValue.matching(header.getValue()).createQuery());
}
query = bool.createQuery();
FullTextQuery persistenceQuery = fullTextEntityManager.createFullTextQuery(query, MessageEntity.class);
persistenceQuery.setMaxResults(10);
return persistenceQuery.getResultList();
}

Your approach will indeed not work. The problem is that Lucene is a flat data structure, in particular associations (embedded entities) are just "added" to the Lucene Document of the owning entity. In your case the MessageEntity document will contain two fields per headerKey respectively headerValue. Once with "foo" and "bar" as value and56" as values. once with "123" and "456" as values. There is no notion that two of these values are acutally a pair.
One potential solution is to create a unique field/value pair. Using a custom class bridge you could create a "keyValueField" containing header key and value as concatenated value. In your query you would then target this field using concatenated query parameters.

Related

How to append where clause to all queries that run with spring data MongoRepository?

I have entities that are persisted in MongoDB and use spring data MongoRepository to fetch data. Now i want to apply filter to all queries that executed on the entites, so i decided to use hibernate filter, something like this:
#Entity
#QueryEntity
#Document(collection = "Opportunity")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#CompoundIndexes({
#CompoundIndex(name = "productGroup_userId_uniqueness", def = "{'productGroupCode' : 1, 'userId': 1}", unique = true)
})
#FilterDef(name = "defaultFilter",parameters = #ParamDef(name = "unitCode",type = "string"))
#Filter(name = "defaultFilter" , condition = " unitCode like :unitCode")
public class Opportunity {
#Id
#Indexed
private String id;
#Indexed
#Enumerated(EnumType.STRING)
private OpportunityStatus opportunityStatus = OpportunityStatus.OPEN;
private LeadType leadType;
#Indexed
private String userId;
#Indexed
private String productCode;
#Indexed
private String productGroupCode;
#Indexed
private Long actionId;
private String assigneeId;
#Transient
private List<AbstractCommand> commandHistory = new ArrayList<>();
#Transient
private Map<Long, Boolean> actionStatus = new HashMap<>();
private String unitCode;
}
and this is the repository class:
#Repository
public interface OpportunityRepository extends MongoRepository<Opportunity, String>, QuerydslPredicateExecutor<Opportunity> {
// this repository contains more than 20 methods
// and all of theme removed for question brevity
}
And I enabled hibernate filter on session with this way:
Session session = (entityManager).unwrap(Session.class);
session.enableFilter(filterName).setParameter("unitCode", this.getCurrentUserUnitCode());
Now, when I call OpportunityRepository.findAll(Predicate predicate, Pageable pageable) i expected to apply the defined filter on the entity, but it didn't work.
I think the reason is that MongoRepository hasn't any sense of hibernate #Filter and i should use another way to append where clause to all mongo queries that running on the Opportunity entity.

Spring Data JPA order by value from OneToMany relation

I am trying to sort a result by nested collection element value. I have a very simple model:
#Entity
public class User {
#Id
#NotNull
#Column(name = "userid")
private Long id;
#OneToMany(mappedBy = "user")
private Collection<Setting> settings = new HashSet<>();
// getters and setters
}
#Entity
public class Setting {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "userid")
private User user;
private String key;
private String value;
// getters and setters
}
public interface UserRepository extends JpaRepository<User, Long>, QuerydslPredicateExecutor<User> {
}
I want to have a result returned sorted by the value of one setting.
Is it possible to order by user.settings.value where settings.name = 'SampleName' using Spring Data JPA with QueryDSL?
I've used JpaSpecificationExecutor. let's see findAll for example.
Page<T> findAll(#Nullable Specification<T> spec, Pageable pageable);
Before call this method you can create your specification dynamically (where condition) and Pageable object with dynamic Sort information.
For example
...
Specification<T> whereSpecifications = Specification.where(yourWhereSpeficiation);
Sort sortByProperty = Sort.by(Sort.Order.asc("property"));
PageRequest orderedPageRequest = PageRequest.of(1, 100, sortByProperty);
userRepository.findAll(whereSpecifications, PageRequest.of(page, limit, orderedPageRequest));

How to query for entities by their string collection values LIKE

I have the following entity:
#Entity
public class SystemLogEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private long creationTime;
private String thread;
private int severity;
#Lob
private String message;
#ElementCollection(fetch = FetchType.EAGER)
#Lob
private List<String> stacktrace;
...
}
My Respository implements JpaSpecificationExecutor, which allows me to use Specifications to filter my db requests:
#Repository
public interface SystemLogRepository extends JpaRepository<SystemLogEntity, Long>, JpaSpecificationExecutor<SystemLogEntity> {
public List<SystemLogEntity> findAll(Specification spec);
}
For the simple field of the SystemLogEntity this works fine, the Predicate are straight forward.
Also if I filter for an exact item in a collection, the Predicate are still straight forward (in).
But how can I filter my SystemLogEntity after a stack trace collection item which is LIKE a given value?
In other words, I would e.g. like to filter SystemLogEntity after the term NullpointerException. Is this even possible with Predicate?
I hope this will work:
Specification<SystemLogEntity> stacktraceLike(String stacktrace) {
return (root, query, cb) -> cb.like(root.join("stacktrace"), "%" + stacktrace + "%");
}
More examples...

Search in key value Entity

Salam,
I have a problem with (key, value) entity searching.
I don't figure out how to write the jpql query to search.. this is my problem
I have document entity, mapped to oneToMany "MetaData" entity
#Entity
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="id_document")
private Integer idDocument;
#Column(name="date_enregistrement")
private Timestamp dateEnregistrement;
// other columns
//bi-directional many-to-one association to MetaData
#OneToMany(mappedBy="document")
private List<MetaData> metaData;
/*
getters and setters
*/
}
and MetaData entity
#Entity
#Table(name="meta_data")
public class MetaData implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="id_meta_data")
private Integer idMetaData;
#Column(name="key")
private String key;
#Column(name="value")
private String value;
//bi-directional many-to-one association to Document
#ManyToOne
#JoinColumn(name="id_document")
private Document document;
/*
getters and setters
*/
}
What i want to do is to search for documents by providing some metadata as parameters.
examples:
Find documents where sender (key = sender) is Youssef (value = Youssef) And receiver (key) is Hamza (value)
it's possible that the client provides more than two parameters.
Thanks in advace
As key-value pairs can vary in number, I'll suggest to go with CriteraQuery for more flexibilty:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Document> query = criteriaBuilder.createQuery(Document.class);
Root<Document> root = query.from(Document.class);
Join<Document, MetaData> metadataJoin = root.join("metaData", JoinType.INNER);
List<Predicate> predicateList = new LinkedList<Predicate>();
//If a map contains all your key value pairs
for(Map.Entry<String, String> entry : keyValueMap.entrySet()){
predicateList.add(
criteriaBuilder.and(
criteriaBuilder.equal(metadataJoin.get("key"), entry.getKey()),
criteriaBuilder.equal(metadataJoin.get("value"), entry.getValue())
)
);
}
query.where(criteriaBuilder.or(
predicateList.toArray(new Predicate[predicateList.size()]))
)
query.groupBy(root.get("idDocument"));
query.having(criteriaBuilder.equal(criteriaBuilder.count(root.get("idDocument")), predicateList.size()));
List<Document> documentList = entityManager.createQuery(query).getResultList();

JPA Query Many To One nullable relationship

I have the following entities and would like to seek help on how to query for selected attributes from both side of the relationship. Here is my model. Assume all tables are properly created in the db. JPA provider I am using is Hibernate.
#Entity
public class Book{
#Id
private long id;
#Column(nullable = false)
private String ISBNCode;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = false)
private Person<Author> author;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
private Person<Borrower> borrower;
}
#Inheritance
#DiscriminatorColumn(name = "personType")
public abstract class Person<T>{
#Id
private long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Info information;
}
#Entity
#DiscriminatorValue(PersonType.Author)
public class Author extends Person<Author> {
private long copiesSold;
}
#Entity
#DiscriminatorValue(PersonType.Borrower)
public class Borrower extends Person<Borrower> {
.....
}
#Entity
public class Info {
#Id
private long id;
#Column(nullable=false)
private String firstName;
#Column(nullable=false)
private String lastName;
......;
}
As you can see, the book table has a many to one relation to Person that is not nullable and Person that is nullable.
I have a requirement to show, the following in a tabular format -
ISBNCode - First Name - Last Name - Person Type
How can I write a JPA query that will allow me to select only attributes that I would want. I would want to get the attributes ISBN Code from Book, and then first and last names from the Info object that is related to Person Object that in turn is related to the Book object. I would not want to get all information from Info object, interested only selected information e.g first and last name in this case.
Please note that the relation between the Borrower and Book is marked with optional=true, meaning there may be a book that may not have been yet borrowed by someone (obviously it has an author).
Example to search for books by the author "Marc":
Criteria JPA Standard
CriteriaQuery<Book> criteria = builder.createQuery( Book.class );
Root<Book> personRoot = criteria.from( Book.class );
Predicate predicate = builder.conjunction();
List<Expression<Boolean>> expressions = predicate.getExpressions();
Path<Object> firtsName = personRoot.get("author").get("information").get("firstName");
expressions.add(builder.equal(firtsName, "Marc"));
criteria.where( predicate );
criteria.select(personRoot);
List<Book> books = em.createQuery( criteria ).getResultList();
Criteria JPA Hibernate
List<Book> books = (List<Book>)sess.createCriteria(Book.class).add( Restrictions.eq("author.information.firstName", "Marc") ).list();
We recommend using hibernate criterias for convenience and possibilities.
Regards,