Пользовательский поиск Spring/Roo

Я хочу создать собственный поисковик (будучи ветераном веб-разработки, но новичком в Java, Spring и Roo).

Первый метод ниже относится к моему классу контроллера представления «Актив». Это то, что происходит при вашем первом переходе на страницу управления активами — он возвращается с представлением и пакетом активных в данный момент активов. Он отлично работает:

@RequestMapping("/assets")
public ModelAndView listAssets() {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");
    Collection<com.myapp.model.Asset> assets = com.myapp.model.Asset.findAllAssets() ;
    mav.addObject("assets", assets);    
    return mav;
}

Итак, теперь я хочу, чтобы запрос «POST» на этот URL-адрес принимал отправку формы поиска активов, которая содержит поля для нескольких ключевых свойств объекта «Актив».

(Сейчас я кодирую этот искатель в моем контроллере представления, и я знаю, что в конечном итоге я захочу передать его в класс модели. Пока я просто работаю здесь для удобства. Кроме того, я знаю, что не не нужно указывать полное имя объекта, я по-новому назвал свой контроллер тем же именем, что и модель, с которой он общается, поэтому, пока я не разберусь с этим, я работаю над этим.)

Я позаимствовал некоторый код из некоторых сгенерированных мной искателей Roo, и в итоге получил:

@RequestMapping(value = "/assets", method = RequestMethod.POST)
public ModelAndView searchAssets(
            @RequestParam("category") String category,
            @RequestParam("year") String year,
            @RequestParam("manufacturer") String manufacturer,
            @RequestParam("drive_type") String driveType,
            @RequestParam("subcategory") String subcategory,
            @RequestParam("serial") String serial,
            @RequestParam("listing_type") String listingType,
            @RequestParam("hours") String hours,
            @RequestParam("model") String model,
            @RequestParam("mileage") String mileage  ) {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");


    EntityManager em = com.myapp.model.Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery("SELECT o FROM Asset AS o ", Asset.class);
    Collection<Asset> assets =  q.getResultList();
    mav.addObject("assets", assets);

    return mav;
}

Итак, вопросы:

1) Есть ли способ получить набор моих параметров, а не запекать их в сигнатуре метода? Потому что я ненавижу это.

2) Есть ли способ перебрать мои параметры и таким образом сгенерировать предложение WHERE моей строки запроса? Я бы не хотел много знать о полях, которые мне здесь дают, и мне нужно быть в состоянии справиться с тем, что в основном у меня не будет их всех. Поэтому я бы предпочел просто построить свой запрос итеративно, а не декларативно, если это имеет смысл.

Как бы вы подошли к построению оператора select здесь?

РЕДАКТИРОВАНИЕ (решение):

@madth3 указал мне правильное направление. Вот что у меня получилось, чем я очень горжусь:

public static TypedQuery<Asset> findAssetsByWebRequest( WebRequest request) {

    ArrayList<String> wheres = new ArrayList<String>();

    Iterator<String> i = request.getParameterNames();

    while (i.hasNext()) {
        String fieldname = i.next();
        String value = request.getParameter(fieldname);

        if (value.length() == 0) {
            continue;
        }

        if (fieldname.equals("manufacturer") || fieldname.equals("model") ) {

            value = value.replace('*', '%');
            if (value.charAt(0) != '%') {
                value = "%" + value;
            }
            if (value.charAt(value.length() - 1) != '%') {
                value = value + "%";
            }
            wheres.add(" o." + fieldname + " LIKE '" + value + "'");
        }
        else if (fieldname.contains("min")) {
            fieldname = fieldname.replace("min", "").toLowerCase();
            wheres.add(" o." + fieldname + " >= '" + value + "' ");
        }
        else if (fieldname.contains("max")) {
            fieldname = fieldname.replace("max", "").toLowerCase();
            wheres.add(" o." + fieldname + " <= '" + value + "' ");

        }
        else {
            wheres.add(" o." + fieldname + " = '" + value + "' ");
        }
    }


    String query = "SELECT o FROM Asset AS o ";
    if (wheres.size() > 0) {
        query += " WHERE ";
        for (String clause: wheres) {
            query += clause + " AND "; 
        }
        query += " 1 = 1 ";
    }


    EntityManager em = Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery(query, Asset.class);

    return q;
}

Очевидно, что его необходимо параметризовать, чтобы избежать атаки SQL-инъекцией, но здорово, что ему (почти) не нужно ничего знать о передаваемых ему данных, что он просто соберет запрос из полей, которые ему были предоставлены. Ему нужно знать, с какими полями он что делает — какие поля являются «подобными», а какие — «равными», как обрабатывать «минимальные» и «максимальные» поля, определяющие диапазон, и т. д. Но это не так уж плохо.


person Dan Ray    schedule 20.10.2011    source источник
comment
Я думаю, что лучше создать собственный поиск, как сказал Рало ниже (используя код, сгенерированный Roo), используя POJO в качестве параметра вместо WebRequest, чтобы не иметь зависимостей с MVC.   -  person jbbarquero    schedule 25.10.2011


Ответы (3)


Ну, по старинке... Стандарт сервлетов в Java определяет запрос объекта, где есть карта (хэш, словарь) с параметрами, поэтому вы можете получить к ним доступ примерно так

public ModelAndView searchAssets(HttpRequest request) {
    StringBuilder builder = new StringBuilder();
    for (String param : request.getParameterNames()) {
        value = request.getParameter(param);
        builder.append(param);
        builder.append("="); // or LIKE
        builder.append("\'" + value "\'");
        builder.append(" AND ");
    }
    // deleting the last " AND "
    builder.delete(builder.length-5, builder.length);
    // In the entity create a method that executes a query with the string
    // If the parameter names are column names then you'd have to use a nativeQuery
    // You'd have to look it up in JPA
   List<Asset> list = Asset.search(builder.toString());
   // put the list in the ModelAndView
}
person madth3    schedule 21.10.2011
comment
Хороший. Я попробую это сегодня. - person Dan Ray; 21.10.2011

Я сделал комментарий, но предпочитаю объяснять свой подход в ответе:

Я думаю, что лучше создать собственный поиск, как сказал Рало ниже (используя код, сгенерированный Roo), используя POJO в качестве параметра вместо WebRequest, чтобы не иметь зависимостей с MVC.

Попробуйте создать POJO, например, AssetFilter, содержащий атрибуты, которые вы хотите использовать в поисковике.

public static TypedQuery<Asset> findAssetsByFilter(AssetFilter filter) {
    If (filter.getManufacturer()!=null) where.append(" AND ...")
}

Контроллер создаст фильтр POJO с запросом, и вы можете использовать этот поиск везде, вы даже можете создать для него интеграционный тест.

person jbbarquero    schedule 24.10.2011

Spring roo может сгенерировать для вас искатели:

тип:

finder list --class ~.domain.MyClass   
finder add --finderName findMyClassByWhatEver

finder list перечислит все средства поиска с одним атрибутом, количество атрибутов можно указать с помощью необязательного параметра

person Ralph    schedule 20.10.2011
comment
Это правда, но генерируемые им средства поиска требуют заполнения каждого поля. И если вы втолкнете их в свою модель и удалите проверку, они умрут при попытках доступа к полям, которых там нет. Так или иначе, похоже, что нужен какой-то собственный код. Меня это особо не беспокоит, просто это на языке, которого я не знаю, так что... - person Dan Ray; 21.10.2011
comment
Хорошо, я вижу. Но вы можете взглянуть на сгенерированный код и использовать его в качестве чернового варианта того, что вам нужно реализовать. - person Ralph; 21.10.2011