Hibernate search - multi field sort - hibernate-search

I am using hibernate search to search nearby stores. Each store belongs to supermarket.
We used to fetch all nearby stores by using hibernate search.
Search results:
Supermaket 1 - Shop 1 (0.5 km)
Supermaket 2 - Shop 1 (1 km)
Supermaket 2 - Shop 2 (1.1 km)
Supermaket 1 - Shop 2 (2 km)
But recent requirement came where we need to group the search by Supermarkets and show that on the app.
Supermaket 1
Shop 1 (0.5 km)
Shop 2 (2 km)
Supermaket 2
Shop 1 (1 km)
Shop 2 (1.1 km)
#Indexed
#Entity
class Shop {
#Id
#Column(name = "Id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name = "SuperMarketId")
#IndexedEmbedded
private SuperMarket superMarket;
#Column(name = "Latitude")
#Latitude(of = "location")
private double latitude;
#Column(name = "Longitude")
#Longitude(of = "location")
private double longitude;
}
#Indexed
#Entity
class SuperMarket {
#Id
#Column(name = "Id")
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
}
I was using sort field:
Sort sort = new Sort(
new DistanceSortField(lat, lng, "location"));
query.setSort(sort);
I have to sort by distance and shops how do I do it.
I tried:
Sort sort = new Sort(
new DistanceSortField(lat, lng, "location"),
new SortField("superMarket.id", SortField.Type.STRING, true));
query.setSort(sort);
Bascially looking for solution in hibernate search where we could order by distance, id. - 2 fields

As an alternative, you may be interested in faceting, which tries to address the same problem, but is not affected by the problem of ordering.
If you really want grouping... you seem to be aware of that, but you will need to perform some post-processing on your data.
Something like:
List<Shop> shops = fullTextQuery.getResultList();
Map<Supermarket, List<Shop>> results =
shops.stream().collect( Collectors.groupingBy( Shop::getSupermarket, LinkedHashMap::new, Collectors.toList() ) ;
Now about the order... It depends what you want. Assuming your results are paginated, if a shop of supermarket 1 was previously on page 2, do you want it to appear on page 1 regardless of its distance? I.e. do you want to sort first by supermarket, then by distance? If so, just do this:
Sort sort = new Sort(
new SortField("superMarket.id", SortField.Type.STRING, true),
new DistanceSortField(lat, lng, "location")
);
query.setSort(sort);
Or do you want it to stay on page two, and have potentially the same supermarket appear on multiple pages? If so, use the same sort you gave in your question:
Sort sort = new Sort(
new DistanceSortField(lat, lng, "location"),
new SortField("superMarket.id", SortField.Type.STRING, true));
query.setSort(sort);

I finally figured it out from "https://developer.jboss.org/thread/267686":
You need to add Fields and SortableField annotation to Id of supermarket:
#Indexed
#Entity
class SuperMarket {
#Id
#Column(name = "Id")
#GeneratedValue(strategy = GenerationType.AUTO)
#Fields({
#Field(name = "id_sort", analyze = Analyze.NO, store = Store.NO, index = Index.NO) })
#SortableField(forField = "id_sort")
private int id;
}
Sort sort = new Sort(
new DistanceSortField(lat, lng, "location"),
new SortField("superMarket.id_sort", SortField.Type.INT, true));
query.setSort(sort);
The sort field was getting ignored previously.

Related

How do I use JPA to select the record with the closest date?

Let's say I have a collection of Rates that all inherit from an AbstractRate
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "name")
#Table(name = "rates")
public abstract class AbstractRate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(precision = 13, scale = 4)
private BigDecimal value;
#OneToOne
private EffectiveDate effectiveDate;
...
}
And, that all have an EffectiveDate:
#Entity
public class EffectiveDate {
#Id
private LocalDateTime date;
...
}
(I acknowledge that a separate date object is a little over-kill for this model, but it's allowing me to bind multiple rates and other figures in the database.)
Now, I'd like to get a specific Rate, say SalaryRate, that is effective as of a certain date. I can do something like
salaryRateRepository.findByEffectivedate(
effectiveDateRepository.findTopByDateLessThanEqualOrderByDateDesc(sampleDate)
);
This should effectively give me a the MAX(date) and its matching Rate. Is this the right way to query these things? Some posts suggest
As an additional option, I have Querydsl setup and the repositories extend QuerydslPredicateExecutor. However, I'm not really familiar with how Querydsl's syntax works.
I think all is OK with findTopByDateLessThanEqualOrderByDateDesc(sampleDate).
Another variants should be:
#Query(value = "select * from effective_date ed where ed.date <= ?1 order by ed.date desc limit 1", nativeQuery = true)
EffectiveDate findEffectiveDate(LocalDateTime dateTime);
or
Predicate p = QEffectiveDate.effectiveDate.date.loe(sampleDate);
Pageable page = new PageRequest(0, 1, new Sort(Sort.Direction.DESC, "date"));
//...
EffectiveDate effectiveDate = effectiveDateRepository.findAll(p, page).getContent().stream().findFirst().orElse(null);
(not tested...)

Issues with bidirectional OneToMany mapping and NamedQuery

I have two entities connected bidirectional and I want to query the Location and its votes only for a specific date.
Location:
#Entity
#Table(name = "TAB_LOCATION")
#NamedQueries({
#NamedQuery(name = "Location.getVotedLocations", query = "SELECT l FROM Location l JOIN l.votes v WHERE v.location = l AND DATE(v.createdAt) = DATE(:date) ORDER BY l.name")
})
public class Location extends AbstractEntity {
#Basic
#Size(min = 5, max = 50)
private String name;
#Basic
#Size(min = 0, max = 50)
private String address;
#OneToMany(mappedBy = "location")
private Set<Vote> votes;
#Basic
private String description;
Vote:
#Entity
#Table(name = "TAB_VOTE")
public class Vote extends AbstractEntity {
#Basic
#ManyToOne
#NotNull
private User user;
#Basic
#ManyToOne
#NotNull
private Location location;
I was trying to use the named query but it seems that the Location always contains all votes regardless the condition.
Isn't it possible to map the queried values back to the object?
entityManager.createNamedQuery("Location.getVotedLocations").
setParameter("date", date).getResultList();
What is wrong?
If it isn't possible with NamedQueries, I also can use the Criteria API.
Here's a simple MySql statement that will return locationIDs not Location entity which are voted on a particular date.
Select DISTINCT LocationID FROM VOTE WHERE DATE == dateCreated;
And you can then get the Location entity by LocationID.
When you get an entity as a result from some query, you get the whole entity. It is not possible in JPA to get just a subset of all data, trimmed by where condition.
Well, if you use Hibernate, take a look at Hibernate Filters, with them you could get the result you want.
Note about your query, you have JOIN l.votes so you don't need to join it again with WHERE v.location = l.

How to make a CriteriaBuilder join with a custom "on" condition?

I want make a query where I join 2 tables, using the CriteriaBuilder. In MySQL the query I'm trying to make would look like this:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
AND item.type_id = 1
I want to get all orders and if they have an item of type #1, I want to join with this item. However, if no item of type #1 is found, I still want to get the order. I can't figure out how to make this with the CriteriaBuilder. All I know how to make is:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
Join<Item, Type> type = order.join(Item_.type, JoinType.LEFT);
cq.select(order);
cq.where(cb.equal(type.get(Type_.id), 1));
This query is broke, since it results in something like this in MySQL:
SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
WHERE item.type_id = 1
The result will only contain orders with items of type #1. Orders without are excluded. How can I use the CriteriaBuilder to create a query like in the first example?
It is possible starting from the version 2.1 of JPA using the on method Join<Z, X> on(Predicate... restrictions);
Here is how:
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
item.on(cb.equal(item.get(Item_.type), 1));
I think this is the same problem as posed in this question. It looks like it is not possible in CriteriaBuilder. It is possible in Hibernate Criteria API, but that probably won't help you.
JPA Criteria API: Multiple condition on LEFT JOIN
I know this question was made a long time a go, but recently a had the same problem and i found this solution from an Oracle forum, i copied and pasted just in case the link is not longer available.
MiguelChillitupaArmijos 29-abr-2011 1:41 (en respuesta a 840578) Think
you should use something like:
em.createQuery("SELECT DISTINCT e.Id" +
" from Email e " +
" left join e.idEmailIn e2 *with* e2.responseType = 'response'" +
" where e.type = 'in' and e.responseMandatory = true").getSingleResult();
An this is the link.
JPA Criteria : LEFT JOIN with an AND condition
There is a workaround if you are using Hibernate 3.6 with JPA 2.0
It is not the better solution, however it works perfect for me.
I´ve duplicate the entity with the #Where hibernate annotation.It means that everytime you use the join with this entity, hibernate will add the extra condition on the join statement at generated SQL.
For instance, initially we have the follow example:
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
}
#Entity
#Table(name = "ADDRESS")
public class Address {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#ManyToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
In order to add extra conditions on criteria Join, we need duplicate the Address #Entity mapping , adding the #Where annotation #Where(clause = " ADDRESS_TYPE_ID = 2").
#Entity
#Table(name = "ADDRESS")
#Where(clause = " ADDRESS_TYPE_ID = 2")
public class ShippingAddress {
#Id
#Column(name = "ADDRESS_ID")
private Long id;
#Id
#Column(name = "ADDRESS_STREET")
private String street;
#OneToOne
#JoinColumn(name = "PERSON_ID")
private Person person;
}
Also, we need to add the duplicate mapping association for the new entity.
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "PERSON_ID")
private Long id;
#Id
#Column(name = "PERSON_NAME")
private String name;
#OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private Set<Address> addresses;
#OneToOne(mappedBy = "person")
private ShippingAddress shippingAddress;
}
Finally, you can use a join with this specific Entity in your criteria :
PersonRoot.join(Person_.shippingAddress, JoinType.LEFT);
The Hibernate Snippet SQL should seems like this :
left outer join
address shippingadd13_
on person11_.person_id=shippingadd13_.person_id
and (
shippingadd13_.ADDRESS_TYPE_ID = 2
)
ON clause is supported in Hibernate 4.3 version, anyone is aware if there is a parameter indexing issue between the parameter index of the additional custom conditions with the index of the existing mapping filters when doing an outer join with ON clause?
Using the Person entity class below as an example, say I am adding this filter to limit the address types and the filter is enabled to populate the IN clause. The parameter index for the IN clause will cause the issue [2] when I add additional conditions (such as using 'street' column) part of the ON clause. Is is a known issue?
[1] #Filter(name = "addressTypes", condition = "ADDRESS_TYPE in (:supportedTypes)")
[2]
Caused by: ERROR 22018: Invalid character string format for type BIGINT.
private Set addresses;

How to use JPA Criteria API in JOIN

I have five tables: Company, Product/Service, Address, Country and
City.
A Company can have n products with category, 1 address with 1 country
and 1 city inside the address entity.
A user has chosen "England - Leeds".
I know now that I have to select every companies from db where city
is Leeds and populate product/service-list with those companies'
products or services. After that user can select for instance dentist
from the third list.
After that I know Enlgand - Leeds - Dentist and I have to populate
the last list with compenies (dentists in Leeds)
public class Company implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Short companyId;
#OneToOne(cascade=CascadeType.PERSIST)
private Address address;
private String companyName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company",cascade=CascadeType.PERSIST)
private Set<Product> products = new HashSet<Product>(0);
public class Product implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "productId", unique = true, nullable = false)
private Integer productId;
private Short branchId;
private String productName;
private String sku;
private String category; ------> I am using this field in company search (dentists, garages etc.)
How can I query only those companies which have products with category dentist?
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Company> criteria = criteriaBuilder.createQuery( Company.class );
Root<Company> companyRoot = criteria.from( Company.class );
//criteria.select(companyRoot);
TypedQuery<Company> q = em.createQuery(criteria);
List<Company> results = q.getResultList();
Now I've got every company, how can I select only the companies with the correct category? I think I will need JOIN but how I don't know how to use it.
use join(),
criteriaBuilder.equal(companyRoot.join("products").get("category"), "dentist")
See,
http://en.wikibooks.org/wiki/Java_Persistence/Querying#Joining.2C_querying_on_a_OneToMany_relationship

JPA Query with count and group by (many to many relationship with join table)

I have a little problem. In my application I have two entities, which look like on the snippets:
//imports, annotations, named queries
public class WishList implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "wishes_seq",
sequenceName = "wish_list_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "wishes_seq")
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 80)
#Column(name = "book_title")
private String bookTitle;
#Size(max = 80)
#Column(name = "book_cattegory")
private String bookCattegory;
#ManyToMany(cascade= CascadeType.ALL, fetch= FetchType.EAGER)
#JoinTable(name="wl_authors",
joinColumns={#JoinColumn(name="wl_id")},
inverseJoinColumns={#JoinColumn(name="author_id")})
private List<Author> authorList;
// other methods
and the second one:
//imports, annotations, named queries
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "authors_seq",
sequenceName = "authors_id_seq",
allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "authors_seq")
#Basic(optional = false)
#NotNull
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 58)
#Column(name = "author_name")
private String authorName;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 58)
#Column(name = "author_surname")
private String authorSurname;
#Size(max = 58)
#Column(name = "nationality")
private String nationality;
#Size(max = 64)
#Column(name = "birth_place")
private String birthPlace;
#JoinTable(name = "wl_authors",
joinColumns = {#JoinColumn(name = "author_id",
referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "wl_id",
referencedColumnName = "id")})
#ManyToMany(fetch = FetchType.EAGER)
private List<WishList> wishListList;
// other methods
As You can see entities, which I prepared represent two tables in database, which are in many to many relationship (I used join table). I want to count all results in WishList entity, which have the same authors, title and cattegory and return all results as follows:
count result
book title
book cattegory
book authors
The results should be ordered by count result.
The JPA query, which I prepared:
SELECT Count(wl.bookTitle) AS POP,
wl.bookTitle,
wl.bookCattegory
FROM WishList wl
GROUP BY wl.bookTitle,
wl.bookCattegory,
wl.authorList
ORDER BY POP DESC
doesn't satisfy me. I can`t return authors of the book from WishList. When I place wl.authorList before 'FROM' EJB exception appears:
...
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.postgresql.util.PSQLException: ERROR: column "t1.id" must appear in the GROUP BY clause or be used in an aggregate function
...
Call: SELECT COUNT(t0.book_title), t0.book_title, t0.book_cattegory, t1.id, t1.author_name, t1.author_surname, t1.birth_place, t1.nationality FROM wl_authors t4, wl_authors t3, authors t2, authors t1, wish_list t0 WHERE (((t3.wl_id = t0.id) AND (t1.id = t3.author_id)) AND ((t4.wl_id = t0.id) AND (t2.id = t4.author_id))) GROUP BY t0.book_title, t0.book_cattegory, t2.id, t2.author_name, t2.author_surname, t2.birth_place, t2.nationality ORDER BY COUNT(t0.book_title) DESC
Query: ReportQuery(referenceClass=WishList sql="SELECT COUNT(t0.book_title), t0.book_title, t0.book_cattegory, t1.id, t1.author_name, t1.author_surname, t1.birth_place, t1.nationality FROM wl_authors t4, wl_authors t3, authors t2, authors t1, wish_list t0 WHERE (((t3.wl_id = t0.id) AND (t1.id = t3.author_id)) AND ((t4.wl_id = t0.id) AND (t2.id = t4.author_id))) GROUP BY t0.book_title, t0.book_cattegory, t2.id, t2.author_name, t2.author_surname, t2.birth_place, t2.nationality ORDER BY COUNT(t0.book_title) DESC")
Caused by: org.postgresql.util.PSQLException: ERROR: column "t1.id" must appear in the GROUP BY clause or be used in an aggregate function
...
Can somebody help me to create apriopriate query? Is it possible to do it in single query?
I don't think you can achieve it using one JPQL query. Perhaps, if native query would be used you might achieve it (not sure, tho).
Consider splitting the problem into two parts:
invoke a query which returns the statistics but without authors,
for each returned row, fetch the authors.
So firstly invoke such query:
SELECT DISTINCT COUNT(wl.bookTitle) AS POP,
wl.bookTitle,
wl.bookCattegory,
MAX(wl.id)
FROM WishList wl
GROUP BY wl.bookTitle, wl.bookCattegory, wl.authorList
ORDER BY POP DESC
According to the query, each returned row will have the same authors. The MAX(wl.id) is used just to get one of these ids you can query for the authors list.
Having this id you can for each record fetch the authors and return such prepared data (popularity, book title, category, authors).
It'll surely not get the highest ranks in the performance category as doing it in one query, I don't see other solution at this point.
By the way - are you sure your model is OK? Each WishList have exactly one book title, category and authors? Shouldn't the book data be separated to a Book entity and each WishList consists of many Books? I think it then should be easier to query for WishLists which consists of the same books.
I don't think you can group by a ManyToMany,
try,
SELECT Count(wl.bookTitle) AS POP, wl.bookTitle, wl.bookCattegory, a.id FROM WishList wl join wl.authorList a GROUP BY wl.bookTitle, wl.bookCattegory, a.id ORDER BY POP DESC
I know it's a bit late, but does the query work on other databases, like MySQL?
Postgres had a limitation and made mandatory in group by section all the columns present in select section - exactly what the error says.
From v9.1, this error should not happen anymore:
Allow non-GROUP BY columns in the query target list when the primary key is specified in the GROUP BY clause [...] The SQL standard allows this behavior, and because of the primary key, the result is unambiguous.