Handle optional parameters in QueryDSL - java

I am using QueryDSL with SpringData.
I have Table say, Employee and I have created entity class say, EmployeeEntity
I have written following service method
public EmployeeEntity getEmployees(String firstName, String lastName)
{
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression query = null;
if(firstName != null)
{
query = employee.firstName.eq(firstName);
}
if(lastName != null)
{
query = query.and(employee.lastName.eq(lastName)); // NPException if firstName is null as query will be NULL
}
return empployeeDAO.findAll(query);
}
As in above I commented the NPException. How to use QueryDSL for optional Parameters in QueryDSL using Spring Data?
Thank you :)

BooleanBuilder can be used as a dynamic builder for boolean expressions:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanBuilder where = new BooleanBuilder();
if (firstName != null) {
where.and(employee.firstName.eq(firstName));
}
if (lastName != null) {
where.and(employee.lastName.eq(lastName));
}
return empployeeDAO.findAll(where);
}

BooleanBuilder is good. You can also wrap it and add "optional" methods in order to avoid the if conditions:
For example, for "and" you can write: (Java 8 lambdas are used)
public class WhereClauseBuilder implements Predicate, Cloneable
{
private BooleanBuilder delegate;
public WhereClauseBuilder()
{
this.delegate = new BooleanBuilder();
}
public WhereClauseBuilder(Predicate pPredicate)
{
this.delegate = new BooleanBuilder(pPredicate);
}
public WhereClauseBuilder and(Predicate right)
{
return new WhereClauseBuilder(delegate.and(right));
}
public <V> WhereClauseBuilder optionalAnd(#Nullable V pValue, LazyBooleanExpression pBooleanExpression)
{
return applyIfNotNull(pValue, this::and, pBooleanExpression);
}
private <V> WhereClauseBuilder applyIfNotNull(#Nullable V pValue, Function<Predicate, WhereClauseBuilder> pFunction, LazyBooleanExpression pBooleanExpression)
{
if (pValue != null)
{
return new WhereClauseBuilder(pFunction.apply(pBooleanExpression.get()));
}
return this;
}
}
#FunctionalInterface
public interface LazyBooleanExpression
{
BooleanExpression get();
}
And then the usage would be much cleaner:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
return empployeeDAO.findAll
(
new WhereClauseBuilder()
.optionalAnd(firstName, () -> employee.firstName.eq(firstName))
.optionalAnd(lastName, () -> employee.lastName.eq(lastName))
);
}
It is possible also to use jdk's Optional class

This is Java 101 actually: check for null and initialize the query instead of concatenating predicates. So a helper method like this could do the trick:
private BooleanExpression createOrAnd(BooleanExpression left, BooleanExpression right) {
return left == null ? right : left.and(right);
}
Then you can simply do:
BooleanExpression query = null;
if (firstName != null) {
query = createOrAnd(query, employee.firstName.eq(firstName));
}
if (lastName != null) {
query = createOrAnd(query, employee.lastName.eq(lastName));
}
…
Note, that I use createOrAnd(…) even in the first clause simply for consistency and to not have to adapt that code in case you decide to add a new clause even before the one for firstName.

if you check the QueryDSL implementation of null:
public BooleanExpression and(#Nullable Predicate right) {
right = (Predicate) ExpressionUtils.extract(right);
if (right != null) {
return BooleanOperation.create(Ops.AND, mixin, right);
} else {
return this;
}
}
which is supposedly what you want.

I faced same problem and here comes another version of Timo Westkämper
's accepted answer using the Optional.
default Optional<Correlation> findOne(
#Nonnull final String value, #Nullable final String environment,
#Nullable final String application, #Nullable final String service) {
final QSome Some = QSome.some;
final BooleanBuilder builder = new BooleanBuilder();
ofNullable(service).map(some.service::eq).map(builder::and);
ofNullable(application).map(some.application::eq).map(builder::and);
ofNullable(environment).map(some.environment::eq).map(builder::and);
builder.and(some.value.eq(value));
return findOne(builder);
}

For any one wants to build predicate based on dynamic request parameters map instead of specific ones can use the following simple format,
public List<User> searchUser(Map<String, Optional<String>> requestParams ){
QUser qUser = Quser.qUser;
BooleanBuilder builder = new BooleanBuilder();
requestParams.forEach( (String key, String value) -> {
if(!value.isEmpty()) {
StringPath column = Expressions.stringPath(qUser, key);
builder.and(column.eq(value));
}
});
}
And here is my controller
#RequestMapping(value = "", method = RequestMethod.GET)
public ResponseEntity<List<User>> searchUser(
#RequestParam() Map<String, Optional<String>> requestParams) {
List<User> userList = userService.searchUser(requestParams);
if(userList!=null)
return new ResponseEntity<>(userList, HttpStatus.OK);
else
return new ResponseEntity<>(userList, HttpStatus.INTERNAL_SERVER_ERROR);
}

Base on what you need i would do this
public List<EmployeeEntity> getEmployees(Optional<String> firstName, Optional<String> lastName)
{
BooleanExpression queryPredicate = QEmployeeEntity.employeeEntity.firstName.containsIgnoreCase(firstName.orElse("")).and(QEmployeeEntity.employeeEntity.lastName.containsIgnoreCase(lastName.orElse("")));
return empployeeDAO.findAll(queryPredicate);
}
First of all you should return a List of EmployeeEntity. Second, its better to use optional than checking if its null, and you may pass Java 8's Optional values obtained from optional RequestParam ones like this:
#RequestMapping(value = "/query", method = RequestMethod.GET)
public ModelAndView queryEmployee(#RequestParam(value = "firstName", required = false) Optional<String> firstName, #RequestParam(value = "lastName", required = false) Optional<String> lastName)
{
List<EmployeeEntity> result = getEmployees(firstName, lastName);
....
}
And a very important thing is to use the containsIgnoreCase function in the predicate: its better than a typical like cause its case insensitive.
In my opinion you should use some approach like this:
#Controller
class UserController {
#Autowired UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, #QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
look it at here.

This is a very simple way to deal with optional parameters, I use it in my project :
public List<ResultEntity> findByOptionalsParams(String param1, Integer param2) {
QResultEntity qResultEntity = QResultEntity.resultEntity;
final JPQLQuery<ResultEntity> query = from(qResultEntity);
if (!StringUtils.isEmpty(param1)) {
query.where(qResultEntity.field1.like(Expressions.asString("%").concat(param1).concat("%")));
}
if (param2 != null) {
query.where(qResultEntity.field2.eq(param2));
}
return query.fetch();
}

There is another way using Optional without BooleanBuilder although the resulting query might be a bit verbose:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = ofNullable(firstName).map(employee.firstName::eq).orElse(Expressions.TRUE)
.and(ofNullable(lastName).map(employee.lastName::eq).orElse(Expressions.TRUE));
return empployeeDAO.findAll(where);
}
Taking that idea and adding a helper function improves readability though:
public EmployeeEntity getEmployees(String firstName, String lastName) {
QEmployeeEntity employee = QEmployeeEntity.employeeEntity;
BooleanExpression where = optionalExpression(firstName, employee.firstName::eq)
.and(optionalExpression(lastName, employee.lastName::eq));
return empployeeDAO.findAll(where);
}
public static <T> BooleanExpression optionalExpression(T arg, Function<T, BooleanExpression> expressionFunction) {
if (arg == null) {
return Expressions.TRUE;
}
return expressionFunction.apply(arg);
}

Related

How to fix missing descriptor for class POJO after update server? [duplicate]

I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)

Spring Boot Rest Web Service fetching multiple parameters in Get Request

I am creating Spring Boot Web service and I have a Model Employee
public class Employee {
private String id;
private String name;
private String designation;
private int salary;
//Has Getters and Setters
}
I want to create a Get request which will fetching and filter the List of Employees based on the parameters given by user.
For example, if the user gives name of an employee and designation of employee, the get method should filter those result. For various combination of parameters it should work.
#Override
public List<Employee> getEmployees(Map<String, Object> parameters) {
if (parameters.size() == 0)
// code to return all employees;
List<Employee> selectedEmployees = new ArrayList<Employee>();
for(Employee currentEmployee: new ArrayList<Employee>(employee.values())) {
for(Map.Entry<String, Object> check: parameters.entrySet()) {
try {
if(check.getValue() instanceof Integer) {
int condition = (int) Employee.class.getMethod("get" + check.getKey()).invoke(currentEmployee);
if((int) check.getValue() == condition)
selectedEmployees.add(currentEmployee);
} else if (check.getValue() instanceof String) {
String condition = (String) Employee.class.getMethod("get" + check.getKey()).invoke(currentEmployee);
if (((String) check.getValue()).equals(condition))
selectedEmployees.add(currentEmployee);
}
} catch(Exception e){
e.printStackTrace();
}
}
}
return selectedEmployees;
}
In order to avoid multiple if else cases I am filtering list based on String and Integer above.
I think I am making an error in the below code which sending request in Controller.
#RequestMapping(value={"/employees","/{id}/{name}/{designation}/{salary}"})
public List<Employee> getEmployeeByProperty(EmployeeRequestParameters requestParams){
//Map for storing parameters to filter the List
Map<String, Object> filterParams = new HashMap<>();
if(requestParams.getIdParam().isEmpty()) {
filterParams.put("id", Integer.parseInt(requestParams.getIdParam()));
}
if(!requestParams.getNameParam().isEmpty()) {
filterParams.put("name", requestParams.getNameParam());
}
if(!requestParams.getDesignationParam().isEmpty()) {
filterParams.put("designation", requestParams.getDesignationParam());
}
if(requestParams.getSalaryParam().isEmpty()) {
filterParams.put("salary", Integer.parseInt(requestParams.getSalaryParam()));
}
return EmployeeService.getEmployeesByProperty(filterParams);
}
If {id} field is not full, {name} or {designation} or {salary} to be null.For {name} or {designation} or {salary} to be full Because should be {id} full.
#GetMapping("/employees")
public List<Employee> getEmployeeByProperty(#RequestParam(value = "id", required=false) String id,
#RequestParam(value = "name", required=false) String name,
#RequestParam(value = "designation", required=false) String designation,
#RequestParam(value = "salary", required=false) int salary) {
//Your codes
}
Even if {id} is empty, you can use others.

Spring Boot, Hibernate, Querydsl: antlr.NoViableAltException: unexpected token

I am currently developing a data warehouse with spring boot, hibernate and querydsl.
Nearly everything is working fine, but I got trouble doing a search request for one of my entity called group. The errors, are not really helpful:
My request is simple /group/advancedSearch?page=0&size=10&sort=name,asc&search=groupCode:dfa,name:dfa,
The errors raise in my service when I do call the repository method.
antlr.NoViableAltException: unexpected token: group
[...]
java.lang.NullPointerException: null
To make this more understandable my code is below. I have the same method for most of my entities and there it is working fine. Because I had no clue where the unexpected token group might come from, I had a look at the generated class QGroup, there I found this peace of code public static final QGroup group = new QGroup("group1");. The name group1 made me wonder, but I'm not sure if it has anything to do with the errors. In all other classes the string was always the name of the class with initial letters small.
I thought the entity group might be duplicated, so querydsl would create group and group1, but that's not the case. So any ideas where the errors might come from and how to prevent / fix them?
The entity:
#Entity
#Table(name = "[Group]")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Group_ID")
private long groupId;
#ManyToOne()
#JoinColumn(name = "Dimension_ID")
private Dimension dimension;
#Column(name = "Dimension_ID", updatable = false, insertable = false)
private Long dimensionId;
#Column(name = "GroupCode")
private String groupCode;
#Column(name = "Name")
private String name;
[...]
}
The function of the controller where the errors raise:
#RequestMapping(value = GROUP_URL + "/advancedSearch", method = RequestMethod.GET)
#ResponseBody
public PagedResources<Group> advancedSearch(
#RequestParam(value = "search", required = false) String search,
Pageable pageable, #RequestParam MultiValueMap<String, String> parameters,
PersistentEntityResourceAssembler persistentEntityResourceAssembler
) {
SimpleGrantedAuthority[] allowedRoles = {SYSADMIN};
GeneralPredicateBuilder<Group> builder = new GeneralPredicateBuilder<>(Group.class);
Predicate predicate = predicateService.getPredicateFromParameters(parameters, Group.class);
Page<Group> results = service.advancedSearch(
this.buildAdvancedSearch(search, predicate, builder), pageable, allowedRoles);
return super.toPagedResource(results, persistentEntityResourceAssembler);
}
public Predicate buildAdvancedSearch(String search, Predicate predicate, GeneralPredicateBuilder<T> builder) {
if (search != null) {
Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
Matcher matcher = pattern.matcher(search + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2), matcher.group(3));
}
BooleanExpression expression = builder.build();
if (predicate != null) {
predicate = expression.and(predicate);
} else {
predicate = expression;
}
}
return predicate;
}
The PredicateService:
#Service
public class PredicateService {
#Autowired
private final QuerydslPredicateBuilder querydslPredicateBuilder;
#Autowired
private final QuerydslBindingsFactory querydslBindingsFactory;
public PredicateService(QuerydslPredicateBuilder querydslPredicateBuilder, QuerydslBindingsFactory querydslBindingsFactory) {
this.querydslPredicateBuilder = querydslPredicateBuilder;
this.querydslBindingsFactory = querydslBindingsFactory;
}
public <T> Predicate getPredicateFromParameters(final MultiValueMap<String, String> parameters, Class<T> tClass) {
TypeInformation<T> typeInformation = ClassTypeInformation.from(tClass);
return querydslPredicateBuilder.getPredicate(typeInformation, parameters, querydslBindingsFactory.createBindingsFor(typeInformation));
}
}
The service method:
public Page<Group> advancedSearch(Predicate predicate, Pageable pageable, SimpleGrantedAuthority[] roles){
if (SecurityUtils.userHasAnyRole(roles)) {
return this.repository.findAll(predicate, pageable); // <-- here the errors raise
} else throw new ForbiddenException(FORBIDDEN);
}
The repository:
#RepositoryRestResource(collectionResourceRel = GROUP_URL, path = GROUP_URL)
#CrossOrigin(exposedHeaders = "Access-Control-Allow-Origin")
public interface GroupRepository extends PagingAndSortingRepository<Group, Long>, JpaSpecificationExecutor<Group>, QuerydslPredicateExecutor<Group> {
}
The generated class QGroup by querydsl:
#Generated("com.querydsl.codegen.EntitySerializer")
public class QGroup extends EntityPathBase<Group> {
private static final long serialVersionUID = 384278695L;
private static final PathInits INITS = PathInits.DIRECT2;
public static final QGroup group = new QGroup("group1"); // <-- this is confusing
[...]
Update:
I finally found the generated query:
select group1
from Group group1
where ?1 = ?1 and lower(group.groupCode) like ?2 escape '!'
I think here is the problem. Form a SQL developer view, group.groupCode should be group1.groupCode. Anyone knows how to fix this?
Update 2 [2020-02-14]:
The GeneralPredicateBuilder:
public class GeneralPredicateBuilder<T> {
private List<SearchCriteria> params;
private final Class<T> type;
public GeneralPredicateBuilder(Class<T> type) {
this.params = new ArrayList<>();
this.type = type;
}
public GeneralPredicateBuilder<T> with(String key, String operation, Object value) {
params.add(new SearchCriteria(key, operation, value));
return this;
}
public BooleanExpression build() {
if (params.size() == 0) {
return null;
}
List<BooleanExpression> predicates = params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<T>(param, type);
BooleanExpression tmp = predicate.getPredicate();
return tmp;
}).filter(Objects::nonNull).collect(Collectors.toList());
BooleanExpression result = Expressions.asBoolean(true).isTrue();
for (BooleanExpression predicate : predicates) {
result = result.and(predicate);
}
return result;
}
public List<Predicate> buildPredicate(){
if (params.size() == 0) {
return null;
}
return params.stream().map(param -> {
GeneralPredicate<T> predicate = new GeneralPredicate<>(param, type);
return predicate.getPredicate();
}).filter(Objects::isNull).collect(Collectors.toList());
}
}
I still don't understand why the, by querydsl, generated classname of Group is group1, but in combination with my GenericPredicateBuilder and the GenericPredicate this leads to the inconsistent sql, as shown in the question. But I was finally able to fix this, unfortunately in a really dirty way. For completeness here is my GeneralPredicate:
public class GeneralPredicate<T> {
private SearchCriteria searchCriteria;
private final Class<T> type;
private final String variable;
public GeneralPredicate(SearchCriteria param, Class<T> type) {
searchCriteria = param;
this.type = type;
if(type.getSimpleName().equals("Group")){
this.variable = "group1";
} else {
this.variable = type.getSimpleName().replaceFirst("" + type.getSimpleName().charAt(0), "" + type.getSimpleName().charAt(0)).toLowerCase();
}
}
public BooleanExpression getPredicate() {
PathBuilder<T> entityPath = new PathBuilder<T>(type, variable);
if (isNumeric(searchCriteria.getValue().toString())) {
NumberPath<Integer> path = entityPath.getNumber(searchCriteria.getKey(), Integer.class);
int value = Integer.parseInt(searchCriteria.getValue().toString());
switch (searchCriteria.getOperation()) {
case ":":
return path.eq(value);
case ">":
return path.goe(value);
case "<":
return path.loe(value);
}
} else {
StringPath path = entityPath.getString(searchCriteria.getKey());
switch (searchCriteria.getOperation()) {
case ":":
return path.containsIgnoreCase(searchCriteria.getValue().toString());
case "<":
return path.startsWith(searchCriteria.getValue().toString());
case ">":
return path.endsWith(searchCriteria.getValue().toString());
}
}
return null;
}
}
You find the dirty fix within the constructor. I really hate it, but it is working and the generated sql is okay.
Maybe I use the generic in a wrong way. I am open for advices.

spring boot find by field search specification

My problem is I can search from database. But I did searching by using findAll of JpaSpecificationExecutor. But, I want to do searching by using findById and pass my specification, pageable and id to it return page. But it is not working.
Here is my controller:
#GetMapping(value = "/search")
public ResponseEntity<ResponseDTO> allAccountRightService(
#RequestParam(value = "search", required = false) String search,
#RequestParam(value = "page", required = false) Integer page,
#RequestParam(value = "size", required = false) Integer size,
#RequestParam(value = "order", required = false) String order,
#RequestParam(value = "orderBy", required = false) String orderBy) {
ResponseDTO responseDTO = new ResponseDTO("accountRightService List", accountRightService.search(search, page, size, order, orderBy));
return new ResponseEntity<>(responseDTO, HttpStatus.OK);
}
and here is my `service impl` method:
public Map<PageInformation, List<AccountRightDTO>> search(String search, Integer page, Integer size, String order,
String orderBy) {
Map<PageInformation, List<AccountRightDTO>> accountRightList = new HashMap<>();
PageInformation pageInfo = new PageInformation();
if (order == null || order.isEmpty())
order = "DESC";
if (orderBy == null || orderBy.isEmpty())
orderBy = "createdAt";
Pageable pageable = CommonUtil.createPageRequest(page, size, order, orderBy);
Specification<AccountRight> spec = CommonUtil.buildSearchSpecification(search);
//Page<AccountRight> accountRightPage = accountRightRepository.findAllByRightByAppointment(CommonUtil.getAppointment().getAppointmentID(), spec, pageable);
Page<AccountRight> accountRightPage = accountRightRepository.findAll(spec, pageable);
List<AccountRight> accountRights = accountRightPage.getContent();
List<AccountRightDTO> accountRightDTOs = new ArrayList<>();
accountRightDTOs = accountRights.stream().map(accountRight -> {
AccountRightDTO accountRightDTO = new AccountRightDTO();
AppointmentDTO rightToAppointmentDTO = new AppointmentDTO();
AppointmentDTO rightByAppointmentDTO = new AppointmentDTO();
BeanUtils.copyProperties(accountRight, accountRightDTO, "accountRightID");
accountRightDTO.setAccountRightID(Long.toString(accountRight.getAccountRightID()));
BeanUtils.copyProperties(accountRight.getRightToAppointment(), rightToAppointmentDTO, "appointmentID");
rightToAppointmentDTO.setAppointmentID(Long.toString(accountRight.getRightToAppointment().getAppointmentID()));
BeanUtils.copyProperties(accountRight.getRightByAppointment(), rightByAppointmentDTO, "appointmentID");
rightByAppointmentDTO.setAppointmentID(Long.toString(accountRight.getRightToAppointment().getAppointmentID()));
accountRightDTO.setRightByAppointment(rightByAppointmentDTO);
accountRightDTO.setRightToAppointment(rightToAppointmentDTO);
return accountRightDTO;
}).collect(Collectors.toList());
pageInfo.setSize(accountRightPage.getSize());
pageInfo.setTotalElements(accountRightPage.getTotalElements());
pageInfo.setTotalPages(accountRightPage.getTotalPages());
accountRightList.put(pageInfo, accountRightDTOs);
return accountRightList;
}
and this is my buildsearchspecification method
public static <T> Specification<T> buildSearchSpecification(String search) {
SearchSpecificationsBuilder<T> builder = new SearchSpecificationsBuilder<T>();
if (search != null && !search.isEmpty()) {
String[] str = search.split(",");
if (str != null) {
for (String strTemp : str) {
Pattern pattern = Pattern.compile("(\\p{Punct}?)(.*)(:|!|<|>|~)(.*)(\\p{Punct}?),");
Matcher matcher = pattern.matcher(strTemp + ",");
while (matcher.find()) {
builder.with(matcher.group(1), matcher.group(2),
SearchOperation.getSimpleOperation(matcher.group(3).toCharArray()[0]),
matcher.group(4));
}
}
}
}
Specification<T> spec = builder.build();
return spec;
}
and here is my findAllByRightByAppointment repository method
#Query("select account from AccountRight account where account.rightByAppointment.appointmentID=?1")
Page<AccountRight> findAllByRightByAppointment(Long appointmentID, #Nullable Specification<AccountRight> spec, Pageable pageable);
If I use findAll method than searching will work otherwise by using my custom method pagination works without searching
I found an answer by using Specification.Where(your_specification).and(your_search_specification).
here is my updated code now:
Specification<AccountRight> searchSpec = CommonUtil.buildSearchSpecification(search); //this specification needs my search string.
SearchSpecification<AccountRight> rightByAppointmentID =
new SearchSpecification<AccountRight>(new SearchCriteria("rightByAppointment.appointmentID", SearchOperation.EQUALITY, CommonUtil.getAppointment().getAppointmentID())); //this specification accepts search criteria with key, operation and value.
Page<AccountRight> accountRightPage = accountRightRepository.findAll(Specification.where(rightByAppointmentID).and(searchSpec), pageable);
//here you will just tell findAll method to findAll entities where rightByAppointmentID is equal to
//CommonUtil.getAppointment().getAppointmentID() and search query is searchSpec

Objectify sequence query

I try to have such code:
Query<Card> query = ofy().load().type(Card.class);
UserData creator = ofy().load().type(UserData.class).id(creatorId).now();
if (creator != null && UserType.USER.equals(creator.getUserType())) {
query.filter("creator", creator);
}
if (orderColumnName != null) {
query.order((ascending ? "" : "-") + orderColumnName);
}
query.offset(startRow).limit(limit);
return query.list();
But it doesn't filter.
Also this filter:
UserData creator = ofy().load().type(UserData.class).id(creatorId).now();
Query<Card> query = ofy().load().type(Card.class).filter("creator", creator);
Any idea why?
EDITED
My class Card.java
#Entity
public class Card implements PersistableObject {
#Id
Long id;
#Index
Date createDate;
...
#Index
Ref<UserData> creator;
...
public UserData getCreator() {
if (creator != null) {
return creator.get();
}
return null;
}
public void setCreator(UserData creator) {
this.creator = Ref.create(creator);
}
}
My class UserData.java
#Entity
public class UserData implements PersistableObject {
#Id
Long id;
Ref<EaistoAccount> eaistoAccount;
UserType userType;
public EaistoAccount getEaistoAccount() {
if (eaistoAccount == null) {
return null;
}
return eaistoAccount.get();
}
public void setEaistoAccount(EaistoAccount aistoAccount) {
this.eaistoAccount = Ref.create(aistoAccount);
}
}
It doesn't work means that I expect to get filtered entities to corresponding UserData but it doesn't filter when I split query in a few parts also it filters when I use query in one line.
I have found a solution:
Why aren't my queries working properly? All of Objectify's
intermediate command objects are immutable. This will not work:
Query q = ofy().load().type(Foo.class); q.filter("bar", bar);
List foos = q.list(); The filter command did nothing because you
did not reassign q. You need this:
q = q.filter("bar", bar); Alternatively, chain the whole sequence in a
single statement. Read more here.
https://code.google.com/p/objectify-appengine/wiki/FrequentlyAskedQuestions

Categories