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

86

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

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

Будет ли Groovy быть хорошим?

Теги:
string

14 ответов

66
Лучший ответ

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

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

Другое, что можно сделать, это сохранить все запросы в файле свойств. Например в файле queries.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
  • 1
    Могу ли я предложить вам поместить InputStream в оператор if (props == null) чтобы не создавать его, когда он не нужен.
54

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

  • 0
    не будет ли это во многих случаях плохим решением, так как вы не можете позволить dbms предварительно проанализировать оператор с различными значениями для «5», «8» и т. д.? Я думаю, что выполнение с Jooq решит это?
  • 0
    @Vegard: у вас есть полный контроль над тем, как jOOQ должен отображать значения связывания в своем выводе SQL: jooq.org/doc/3.1/manual/sql-building/bind-values . Другими словами, вы можете выбрать, следует ли отображать "?" или стоит ли встраивать значения привязки.
Показать ещё 6 комментариев
13

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

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

  • 0
    Это устарело сейчас, согласно википедии.
  • 1
    На момент написания статьи (январь 2016 г.) SQLJ упоминается в Википедии как «устаревший» без каких-либо ссылок. Был ли он официально заброшен? Если это так, я вставлю предупреждение в верхней части этого ответа.
Показать ещё 1 комментарий
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.

  • 0
    Как я могу получить реально выполненный SQL-запрос? Я хочу войти.
5

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

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

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

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

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

4

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

3

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

  • 2
    посмотрите на jooq ( jooq.org ) тоже
2

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

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

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

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

Maintaince

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

Конверсия

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

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

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

2

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

1

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

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

1

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

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

0

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

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

  • 0
    Но если вы не публикуете веб-страницу для публики - является ли SQL-инъекция актуальной проблемой?
  • 3
    SQL-инъекция всегда актуальна, потому что это может произойти как случайно, так и по намерению.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню