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

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.

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)

How to make commons methods for a few classes(Java, Spring)?

I have two methods in my controllers that I should insert in each controller. Code of these methods are changed depending on the controller.
For example, I have:
private T1 variantDTO(Variant variant, T2 entity) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return T3.toIdDTO(entity);
default:
return T3.toDTO(entity);
}
}
private List<? extends T1> variantDTOList(Variant variant, List<T2> entityList) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return T3.toIdDTOList(entityList);
default:
return T3.toDTOList(entityList);
}
}
T1, T2, T3 should be changed in each controller.
T3 is autowired field.
For example:
#Autowired
EntityMapper entityMapper;
And then:
//...
case ID:
return entityMapper.toIdDTOList(entityList);
//...
I can't understand how to do this.
Can you help me? What should I do? When I want to change these methods I should do it in a few controllers. It's very hard.
UPDATE:
I try to do a common parametric class with these methods that I want to inheritance in my controllers. But I get errors in methods(toIdDTO, toDTO, ...)
public class VariantController<Abstract, Entity, Mapper> {
#Autowired
Mapper mapper;
private Abstract variantDTO(Variant variant, Entity entity) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return mapper.toIdDTO(entity);
default:
return mapper.toDTO(entity);
}
}
private List<? extends Abstract> variantDTOList(Variant variant, List<Entity> entityList) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return mapper.toIdDTOList(entityList);
default:
return mapper.toDTOList(entityList);
}
}
}
For example:
Cann't resolve method toIdDTO(entity)
UPDATE2:
I have a different mapper in each controller.
UPDATE3 - Full Example:
Controller:
#RestController
#RequestMapping("/api/groupsparameters")
public class GroupParametersController {
#Autowired
private GroupParameterService groupParameterService;
#Autowired
private GroupParameterMapper groupParameterMapper;
#GetMapping("/{id}")
public ResponseEntity<? extends GroupParameterAbstract> get(#PathVariable Long id,
#RequestParam(value = "variant", required = false) Variant variant) throws EntityNotFoundException {
GroupParameter groupParameter = groupParameterService.findById(id);
return new ResponseEntity<>(variantDTO(variant, groupParameter), HttpStatus.OK);
}
#GetMapping
public ResponseEntity<List<? extends GroupParameterAbstract>> findAll(#RequestParam(value = "variant", required = false) Variant variant,
#RequestParam(value = "search", required = false) String search) {
List<GroupParameter> groupParameterList = groupParameterService.findAll();
return new ResponseEntity<>(variantDTOList(variant, groupParameterList), HttpStatus.OK);
}
private GroupParameterAbstract variantDTO(Variant variant, GroupParameter entity) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return groupParameterMapper.toIdDTO(entity);
default:
return groupParameterMapper.toDTO(entity);
}
}
private List<? extends GroupParameterAbstract> variantDTOList(Variant variant, List<GroupParameter> entityList) {
if (variant == null) variant = Variant.DEFAULT;
switch (variant) {
case ID:
return groupParameterMapper.toIdDTOList(entityList);
default:
return groupParameterMapper.toDTOList(entityList);
}
}
}
Mapper:
#Mapper(componentModel = "spring", uses = { TypeReportService.class})
public interface GroupParameterMapper {
//GroupParatemerDTO
GroupParameterDTO toDTO(GroupParameter groupParameter);
List<GroupParameterDTO> toDTOList(List<GroupParameter> groupParameterList);
//GroupParameterIdDTO
#Mapping(target = "typeReportId", source = "typeReport.id")
GroupParameterIdDTO toIdDTO(GroupParameter groupParameter);
List<GroupParameterIdDTO> toIdDTOList(List<GroupParameter> groupParameterList);
#Mapping(target = "id", ignore = true)
#Mapping(target = "typeReport", source ="typeReportId", qualifiedByName = { "findTypeReportById" })
GroupParameter fromIdDTO(GroupParameterIdDTO groupParameterIdDTO) throws EntityNotFoundException;
//GroupParameter
#Mapping(target = "id", ignore = true)
#Mapping(target = "common", ignore = true)
#BeanMapping(nullValuePropertyMappingStrategy = IGNORE)
void fill(GroupParameter source, #MappingTarget GroupParameter target);
}
Entity:
#Entity
#Data
public class GroupParameter {
#Id
#GeneratedValue(generator = ID_GENERATOR)
private Long id;
private String title;
private boolean common;
#ManyToOne
#JoinColumn(name = "TYPE_REPORT_ID", nullable = false)
private TypeReport typeReport;
}
DTO:
#Data
public abstract class GroupParameterAbstract {
private Long id;
private String title;
private boolean common;
}
public class GroupParameterDTO extends GroupParameterAbstract {
}
#Data
public class GroupParameterIdDTO extends GroupParameterAbstract {
private Long typeReportId;
}

CrudRepository filter for String value in List<String> property

basically I got following entity (extended by Lombok)
#Getter
#Setter
#Entity("FOO")
public class Foo{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private long id;
#ManyToOne
#JoinColumn(name = "FK_FEE", nullable = false)
private Fee fee;
#Column(name = "CCCS")
#Convert(converter = StringListConverter.class)
private List<String> cccs;
}
And the StringListConverter:
#Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(final List<String> list) {
String returnValue = null;
if (list != null) {
final List<String> trimmedList = new ArrayList<>();
for (final String strg : list) {
if (strg != null && !strg.isEmpty()) {
trimmedList.add(strg.trim());
}
}
returnValue = String.join(",", trimmedList);
}
return returnValue;
}
#Override
public List<String> convertToEntityAttribute(final String joined) {
List<String> returnValue = null;
if (joined != null) {
returnValue = new ArrayList<>();
final String[] splitted = joined.split(",");
for (final String strg : splitted) {
if (strg != null && !strg.isEmpty()) {
returnValue.add(strg.trim());
}
}
}
return returnValue;
}
}
And now I want to fetch a List of Foo where Fee.Id= 123 and Foo.cccs contains a specific string value.
#Repository
public interface FooRepository extends CrudRepository<Foo, Long> {
List<Foo> findByFeeIdAndCccsIn(Long feeId, String ccc);
}
But this does not work. Is the only way to solve this by writing an own query?
#Repository
public interface FooRepository extends CrudRepository<Foo, Long> {
#Query( "Select foo FROM Foo foo WHERE foo.fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :cccs) ,'%')")
List<Foo> findByFeeIdAndCccsIn(#Param("feeId") Long feeId,#Param("cccs") String ccc);
}
Currently it seems the only solution is to use #Query and write a JPQL or native SQL query to get the data.
String SELECT_BY_FEE_AND_CCC = "Select foo FROM Foo foo "
+ " WHERE foo.fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :ccc) ,'%')";
and attached with the #Query annotation like:
#Query(SELECT_BY_FEE_AND_CCC)
List<Foo> findByFeeIdAndCccsContains(#Param("feeId") Long feeId, #Param("ccc") String ccc);
NOTE/EDIT:
Sometimes you have to join the foreign table to have access on it's properties.
String SELECT_BY_FEE_AND_CCC = "Select foo FROM Foo foo "
+ " JOIN foo.fee fee "
+ " WHERE fee.id = :feeId AND foo.cccs LIKE CONCAT(CONCAT('%', :ccc) ,'%')";
And now I want to fetch a List of Foo where Fee.Id= 123 and Foo.cccs
contains a specific string value.
You have specific requirements. According to Spring Data JPA doc what you are trying to do is not supported.
So you should use #Query for your requirements.

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

Handle optional parameters in QueryDSL

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);
}

Categories