How to sort on a field in indexedEmbedded - java

I have an indexedEmbedded object with #OneToMany relation, inside a class, and want to sort with a field containded in that object.
When i call my method for search i got this exception:
"Unexpected docvalues type NONE for field 'employees.id_forSort' (expected=NUMERIC). Use UninvertingReader or index with docvalues"
Thanks in advance!
Here is my code:
public Company {
....
#IndexedEmbedded(prefix = "students.", includeEmbeddedObjectId = true)
#OneToMany(mappedBy = "company")
private Set<Employee> employees= new HashSet<>();
}
public Employee{
....
#SortableField(forField = "id_forSort")
#Field(name = "id_forSort", analyze = Analyze.NO)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
public class CompanySearchRepository{
....
public List<Company> searchCompany(
DataTablePagination pagination, Long id, SearchCriteria searchCriteria) {
FilterField[] filterFields = validateFilterFields(searchCriteria.getFilterFields());
// Sorting
String sortField = "employees.id";
Sort sort = getSort(sortField, pagination.getSortDirection());
Query query;
if (filterFields.length > 0) {
query = getFilterableColumnsQuery(filterFields);
} else {
// global search
query = getGlobalSearchQuery(searchableFields, searchCriteria.getSearchTerm());
}
FullTextQuery fullTextQuery = getFullTextQuery(query);
initPagination(
fullTextQuery,
sort,
pagination.getPage(),
pagination.getPageSize());
List<Company> data = fullTextQuery.getResultList();
}
Sort getSort(String sortField, SortDirection sortDirection) {
SortFieldContext sortFieldContext =
getQueryBuilder().sort().byField(sortField.concat("_forSort"));
return sortDirection.equals(SortDirection.ASC)
? sortFieldContext.asc().createSort()
: sortFieldContext.desc().createSort();
}
}

You named your sortable field employees.id_forSort, but when searching, you're using another field for sorts: employees.id. Use the field that is intended for sorts. Replace String sortField = "employees.id"; with String sortField = "employees.id_forSort"; My bad, I didn't see the weird code that adds a suffix to the field name in the getSort method. Then the message is strange. Would your index be empty, by any chance?
Sorts on multi-valued fields are not supported. You will likely get different results from one execution to the other, since the search engine has to select a value to use for sorts, and which value is selected is undefined.
Regardless of the technical aspect, I'm not sure what you're trying to achieve. You're getting Company instances as a result, and want them to be sorted by Employee ID, of which there are many for each Company. What does it mean? You want the company with the oldest employee first? Something else? If you're just trying to stabilize the order of hits, I'd recommend using the company ID instead.

Related

JPA: How do I sort by field customising order when using findAll with specification

I`m using Spring Tool Suite 4 (4.12.0.RELEASE) with Java 8 and I have the following piece of code to get results from a table filtered, paged and sorted:
// Define pageable
Sort sort = JpaSort.unsafe(Sort.Direction.ASC, Arrays.asList("FIELD(Status, '0','6','1','3','7')"));
Pageable pageRequest = PageRequest.of(searchCriteria.getPageNo(), searchCriteria.getPageSize(), sort);
// Initialise specification
Specification<BestPracticeAdminList> spec = Specification.where(null);
// If search criteria contains searchText field, add it to the specification
if(searchCriteria.getSearchText() != null)
spec = spec.and(BestPracticeAdminListFilter.titleLike(searchCriteria.getSearchText()));
// If search criteria contains filters field, inspect filters field
if(searchCriteria.getFilters() != null) {
// If search criteria contains subjectID field, add it to the specification
if(searchCriteria.getFilters().getSubjectID() != null)
spec = spec.and(BestPracticeAdminListFilter.hasAlternativeSubjectID(searchCriteria.getFilters().getSubjectID()));
// If search criteria contains categoryID field, add it to the specification
if(searchCriteria.getFilters().getCategoryID() != null)
spec = spec.and(BestPracticeAdminListFilter.hasAlternativeSubjectCategoryID(searchCriteria.getFilters().getCategoryID()));
// If search criteria contains status field, add it to the specification
if(searchCriteria.getFilters().getStatus() != null)
spec = spec.and(BestPracticeAdminListFilter.hasStatus(searchCriteria.getFilters().getStatus()));
}
// Get results
Page<BestPracticeAdminList> pagedResults = bestPracticeAdminListRepository.findAll(spec, pageRequest);
Problem is adding a custom sorting. I need to sort by a status field, which is an enumerator, but in an specific order, not the order the enumerator is written.
Custom sorting works in a different situation when using a native query on a repository with #Query and nativeQuery=true but not in this case when I want to use the findAll method with a specification for filtering.
This is the entity class:
#Entity
#Table
public class BestPracticeAdminList implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "BestPracticeAdminListID")
private int bestPracticeAdminListID;
#Column(name = "Title")
private String title;
#Column(name = "BestPracticeID")
private int bestPracticeID;
#Column(name = "AlternativeSubjectID")
private int alternativeSubjectID;
#Column(name = "AlternativeSubjectCategoryID")
private int alternativeSubjectCategoryID;
#Column(name = "Status")
#Enumerated(EnumType.ORDINAL)
private LookupReportingProvisionWorkflowStatus status;
#Column(name = "IsPreviousVersionPublished")
private Boolean isPreviousVersionPublished;
public BestPracticeAdminList() {
}
Currently, exception I get is:
org.springframework.data.mapping.PropertyReferenceException: No property fIELD(Status, '0','6','1','3','7') found for type BestPracticeAdminList!
How could I solve this problem? Thanks
Since Spring is complaining you don't have a sorting field, you could solve this by creating it using the FIELD function in a #Formula in your BestPracticeAdminList
#Formula("FIELD(Status, '0','6','1','3','7')")
private String sortingStatus;
and using it for sorting
Sort sort = JpaSort.unsafe(Sort.Direction.ASC, Arrays.asList("sortingStatus"));

Hibernate OneToMany List or Iterator different?

#Entity
#Table(name = "STUDENT")
public class Student {
private long studentId;
private String studentName;
private List<Phone> studentPhoneNumbers;
....
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "STUDENT_PHONE", joinColumns = { #JoinColumn(name = "STUDENT_ID") }, inverseJoinColumns = { #JoinColumn(name = "PHONE_ID") })
public List<Phone> getStudentPhoneNumbers() {
return this.studentPhoneNumbers;
}
public void setStudentPhoneNumbers(List<Phone> studentPhoneNumbers) {
this.studentPhoneNumbers = studentPhoneNumbers;
}
}
1)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
for (Phone p : phoneList) {
...
}
2)
Student student = session.loadStudent(123); // pseudocode
List phoneList = student.getStudentPhoneNumbers();
Iterator itr = phoneList.iterator();
while(itr.hasNext()) {
...
}
I read the answer from here: difference between query.list and query.iterate
Obviously there is difference between list() and iterator() (in Query). What if I use it in the OneToMany list? like the example above, is there difference in term of performance? memory?
It has nothing to do with Hibernate.
When Java Compiler encounters
for (Phone p : phoneList) {
....
}
it automatically generate code equivalent to
for (Iterator<Phone> itr = phoneList.iterator(); itr.hasNext();) {
Phone p = itr.next();
....
}
So it is essentially the same for that two examples you are showing.
I read up this Hibernate chapter which explain the proxy performance in detail.
The entity's mapping FetchType by default is lazy, which, hibernate will create a proxy around the attribute.
Upon calling list.size() (and etc), hibernate will start to load all the children objects.
If we don't want to load all, we can use the new feature called extra lazy. It will issue select statement for specific record only, for example, list.get(3) select fourth row only.
If we annotate the attribute with eager, then hibernate will load all the children objects (use outer join, which will have duplicate issue) upon it load the parent object. In this case, there is no proxy wrapping around the attribute. It will not have performance difference, not matter we use it as a list or iterator.

Enforce multiple constraints in embedded entities with hibernate-search

I have a question regarding the possibility to enforce multiple constraints while performing a query with hibernate-search.
#Indexed
public class Contact{
//ommited fields
#IndexEmbedded
private List<Communication> communications;
//setters - getters
}
and the associated class
#Indexed
public class Communication{
#Field(analyze = Analyze.YES, store = Store.YES)
private String value;
#Field(analyze = Analyze.YES, store = Store.YES)
private CommunicationType communicationType;
#Field(analyze = Analyze.YES, store = Store.YES)
private CommunicationUsage communicationUsage;
}
the enums
public static enum CommunicationUsage {
PRIVATE,
PROFESSIONNAL
}
public static enum CommunicationType{
PHONE,
EMAIL
}
An example query that I would need to accomplish is the following :
Find all contacts for which the communication type is "PHONE" and the CommunicationUsage is "PRIVATE" and the field value of the Communication class contains the string 999
public List<Contact> search(){
FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(em);
QueryBuilder qb = fullTextEntityManager.getSearchFactory()
.buildQueryBuilder().forEntity(Contact.class).get();
org.apache.lucene.search.Query luceneQuery =
qb.bool() .must(qb.keyword().wildcard().onField("communications.value").matching("*99999*").createQuery()) .must(qb.keyword().onField("communications.type").ignoreFieldBridge().matching("phone").createQuery()) .must(qb.keyword().onField("communications.usage").ignoreFieldBridge().matching("private").createQuery())
.createQuery();
org.hibernate.search.jpa.FullTextQuery jpaQuery =
fullTextEntityManager.createFullTextQuery(luceneQuery, Contact.class);
List result = jpaQuery.getResultList();
}
However I'm getting contacts that have a phone number matching the one provided but for different communication types and usages (such as PHONE and PROFESSIONAL)
So can this type of query be accomplished with hibernate-search or not?
At the moment this use case is not solvable with Hibernate Search with default indexing. The problem is that Hibernate Search flattens all data to be indexed (including the associations annotated via #IndexedEmbedded) into a single Lucene Document. In particular one looses in this case the "grouping" given by a single Communication instance. If you have one Communication instance with the type you are interested in and another instance with the value you are interested in, you will in your case get a match.
As a workaround you could provide a custom class bridge for the Communication instance which somehow concatenates the values you are interested in. You would then try to write a query which targets this custom field.

Hibernate criteria using left join to filter results

JSF application with hibernate
Is there a way to use a join to filter the results returned by criteria list?
Example: i have 2 tables. orders and customers.
#Entity(name = "order")
public class Order
{
#Id
private int id;
private String billingCustomerId;
private String shippingCustomerId;
private Date orderDate;
....
}
#Entity(name = "customer")
public class Customer
{
#Id
private String id;
private String name;
private String emailAddress
....
}
I need to return all orders for customers that are missing an email address and all orders that the order.billingCustomerId = null and order.shippingCustomerId = null.
The customer could match on the billingCustomerId or shippingCustomerId.
The SQL I would use
select o.* from order as o
LEFT join customer as c1 on o.billingCustomerId = c1.id
LEFT join customer as c2 on o.shippingCustomerId= c2.id
where (o.billingCustomerId is null and o.shippingCustomerId is null) or
(o.billingCustomerId is not null and c1.emailAddress is null) or
(o.shippingCustomerIdis not null and c2.emailAddress is null)
Hibernate Criteria
Criteria criteria1 = session.createCriteria(Order.class);
criteria.add(Restrictions.and(Restrictions.isNull("billingCustomerId"),
Restrictions.isNull("shippingCustomerId"));
List<Order> = criteria.list();
This will return the list of orders that billing /shipping customer = null.
How can i change the criteria to also include the orders for customers with missing email addresses?
Disjunction disjunciton = Restrictions.disjunction();
Criteria criteria = session.createCriteria(Order.class);
disjunciton.add(Restrictions.and(Restrictions.isNull("billingCustomerId"),
Restrictions.isNull("shippingCustomerId")));
disjunciton.add(...
...)
criteria.add(disjunciton);
List<Order> = criteria.list();
I have not been able to find examples of joining on a column, but only where the table have a common key.
I asked this question: Hibernate trouble getting composite key to work and discovered Hibernate can only create a join on columns that were created by relating 2 objects. I am going to add more to my answer to give more useful information but the best alternative you your case is to do a Session.createSQLQuery() using the query you showed above. Then before running the query put Query.addEntity(Order.class).addEntity(Customer.class). As long as your query returns the correct rows to fill out the Java objects correctly, Hibernate can populate them automatically. If that doesn't work you can still retrieve the data and populate it manually yourself.

Hibernate - Use native query and alias to Bean with enum properties?

I am having trouble using a native query in hibernate to alias a bean that contains enum properties. I am getting an InvocationTargetException when query.list() is called. My example is below:
#Entity(name = "table1")
public class Class1 {
#Column(name = "col1")
#NotNull
private Integer prop1;
#Column(name = "col2")
#NotNull
private String prop2;
#Column(name = "col3", length = 6)
#Enumerated(value = EnumType.STRING)
private MyEnumType prop3;
// ... Getters/Setters...
}
public List getClass1List(){
String sql = "select col1 as prop1, col2 as prop2, col3 as prop3 from table1";
Session session = getSession(Boolean.FALSE);
SQLQuery query = session.createSQLQuery(sql);
query.addScalar("col1", Hibernate.INTEGER);
query.addScalar("col2", Hibernate.STRING);
query.addScalar("col3", Hibernate.STRING);
query.setResultTransformer(Transformers.aliasToBean(Class1.class));
return query.list();
}
During the query.addScalar("col3", Hibernate.STRING) call, I don't know what type to use for col3 (the enum type). Hibernate.String is not working! I have also tried to leave the type off entirely ( query.addScalar("col3") ) but I get the same InvocationTargetException. Can anyone help me out with this? My model cannot be changed and I am stuck with a native sql query. Any ideas are appreciated.
// In public List getClass1List() method:
// 1. DEFINE:
Properties params = new Properties();
params.put("enumClass", "enumerators.MyEnumType");
params.put("type", "12");
// 2. REPLACE:
// query.addScalar("col3", Hibernate.STRING);
// X
query.addScalar("col3", Hibernate.custom(org.hibernate.type.EnumType.class, params));
Firstly, you shouldn't use
private EnumType prop3;
but
private ActualEnum prop3;
Where ActualEnum is your own enum type (for example, Fruits to distinguish apples and oranges).
Second, you hibernate mapping is irrelevant when you use native sql.
Now, there are couple of options I can propose. You can try to use addEntity() instead of bunch of scalars. It's possible that Hibernate will recognize enum property and map correctly.
Other option is to have non public setter that would take string from database, convert it to enum and set actual property.
Finally, you can customize transformer. But it's probably most complex option.

Categories

Resources