Spring Boot, Hibernate, Querydsl: antlr.NoViableAltException: неожиданный токен - PullRequest
2 голосов
/ 30 января 2020

В настоящее время я занимаюсь разработкой хранилища данных с использованием весенней загрузки, гибернации и querydsl. Почти все работает нормально, но у меня возникли проблемы при выполнении поискового запроса для одной из моих сущностей с именем group. Ошибки не очень полезны:

Мой запрос прост /group/advancedSearch?page=0&size=10&sort=name,asc&search=groupCode:dfa,name:dfa,

Ошибки возникают в моем сервисе, когда я вызываю метод репозитория.

antlr.NoViableAltException: unexpected token: group
[...]
java.lang.NullPointerException: null

Чтобы сделать это более понятным, мой код приведен ниже. У меня есть тот же метод для большинства моих сущностей, и там он работает нормально. Поскольку я понятия не имел, откуда взялся unexpected token group, я взглянул на сгенерированный класс QGroup, и я нашел этот кодовый код public static final QGroup group = new QGroup("group1");. Имя group1 заставило меня задуматься, но я не уверен, имеет ли это какое-либо отношение к ошибкам. Во всех других классах строка всегда была именем класса с маленькими начальными буквами.

Я думал, что сущность group может дублироваться, поэтому querydsl создаст group и group1, но это не так дело. Итак, есть идеи, откуда могут возникнуть ошибки и как их предотвратить / исправить?

Сущность:

@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;

    [...]
}

Функция контроллера, в которой возникают ошибки:

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

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

Метод обслуживания:

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

Репозиторий:

@RepositoryRestResource(collectionResourceRel = GROUP_URL, path = GROUP_URL)
@CrossOrigin(exposedHeaders = "Access-Control-Allow-Origin")
public interface GroupRepository extends PagingAndSortingRepository<Group, Long>, JpaSpecificationExecutor<Group>, QuerydslPredicateExecutor<Group> {
}

Сгенерированный класс 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

    [...]

Обновление :

Я наконец нашел сгенерированный запрос:

select group1
from Group group1
where ?1 = ?1 and lower(group.groupCode) like ?2 escape '!'

Я думаю, что здесь проблема , Форма SQL для разработчиков, group.groupCode должна быть group1.groupCode. Кто-нибудь знает, как это исправить?


Обновление 2 [2020-02-14]:

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

1 Ответ

0 голосов
/ 21 февраля 2020

Я до сих пор не понимаю, почему с помощью querydsl сгенерированное имя класса Group равно group1, но в сочетании с моими GenericPredicateBuilder и GenericPredicate это приводит к несогласованности sql, как показано в вопросе. Но я наконец смог исправить это, к сожалению, очень грязным способом. Для полноты вот мое 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;
    }
}

Вы найдете грязное исправление в конструкторе. Я действительно ненавижу это, но это работает, и сгенерированный sql в порядке.

Возможно, я неправильно использую generi c. Я открыт для советов.

...