Самый чистый способ построить строку SQL в Java

107

Я хочу создать строку SQL для манипуляций с базой данных (обновления, удаления, вставки, выборки и тому подобное) - вместо ужасного метода конкатенации строк, использующего миллионы знаков "+" и кавычек, которые в лучшем случае нечитаемы - там должен быть лучший способ.

Я действительно думал об использовании MessageFormat - но его предполагалось использовать для пользовательских сообщений, хотя я думаю, что он будет выполнять разумную работу, - но я думаю, что в библиотеках java sql должно быть что-то более согласованное с операциями типа SQL.

Подойдет ли Groovy?

Видар
источник

Ответы:

76

Прежде всего рассмотрите возможность использования параметров запроса в подготовленных операторах:

PreparedStatement stm = c.prepareStatement("UPDATE user_table SET name=? WHERE id=?");
stm.setString(1, "the name");
stm.setInt(2, 345);
stm.executeUpdate();

Другая вещь, которую можно сделать, - это сохранить все запросы в файле свойств. Например, в файле query.properties можно разместить указанный выше запрос:

update_query=UPDATE user_table SET name=? WHERE id=?

Затем с помощью простого служебного класса:

public class Queries {

    private static final String propFileName = "queries.properties";
    private static Properties props;

    public static Properties getQueries() throws SQLException {
        InputStream is = 
            Queries.class.getResourceAsStream("/" + propFileName);
        if (is == null){
            throw new SQLException("Unable to load property file: " + propFileName);
        }
        //singleton
        if(props == null){
            props = new Properties();
            try {
                props.load(is);
            } catch (IOException e) {
                throw new SQLException("Unable to load property file: " + propFileName + "\n" + e.getMessage());
            }           
        }
        return props;
    }

    public static String getQuery(String query) throws SQLException{
        return getQueries().getProperty(query);
    }

}

вы можете использовать свои запросы следующим образом:

PreparedStatement stm = c.prepareStatement(Queries.getQuery("update_query"));

Это довольно простое решение, но оно работает хорошо.

Петр Кочанский
источник
1
Я предпочитаю использовать чистый SQL-конструктор вроде этого: mentabean.soliveirajr.com
TraderJoeChicago
2
Могу я предложить вам поместить выражение InputStreamвнутрь, if (props == null)чтобы вы не создавали его экземпляр, когда он не нужен.
SyntaxRules
64

Для произвольного SQL используйте jOOQ . jOOQ в настоящее время поддерживает SELECT, INSERT, UPDATE, DELETE, TRUNCATE, и MERGE. Вы можете создать SQL так:

String sql1 = DSL.using(SQLDialect.MYSQL)  
                 .select(A, B, C)
                 .from(MY_TABLE)
                 .where(A.equal(5))
                 .and(B.greaterThan(8))
                 .getSQL();

String sql2 = DSL.using(SQLDialect.MYSQL)  
                 .insertInto(MY_TABLE)
                 .values(A, 1)
                 .values(B, 2)
                 .getSQL();

String sql3 = DSL.using(SQLDialect.MYSQL)  
                 .update(MY_TABLE)
                 .set(A, 1)
                 .set(B, 2)
                 .where(C.greaterThan(5))
                 .getSQL();

Вместо получения строки SQL вы также можете просто выполнить ее, используя jOOQ. Видеть

http://www.jooq.org

(Отказ от ответственности: я работаю в компании, стоящей за jOOQ)

Лукас Эдер
источник
не будет ли это во многих случаях плохим решением, поскольку вы не можете позволить dbms проанализировать инструкцию заранее с разными значениями для «5», «8» и т. д.? Я предполагаю, что выполнение с помощью jooq решит это?
Vegard
@Vegard: у вас есть полный контроль над тем, как jOOQ должен отображать значения привязки в своем выводе SQL: jooq.org/doc/3.1/manual/sql-building/bind-values . Другими словами, вы можете выбрать, отображать "?"ли значения привязки или встроить их.
Лукас Эдер
да, но что касается чистых способов сборки sql, то в моих глазах это был бы немного беспорядочный код, если вы не используете JOOQ для выполнения. в этом примере вы устанавливаете A на 1, B на 2 и т.д., но вам нужно сделать это еще раз, когда вы выполняете, если вы не выполняете с JOOQ.
Vegard
1
@Vegard: ничто не мешает вам передать переменную в jOOQ API и пересобрать инструкцию SQL. Кроме того, вы можете извлекать значения привязки в их порядке, используя jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() , или именованные значения привязки по их именам, используя jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Мой ответ просто содержит очень упрощенный пример ... Я не уверен, что это отвечает вашим опасениям?
Лукас Эдер
2
Это дорогостоящее решение.
Sorter
15

Одной из технологий, которую вы должны рассмотреть, является SQLJ - способ встраивать операторы SQL непосредственно в Java. В качестве простого примера в файле TestQueries.sqlj может быть следующее:

public class TestQueries
{
    public String getUsername(int id)
    {
        String username;
        #sql
        {
            select username into :username
            from users
            where pkey = :id
        };
        return username;
    }
}

Существует дополнительный этап предварительной компиляции, который берет ваши файлы .sqlj и переводит их в чистую Java - короче говоря, он ищет специальные блоки, разделенные символом

#sql
{
    ...
}

и превращает их в вызовы JDBC. Использование SQLJ дает несколько ключевых преимуществ:

  • полностью абстрагируется от уровня JDBC - программистам нужно думать только о Java и SQL
  • переводчик может проверять синтаксис ваших запросов и т. д. по базе данных во время компиляции.
  • возможность напрямую связывать переменные Java в запросах с помощью префикса ":"

Существуют реализации переводчика для большинства основных поставщиков баз данных, поэтому вы сможете легко найти все, что вам нужно.

Эшли Мерсер
источник
Согласно википедии, этот сейчас уже устарел.
Zeus
1
На момент написания (январь 2016 г.) SQLJ упоминается в Википедии как «устаревший» без каких-либо ссылок. Он был официально заброшен? Если так, я прикреплю предупреждение вверху этого ответа.
Эшли Мерсер,
NB Технология все еще поддерживается, например, в последней версии Oracle, 12c . Я признаю, что это не самый современный стандарт, но он все еще работает и имеет некоторые преимущества (например, проверка запросов к БД во время компиляции), которые недоступны в других системах.
Эшли Мерсер,
12

Мне интересно, не хотите ли вы чего-нибудь вроде Squiggle . Также очень полезным является jDBI . Однако это не поможет вам с вопросами.

tcurdt
источник
9

Я бы посмотрел на Spring JDBC . Я использую его всякий раз, когда мне нужно выполнить SQL программно. Пример:

int countOfActorsNamedJoe
    = jdbcTemplate.queryForInt("select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});

Это действительно здорово для любого вида выполнения sql, особенно для запросов; он поможет вам сопоставить наборы результатов с объектами, не добавляя сложности полной ORM.

Бент Андре Сольхейм
источник
как я могу получить реальный выполненный запрос sql? Я хочу это записать.
Кодманьяга 02
5

Я предпочитаю использовать именованные параметры JDBC Spring, поэтому я могу написать стандартную строку вроде «select * from blah where colX = ': someValue'»; Я думаю, это довольно удобно для чтения.

Альтернативой может быть предоставление строки в отдельном файле .sql и считывание содержимого с помощью служебного метода.

О, также стоит взглянуть на Squill: https://squill.dev.java.net/docs/tutorial.html

GaryF
источник
Я полагаю, вы имеете в виду, что используете BeanPropertySqlParameterSource? Я почти согласен с вами, класс, который я только что упомянул, хорош при использовании строго beans, но в противном случае я бы рекомендовал использовать настраиваемый ParameterizedRowMapper для создания объектов.
Esko
Не совсем. Вы можете использовать любой SqlParameterSource с именованными параметрами JDBC. Моим потребностям соответствовало использование MapSqlParameterSource, а не различных компонентов. В любом случае это хорошее решение. Однако RowMappers имеет дело с другой стороной головоломки SQL: превращением наборов результатов в объекты.
GaryF
4

Я поддерживаю рекомендации по использованию ORM, например Hibernate. Однако, безусловно, бывают ситуации, когда это не работает, поэтому я воспользуюсь этой возможностью, чтобы рассказать о некоторых материалах, которые я помог написать: SqlBuilder - это java-библиотека для динамического построения операторов sql с использованием стиля «построителя». он довольно мощный и довольно гибкий.

Джеймс
источник
4

Я работал над приложением сервлета Java, которое должно создавать очень динамические операторы SQL для целей специальной отчетности. Основная функция приложения - передать кучу именованных параметров HTTP-запроса в предварительно закодированный запрос и создать красиво отформатированную таблицу вывода. Я использовал Spring MVC и структуру внедрения зависимостей для хранения всех моих SQL-запросов в файлах XML и загрузки их в приложение для создания отчетов вместе с информацией о форматировании таблиц. В конце концов, требования к отчетности стали более сложными, чем возможности существующих структур сопоставления параметров, и мне пришлось написать свои собственные. Это было интересное упражнение в разработке, и он позволил создать структуру для отображения параметров, гораздо более надежную, чем все, что я мог найти.

Новые сопоставления параметров выглядели так:

select app.name as "App", 
       ${optional(" app.owner as "Owner", "):showOwner}
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = ${integer(0,50):serverId}
   and app.id in ${integerList(50):appId}
 group by app.name, ${optional(" app.owner, "):showOwner} sv.name
 order by app.name, sv.name

Прелесть полученного фреймворка заключалась в том, что он мог обрабатывать параметры HTTP-запроса непосредственно в запросе с надлежащей проверкой типов и проверкой ограничений. Для проверки ввода не требуется дополнительных сопоставлений. В приведенном выше примере запроса параметр с именем serverId будет проверен, чтобы убедиться, что он может быть приведен к целому числу и находится в диапазоне от 0 до 50. Параметр appId будет обрабатываться как массив целых чисел с максимальной длиной 50. Если поле showOwnerприсутствует и установлено значение «истина», биты SQL в кавычках будут добавлены к сгенерированному запросу для дополнительных сопоставлений полей. field Доступно еще несколько сопоставлений типов параметров, включая необязательные сегменты SQL с дополнительными сопоставлениями параметров. Он позволяет реализовать настолько сложное отображение запросов, насколько это может придумать разработчик. Он даже имеет элементы управления в конфигурации отчета, чтобы определить, будет ли данный запрос иметь окончательные сопоставления с помощью PreparedStatement или просто запускаться как предварительно созданный запрос.

Для примеров значений запроса Http:

showOwner: true
serverId: 20
appId: 1,2,3,5,7,11,13

Это даст следующий SQL:

select app.name as "App", 
       app.owner as "Owner", 
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = 20
   and app.id in (1,2,3,5,7,11,13)
 group by app.name,  app.owner,  sv.name
 order by app.name, sv.name

Я действительно думаю, что Spring или Hibernate или одна из этих структур должны предлагать более надежный механизм сопоставления, который проверяет типы, позволяет использовать сложные типы данных, такие как массивы и другие подобные функции. Я написал свой движок исключительно для своих целей, он не совсем читается для общего выпуска. На данный момент он работает только с запросами Oracle, а весь код принадлежит большой корпорации. Когда-нибудь я воспользуюсь своими идеями и построю новый фреймворк с открытым исходным кодом, но я надеюсь, что один из существующих крупных игроков примет вызов.

Наталья
источник
3

Почему вы хотите сгенерировать все sql вручную? Смотрели ли вы на ORM, например Hibernate? В зависимости от вашего проекта он, вероятно, будет делать не менее 95% того, что вам нужно, делать это более чистым способом, чем необработанный SQL, и если вам нужно получить последний бит производительности, вы можете создать SQL-запросы, требующие ручной настройки.

Джаред
источник
3

Вы также можете посетить MyBatis ( www.mybatis.org ). Это помогает вам писать операторы SQL вне вашего Java-кода и, среди прочего, сопоставляет результаты sql с вашими java-объектами.

Джошуа
источник
3

Google предоставляет библиотеку под названием Room Persitence Library, которая обеспечивает очень чистый способ написания SQL для приложений Android , в основном слой абстракции над базовой базой данных SQLite . Bellow - это короткий фрагмент кода с официального сайта:

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

В официальных документах библиотеки есть больше примеров и лучшая документация.

Также существует один под названием MentaBean, который представляет собой Java ORM . Он имеет приятные функции и кажется довольно простым способом написания SQL.

CasualCoder3
источник
В соответствии с документацией номер : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Итак, это не общая библиотека ORM для СУБД. В первую очередь он предназначен для приложений Android.
RafiAlhamd
2

Прочтите файл XML.

Вы можете прочитать его из файла XML. Его легко поддерживать и работать с ним. Существуют стандартные парсеры STaX, DOM, SAX, позволяющие сделать несколько строк кода на java.

Делайте больше с атрибутами

У вас может быть некоторая семантическая информация с атрибутами в теге, чтобы помочь вам делать больше с SQL. Это может быть имя метода или тип запроса или что-то еще, что помогает вам меньше кодировать.

Поддерживать

Вы можете поместить xml вне банки и легко поддерживать его. Те же преимущества, что и у файла свойств.

Преобразование

XML является расширяемым и легко конвертируемым в другие форматы.

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

Metamug использует xml для настройки файлов ресурсов REST с помощью sql.

Сортировщик
источник
Вы можете использовать yaml или json, если они вам нравятся. Они лучше, чем хранить в обычном файле свойств
Сортировщик
Вопрос в том, как СОЗДАТЬ SQL. Для создания SQL, если вам нужно использовать XML, Parser, Validation и т. Д., Это чрезмерно. Большинство ранних попыток построения SQL с использованием XML были отклонены в пользу Annotation. Общепринятый ответ на Piotr Kochanski является простым и элегантным и точками - решает эту проблему и ремонтопригодна. ПРИМЕЧАНИЕ. НЕТ альтернативного способа поддерживать лучший SQL на другом языке.
RafiAlhamd
Я удалил свой предыдущий комментарий I don't see a reason to make use of XML. , так как не мог его редактировать.
RafiAlhamd
1

Если вы поместите строки SQL в файл свойств, а затем прочитаете его, вы можете сохранить строки SQL в текстовом файле.

Это не решает проблемы типа SQL, но, по крайней мере, значительно упрощает копирование и вставку из TOAD или sqlplus.

Рябина
источник
0

Как получить конкатенацию строк, помимо длинных строк SQL в PreparedStatements (которые вы можете легко предоставить в текстовом файле и в любом случае загрузить как ресурс), которые вы разбиваете на несколько строк?

Вы не создаете строки SQL напрямую, не так ли? Это самый большой запрет в программировании. Используйте PreparedStatements и укажите данные в качестве параметров. Это значительно снижает вероятность внедрения SQL-кода.

JeeBee
источник
Но если вы не открываете веб-страницу для публики - актуальна ли SQL-инъекция?
Видар
4
SQL-инъекция всегда актуальна, потому что это может произойти как случайно, так и намеренно.
sleske
1
@Vidar - Вы не могли бы быть обнажая веб - страницы для общественности в настоящее время , но даже код , который будет «всегда» быть внутренним часто заканчивается получение какого - то внешнего воздействия какой - то момент далее вниз по линии. И сделать это правильно с первого раза быстрее и безопаснее, чем потом проверять всю кодовую базу на предмет проблем ...
Анджей Дойл,
4
Даже PreparedStatement нужно создавать из String, не так ли?
Стюарт,
Да, но создавать PreparedStatement из String безопасно, если вы создаете безопасный PreparedStatement. Вероятно, вам следует написать класс PreparedStatementBuilder для их генерации, чтобы скрыть беспорядок конкатенации вещей.
JeeBee