Самый чистый способ построить строку 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 . Однако это не поможет вам с вопросами.


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


Я полагаю, вы имеете в виду, что используете 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.


В соответствии с документацией номер : 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-кода.


Но если вы не открываете веб-страницу для публики - актуальна ли SQL-инъекция?
— Видар

4
SQL-инъекция всегда актуальна, потому что это может произойти как случайно, так и намеренно.
— sleske

1
@Vidar - Вы не могли бы быть обнажая веб - страницы для общественности в настоящее время , но даже код , который будет «всегда» быть внутренним часто заканчивается получение какого - то внешнего воздействия какой - то момент далее вниз по линии. И сделать это правильно с первого раза быстрее и безопаснее, чем потом проверять всю кодовую базу на предмет проблем ...
— Анджей Дойл,

4
Даже PreparedStatement нужно создавать из String, не так ли?
— Стюарт,

Да, но создавать PreparedStatement из String безопасно, если вы создаете безопасный PreparedStatement. Вероятно, вам следует написать класс PreparedStatementBuilder для их генерации, чтобы скрыть беспорядок конкатенации вещей.
— JeeBee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.