I currently have a system in place which can filter and sort records in the database and return them as a Paged object. One of the lines is like this:
final PageRequest request = new PageRequest(this.pagingSettings.getPageNumber(),
this.pagingSettings.getPageSize(), sortDirection, sortedBy);
This works correctly, but now I'm having the following situation. I'm trying to sort on a house number, which is a varchar in my Postgres database. For example, we have 1, 12, 111, 1004 but also 1A or 36-BASEMENT. When sorting on these (character) values, these would sort on: 1, 1004, 111, 12, 1A, ...
So, sortedBy is now a String, which in this case is houseNumber. I found out that using the ORDER BY argument ... ORDER BY NULLIF(regexp_replace(container_number, E'\\D', '', 'g'), '')::int"; in Postgres, the sorting would be exactly like I wanted: 1, 1A, 12, 111, ...
However, just changing the sortedBy String to sortedBy = "NULLIF(regexp_replace(container_number, E'\\D', '', 'g'), '')::int"; does not seem to work.
Does anyone have a suggestion on how to sort the character values in a PageRequest numerical, without changing the database?
So basically you need to do two things:
Implement custom comparator,
Annotate the entity class.
Ad.1.
public class HouseComparator implements Comparator<House> {
#Override
public int compare(House h1, House h2) {
String s1 = h1.getHouseNumber().split("[^0-9]")[0];
String s2 = h2.getHouseNumber().split("[^0-9]")[0];
return s1.compareTo(s2);
}
}
You need to add some better handling of your cases. The above comparator says, that h1 is less than h2 when it begins with a smaller number and vise versa. For this comparator 12A is equal 12B but it's up to you.
Ad.2.
#SortComparator(HouseComparator.class)
List<House> findByHouseNumber(Pageable pageable);
Assume that you have entity with a String filed and you want to sort it like Long with jpa Pageable.
So you need to do following things:(Remember this only works with Oracle Database)
Add a new filed in your entity with Long type
for the new filed use #Formula in getter method and evoke to_number()
#Entity
public class testEntity{
private String oldField; //Getter and Setter
private Long newField; //Setter
#Formula(value = "to_number(oldField)")
public Long getNewField() {
return newField;
}
}
in your service find sorted filed and change it to the newfiled
if (Objects.nonNull(pagingRequest.getSort()) && pagingRequest.getSort().getFieldName().equals("oldField")) {
pagingRequest.getSort().setFieldName("newField");
}
I think you could try the Spring Data JpaSort class which allows function calls.
As stated in the documentation you will have something like :
#Query("select u from User u where u.lastname like ?1%")
List<User> findByAndSort(String lastname, Sort sort);
repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)"));
You could also use it with a Pageable object.
Related
I am working on a legacy database where modifying the table schema is not an option. Most records are unique but there are some duplicate entries. For that reason I have modified the RecordRepository.java interface to perform a #Query with map(). Otherwise JPA will return the same data if it thinks it's the same record.
RecordRepository.java:
#Query("select new map(field1 as field1, field2 as field2) from Record where year = ?1")
List<Record> findByYear(String year);
RecordController.java:
#RestController
public class RecordController {
#Autowired
private RecordRepository recordRepository;
#RequestMapping(value = "/record/{year}", method = RequestMethod.GET)
public List<Record> recordByYear(#PathVariable("year") String year) {
List<Record> l = recordRepository.findByYear(year);
System.out.println(l.getClass());
System.out.println(l.get(1967));
return l;
}
}
The output from getClass() is class java.util.ArrayList. Printing item 1967 from the ArrayList is {field1=2018-01-15, field2=201801}.
But when trying to get the string-value of field1 using String tmp_r = l.get(1967).getField1() I get the error java.util.HashMap cannot be cast to Record.
I have tried various suggestions from SO. My head is spinning, I must be overlooking something simple explanation to this.
Regards
Claus
Well l.getClass() is an ArrayList, but that doesn't mean that all its elements are Record-s (maybe casts were done somewhere else).
When you call l.get(1967) the resulting item is an HashMap (right?), so maybe you can check the actual type of the expression l.get(1967) first.
If the map is indexed by Strings, then String tmp_r = l.get(1967).get("field1") will print your field.
Danieles answer, and STaefi's comment, led me in the rigth direction. The solution ended up being quite simple. My error can be attributed to the facts it's been a while I worked with this. I changed the return type in the interface from List to List.
RecordRepository.java:
List<HashMap<String, Record>> findByYear(String year);
RecordController:
List<HashMap<String, Record>> l = recordRepository.findByYear(year);
System.out.println(l.get(1967).get("field1"));
will give me the value field1 has.
Here is the method I wrote
public static <T> List selectOnCriteria(SessionFactory sessionFactory, int maxResults, T model)
{
List resultList=null;
session=sessionFactory.openSession();
Criteria criteria= session.createCriteria(model.getClass());
criteria.add(Example.create(model).ignoreCase());// only for exact search, uppercase and lowercase are both included in ignorecase
if(maxResults>0)
criteria.setMaxResults(maxResults);
resultList=criteria.list();
return resultList;
}
Here is one of my models
public class UserModel
{
#Id
private String username;
private String password;
private String company;
//getters and setters are also defined
}
Now Suppose That there is an entry in the table like this
Username: Chris
Password: Nolan
Company : Syncopy
If I populate my model with all these values then the resultList I obtain has 1 record.
However if only populate my model with username, ie Chris in this case, the resultlist I receive is always an empty ArrayList.
My Projects JPA Specification is 2.0, but if that is the issue, why does it return me the object in the first case and not the second.
Secondly , if that's the issue : is there any other way I can design a generic class that can take any model (of any type, with any number of fields populated)?
The last thing I want to do is to create a search method for all of my models.
Thanks and have a great day!
I have a SearchCriteria POJO class
public class SearchCriteria{
private int empId;
private String empName;
private String empAddress;
private String empDesignation,
:
:
//getter + setters
}
I have a returnAllEmployees method in other class
public List<Employees> returnAllEmployees (){
// makes a db call which has lot of joins and returns info for all the employees
}
now my question is I have to filter out the result of returnAllEmployees() based on the search criteria passed i.e. if empName field of searchcriteria is populated as "ABC", the filter list should contain details of all the employees as ABC.
Similarly, if search criteria contains empName="ABC" and empDesignation="engineer", it should filter out the list containing all the employees having name abc and designation as engineer
I know it is possible by using if-else but that would create a lot of lines of codes
Your best solution is to use Java 8 streams. They are perfect for this:
List<Employee> listOfEngineersCalledFred = getAllEmployees().stream()
.filter(emp -> emp.getName().equals("Fred"))
.filter(emp -> emp.getDesignation().equals("Engineer"))
.collect(Collectors.toList());
A technique that I personally find useful and neat is to add static methods that return predicates instead of using getters:
class Employee {
public static Predicate<Employee> hasName(String name) {
return emp -> emp.name.equals(name);
}
}
These can then be used, for example, to find all employees not call Fred:
streamAllEmployees()
.filter(Employee.hasName("Fred").negate())
...
Which seems neater and more deliberate than exposing the field with a getter.
You also might consider converting your getAllEmployees to streamAllEmployees:
public Stream<Employee> streamAllEmployees() {
return employeeList.stream();
}
Then you are telling the user they can do things with the employee objects in the list rather than the list itself.
The nice thing about returning it as a stream is that once you have filtered it you can easily count, group, sort, remove duplicates, get first n etc. You can even trivially convert it to use multiple threads if you are filtering large numbers of items.
For example:
Map<String, Employee> employeesByDesignation = streamAllEmployees()
.collect(Collectors.groupingBy(emp -> emp.getDesignation()));
They are very powerful and worth learning and using.
I am starting with hibernate search and am struggling with a query on a List<Integer>
I created a bridge to translate the list<Integer> to a string. From this, I am able to search by keyword exact matches on any item on the list, but I don't seem to be able to query it using range.
My entity A has an attribute "b" defined as List.
I would like to know if anyone can help me to get to query all the A entities which have any of the b elements inside a defined range?
For example:
an A instance with the following collection {1,10, 15}, should come up in the following queries on "b" attribute:
below(20),
above(14),
below(2)
but not in a search like:
above(16), below(0).
I hope I made myself clear.
Thanks in advance!
Change your bridge to storing same field multiple times, each with value a of the Integer list. So assuming your field is called myInt, you would store myInt = 1, myInt = 10 and myInt = 15, example code:
public class MyBridge implements FieldBridge {
public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
if (value instanceof List){
for(Object myInt:(List)value){
Field myIntField = new Field(name, myInt.toString(), luceneOptions.getStore(), luceneOptions.getIndex(), luceneOptions.getTermVector());
myIntField.setBoost(luceneOptions.getBoost());
document.add(myIntField);
}
}
}
}
Alternately, you might be able to plugin some custom lucene Filter to do it, but Filters are a bit convoluted.
I have an entity with Integer attributes that looks like this in proto code:
class MyEntity:
String name
#Choices({1, "BSD", 2, "Apache", 3, "GPL"}
Integer frequency
#ChoicesSegment({1000, 2000, 3000}, {"BSD", "Apache", "GPL"})
Integer type
String getFrequency()
return getChoice("frequency", frequency)
String getType()
return getChoice("type", type)
maybe this solution is more feasible:
class MyEntity:
String name
final static private Something? frequencyChoices = {1000, 2000, 3000}, {"BSD", "Apache", "GPL"}
Integer frequency
final static private String[] typeChoices = new String[] {"BSD", "Apache", "GPL"}
Integer type
#Choices(MyEntity.frequencyChoices)
String getFrequency()
return frequency)
#IntervalChoices(MyEntity.typeChoices)
String getType()
return type
*get** accessors return strings according to this table.
value(type) HumanReadableString(type)
1 BSD
2 Apache
3 GPL
min frequency max frequency HumanReadableString(frequency)
0 1000 rare
1000 2000 frequent
2001 3000 sexy
It should be possible to get all possible values that an attribute can take, example:
getChoices(MyEntity, "type") returns ("rare", "frequent", "sexy")
It should be possible to get the bound value from the string:
getValue(MyEntity, "frequency", "sexy") returns (2000,3000)
edit: purpose of all of this This methods should simplify the generation of forms and requests (of course this should not be view implementation bound)
edit: added how I would like to tell Java that some attributes are spécial so that it can generate get* accessors acordingly.
edit: added how to submit in the code the choices
edit: the only thing that I store in the db is integers, when I want to print them they should be converted somehow to their human readable string.
You can have additional info in enums:
public enum License {
GPL("GPL"),
APACHE("Apache License");
public License(String displayName) {
this.displayName=displayName;
}
String displayName;
}
Additional functions as required, but have a close look which functions are already provided by the Enum classes.
You can do it without any hassle (but note, that the value in the DB will be the ordinal() value of the enums. So:
public enum License { GPL, APACHE, BSD }
FrequencyChoices could go into an #ElementCollection.
If you need human readable values, you may want to convert your Enum to an ordinary class, and persist it as a separate table, so you can add new licenses more easily to the list...
#Entity
public class License {
#Id long id;
String name;
}
I have not tried to persist this but you can try following http://marekhalmo.blogspot.sk/2012/09/cool-java-enums.html
I would stick to enums.