Поиск по нескольким полям в Java

265
19 апреля 2017, 11:23

Пишу программу на Java + PostgreSQL. Нужно сделать поиск по нескольким полям как на фото:

Поля могут быть пустыми.

Вот код, который сейчас работает на два поля:

for (int i = 0; i < protokols.size(); i++) {
    if (protokols.get(i).getNomer().contains(x)) {
        if (protokols.get(i).getSotrudnik_Key().contains(y)) {
            filterProtokols.add(protokols.get(i));
        }
    }
}
if (x.equals("")) {
    for (int i = 0; i < protokols.size(); i++) {
        if (protokols.get(i).getSotrudnik_Key().contains(y)) {
            filterProtokols.add(protokols.get(i));
        }
    }
}

Как это лучше реализовать?

Answer 1

Если все поля строковые, то просто:

String query = "SELECT * FROM some_table";
List<String> paramsList;
Map<String, String> paramsMap = new LinkedHashMap<>();
// Здесь добавляем значения полей формы, если они не равны пустой строке
paramsMap.put("first_parameter", "qwerty");
paramsMap.put("second_parameter", "12345");
paramsList = paramsMap.keySet().stream()
    .map(i -> String.format("%s = ?", i))
    .collect(Collectors.toList());
if(paramsList.size() > 0)
    query += " WHERE " + String.join(" AND ", paramsList);
System.out.println(query);

Можно было бы этим и ограничиться, перебирая не ключи, а пары и вставляя значения параметров вместе с именами в методе map(). Но тогда не было бы экранирования и можно было бы нарваться на SQL injection. Поэтому добавим к генерации запроса PreparedStatement:

try {
    Class.forName("org.sqlite.JDBC");
    try (Connection connection = DriverManager.getConnection("jdbc:sqlite:test.db")) {
        try (PreparedStatement stmt = connection.prepareStatement(query)) {
            int pn = 1;
            for (String value : paramsMap.values()) {
                stmt.setString(pn++, value);
            }
            ...
        }
    }
    catch (SQLException e) {
        e.printStackTrace();
    }
}
catch (ClassNotFoundException e) {
    e.printStackTrace();
}
Answer 2

Если по какой-то причине фильтровать нужно обязательно на стороне Java, то при условии что все поля в классе Protocol типа String, с использованием Java 8 можно сделать так:

  1. Фильтр сделать в виде объекта класса Protocol
  2. Создать список методов, возвращающих сравниваемые поля объекта
  3. Сравнить каждый протокол с фильтром по заданному списку методов.

Тестовый вариант класса Protocol:

public class Protocol
{
    private final String
            nomer,
            sotrudnik_Key,
            street;
    public Protocol(String nomer, String sotrudnik_Key, String street)
    {
        this.nomer = nomer;
        this.sotrudnik_Key = sotrudnik_Key;
        this.street = street;
    }
    public String getNomer() { return nomer; }
    public String getSotrudnik_Key() { return sotrudnik_Key; }
    public String getStreet() { return street; }
    @Override
    public String toString() { return nomer + " " + sotrudnik_Key + " " + street; }
}

Сама фильтрация:

public static List<Protocol> filter(List<Protocol> allProtocols, Protocol filter,
        List<Function<Protocol, String>> comparingFields)
{
    return allProtocols.stream()
            .filter(protocol -> test(protocol, filter, comparingFields))
            .collect(Collectors.toList());
}
private static boolean test(Protocol protocol, Protocol filter,
        List<Function<Protocol, String>> comparingFields)
{
    return comparingFields.stream()
            .allMatch(func -> func.apply(protocol).contains(func.apply(filter)));
}

И пример использования:

Protocol filter = new Protocol("2", "sk", "");
List<Protocol> allProtocols = Arrays.asList(new Protocol("n12", "sk12", "st12"),
        new Protocol("n23", "sk23", "st23"), new Protocol("n34", "sk34", "st34"));
List<Function<Protocol, String>> comparingFields = Arrays.asList(Protocol::getNomer,
        Protocol::getSotrudnik_Key, Protocol::getStreet);
List<Protocol> filteredProtocols = filter(allProtocols, filter, comparingFields);
System.out.println(filteredProtocols);

Может показаться, что тут слишком много кода для сравнения по всего трём полям, однако не стоит забывать, что, во-первых, из кода исключено дублирование, которого возникло бы при проверке каждого поля "в лоб". А во-вторых, для расширения сравнения с 3 до 9 полей достаточно просто изменить список comparingFields и обойтись без добавления кучи кода для сравнения по новым 6 полям.

При использовании более старой версии Java подобный подход можно реализовать с помощью рефлексии, сделав comparingFields в виде списка названий методов.

Answer 3

Рекомендую фильтр на стороне БД: Берешь все поля и передаешь в метод, где формируешь запрос в БД. А там что-то вроде:

String sql = sqlBegin;  //Начало запроса "select ... from ... where ...
if (x1.length()>0){   //Поле фильтра первое
    sql +=" and ..." //Добавляешь условие.
}
if (x2.length()>0){   //Поле фильтра второе 
    sql +=" and ..." //Добавляешь условие.
}
//И т.д.
sql += sqlEnd; //Хвост запроса " ... order by ..."

Пример примитивный. Естественно должный быть повешены ограничения на соответствие вводимых данных требованиям и т.п.

READ ALSO
android firebase tokens? как найти всех пользователей?

android firebase tokens? как найти всех пользователей?

Здравствуйте, у меня небольшая проблема возникла, мне необходимо организовать поиск по всем пользователям по базе данных, сохраняю данные...

323
Как проверть SOAP запрос и найти ошибку?

Как проверть SOAP запрос и найти ошибку?

Помогите разобраться как проверить запрос и найти проблему? У меня есть

322
Уменьшается размер файла при записи в Java

Уменьшается размер файла при записи в Java

Есть bytearray, в котором хранятся байты картинкиЧитал их с помощью OpenCV Imgcodecs

268