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

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

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

Будет ли Groovy хорош?


person Vidar    schedule 16.12.2008    source источник


Ответы (14)


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

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

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

person Piotr Kochański    schedule 16.12.2008
comment
Я предпочитаю использовать чистый конструктор SQL, такой как этот: mentabean.soliveirajr.com - person TraderJoeChicago; 08.09.2011
comment
Могу ли я предложить вам поместить InputStream внутрь оператора if (props == null), чтобы вы не создавали его экземпляр, когда он не нужен. - person SyntaxRules; 22.08.2013
comment
Что такое c в первом и последнем фрагменте кода? - person AMTerp; 27.05.2021

Для произвольного 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)

person Lukas Eder    schedule 09.08.2011
comment
не будет ли это во многих случаях плохим решением, поскольку вы не можете позволить СУБД заранее проанализировать оператор с разными значениями для 5, 8 и т. д.? Я думаю, выполнение с jooq решит это? - person Vegard; 16.09.2013
comment
@Vegard: у вас есть полный контроль над тем, как jOOQ должен отображать значения привязки в выводе SQL: jooq.org/doc/3.1/manual/sql-building/bind-values. Другими словами, вы можете выбрать, отображать ли "?" или встраивать значения привязки. - person Lukas Eder; 16.09.2013
comment
да, но что касается чистых способов сборки sql, это будет немного беспорядочный код, на мой взгляд, если вы не используете JOOQ для выполнения. в этом примере вы устанавливаете A на 1, B на 2 и т. д., но вам нужно сделать это еще раз, когда вы выполняете, если вы не выполняете с JOOQ. - person Vegard; 16.09.2013
comment
@Vegard: ничто не мешает вам передать переменную в API jOOQ и перестроить оператор SQL. Кроме того, вы можете извлечь значения привязки в их порядке, используя jooq .org/javadoc/latest/org/jooq/Query.html#getBindValues() или называйте значения привязки по их именам, используя jooq.org/javadoc/latest/org/jooq/Query.html#getParams(). Мой ответ просто содержит очень упрощенный пример... Однако я не уверен, отвечает ли это вашим опасениям? - person Lukas Eder; 16.09.2013
comment
у вас есть очень хорошая мысль, использование этих методов должно привести к более чистому коду, чем то, о чем я изначально думал. Что мне также нравится в jOOQ, так это возможность писать SQL-запросы с некоторой безопасностью типов. - person Vegard; 16.09.2013
comment
Это дорогостоящее решение. - person Sorter; 13.01.2015
comment
Не забудьте добавить заявление об отказе от ответственности, в котором говорится, что вы являетесь генеральным директором компании, стоящей за jOOQ. ;) - person Stephan; 08.05.2016
comment
@Stephan: Действительно, спасибо за подсказку. Я задним числом добавил этот отказ от ответственности к большинству ответов, но все еще есть 1-2, которые еще не были изменены. - person Lukas Eder; 08.05.2016
comment
@ChitrangSharma: Пожалуйста, задайте новый вопрос - person Lukas Eder; 12.07.2021

Одной из технологий, которую вы должны рассмотреть, является 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 в запросах с использованием префикса ":"

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

person Ashley Mercer    schedule 16.12.2008

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

person tcurdt    schedule 16.12.2008

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

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

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

person Bent André Solheim    schedule 16.12.2008
comment
как я могу получить реальный выполненный sql-запрос? Я хочу зарегистрировать это. - person kodmanyagha; 02.12.2017

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

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

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

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

Я работаю над приложением сервлета 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 присутствует и имеет значение "true", биты 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, и весь код принадлежит крупной корпорации. Когда-нибудь я возьму свои идеи и создам новую среду с открытым исходным кодом, но я надеюсь, что один из существующих крупных игроков примет вызов.

person Natalia    schedule 02.02.2009

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

person james    schedule 16.12.2008

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

person Jared    schedule 16.12.2008

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

person joshua    schedule 27.03.2012

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

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

person CasualCoder3    schedule 08.10.2017
comment
Согласно документации комнаты: Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Таким образом, это не универсальная библиотека ORM для СУБД. Это в первую очередь предназначено для приложений Android. - person RafiAlhamd; 09.02.2020

Чтение XML-файла.

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

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

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

Обслуживание

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

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

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

Вариант использования

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

person Sorter    schedule 27.04.2017
comment
Вы можете использовать yaml или json, если они вам нравятся. Они лучше, чем хранить в обычном файле свойств - person Sorter; 09.02.2020
comment
Вопрос в том, как ПОСТРОИТЬ SQL. Чтобы построить SQL, если вам нужно использовать XML, Parser, Validation и т. д., это перегружено. Большинство ранних попыток использовать XML для построения SQL были отвергнуты в пользу аннотаций. принятый ответ от Piotr Kochański прост и элегантен и по существу - решает проблему и удобен в сопровождении. ПРИМЕЧАНИЕ. НЕТ альтернативного способа поддерживать более качественный SQL на другом языке. - person RafiAlhamd; 09.02.2020
comment
Я удалил свой предыдущий комментарий I don't see a reason to make use of XML. , так как не мог его отредактировать. - person RafiAlhamd; 09.02.2020

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

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

person Rowan    schedule 16.12.2008

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

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

person JeeBee    schedule 16.12.2008
comment
Но если вы не выставляете веб-страницу на всеобщее обозрение, является ли SQL-инъекция актуальной проблемой? - person Vidar; 16.12.2008
comment
SQL-инъекция всегда актуальна, потому что она может произойти как случайно, так и намеренно. - person sleske; 31.03.2009
comment
@Vidar - возможно, вы не раскрываете веб-страницу публике сейчас, но даже код, который всегда будет внутренним, часто в какой-то момент в конечном итоге получает какое-то внешнее воздействие. И быстрее и безопаснее сделать это правильно с первого раза, чем потом проверять всю кодовую базу на наличие проблем... - person Andrzej Doyle; 26.08.2009
comment
Даже PreparedStatement нужно создать из строки, не так ли? - person Stewart; 24.04.2012
comment
Да, но безопасно создавать PreparedStatement из строки, пока вы создаете безопасный PreparedStatement. Вероятно, вам следует написать класс PreparedStatementBuilder для их генерации, чтобы скрыть беспорядок объединения вещей. - person JeeBee; 15.10.2013
comment
Эта статья было бы полезно понять, как справиться с проблемой «SQL Injection». - person RafiAlhamd; 09.02.2020