Найпростіший спосіб побудувати рядок SQL в Java


107

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

Я думав використовувати MessageFormat - але його слід використовувати для повідомлень користувачів, хоча я думаю, що це зробить розумну роботу - але, мабуть, має бути щось більше узгоджене з операціями типу SQL в бібліотеках Java 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();

Інша річ, яку можна зробити, це зберегти всі запити у файлі властивостей. Наприклад, у файл 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
Я вважаю за краще використовувати чистий конструктор
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: Ви маєте повний контроль над тим, як jOOQ повинен надавати значення прив'язки у своєму SQL-виході: jooq.org/doc/3.1/manual/sql-building/bind-values . Іншими словами, ви можете вибрати, чи відображати "?"чи вбудовувати значення прив’язки.
Лукас Едер

так, але що стосується чистих способів побудови sql, це було б трохи безладним кодом у моїх очах, якщо ви не використовуєте JOOQ для виконання. у цьому прикладі ви встановлюєте A до 1, B до 2 тощо, але це потрібно зробити ще раз, коли ви виконуєте виконання, якщо ви не виконуєте JOOQ.
Вегард

1
@Vegard: Ніщо не перешкоджає передачі змінної в API jOOQ та відновлення оператора SQL. Крім того, ви можете витягнути значення прив'язки у їхньому порядку, використовуючи jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() , або вказавши значення прив’язки за їх іменами, використовуючи jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Моя відповідь просто містить дуже спрощений приклад ... Я не впевнений, чи відповідає це вашим питанням?
Лукас Едер

2
Це дороге рішення.
Сортер

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 у запитах за допомогою префікса ":"

Існує реалізація перекладача для більшості основних постачальників баз даних, тому ви зможете легко знайти все необхідне.


Зараз ця застаріла, згідно з вікіпедією.
Зевс

1
На момент написання (січня 2016 року) SQLJ у Вікіпедії називається "застарілим" без жодних посилань. Чи було офіційно відмовлено? Якщо так, я буду дотримуватися попередження у верхній частині цієї відповіді.
Ешлі Мерсер

Примітка. Ця технологія все ще підтримується, наприклад, в останній версії Oracle, 12c . Я визнаю, що це не найсучасніший стандарт, але він все ще працює і має деякі переваги (наприклад, перевірка часу збирання запитів проти БД), які відсутні в інших системах.
Ешлі Мерсер


9

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

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

Це дійсно чудово для будь-якого виконання sql, особливо запитів; це допоможе вам відобразити набори результатів для об’єктів, не додаючи складності повного ORM.


як я можу отримати реально виконаний запит sql? Я хочу це ввійти.
kodmanyagha

5

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

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

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


Я припускаю, ви маєте на увазі, що ви використовуєте BeanPropertySqlParameterSource? Я майже погоджуюся з вами, клас, який я щойно згадував, класний при використанні суворо квасолі, але в іншому випадку я б рекомендував використовувати для створення об'єктів користувальницький ParameterizedRowMapper.
Есько

Не зовсім. Ви можете використовувати будь-який SqlParameterSource з іменованими параметрами JDBC. Це підходило моїм потребам використовувати MapSqlParameterSource, а не сорт квасолі. У будь-якому випадку це хороше рішення. RowMappers, однак, має справу з іншою стороною головоломки: перетворення наборів результатів у об'єкти.
GaryF

4

Я друге рекомендації щодо використання ORM типу Hibernate. Однак, безумовно, є ситуації, коли це не працює, тому я скористаюсь цією можливістю, щоб промальовувати деякі речі, які мені допомогли написати: SqlBuilder - це бібліотека Java для динамічного складання заяв sql, використовуючи стиль "builder". вона досить потужна і досить гнучка.


4

Я працював над додатком сервлетів Java, який потребує побудови дуже динамічних операторів SQL для цілей звітування adhoc. Основна функція програми - подавати купу іменованих параметрів запиту 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, і весь код належить великій корпорації. Колись я можу взяти свої ідеї та створити нову рамку з відкритим кодом, але сподіваюся, що хтось із існуючих великих гравців вирішить цю проблему.


3

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


3

Ви також можете подивитися на MyBatis ( www.mybatis.org ). Це допомагає вам писати SQL-оператори за межами коду Java та відображати результати sql у ваші java-об’єкти.


3

Google надає бібліотеку під назвою Library Persitence Library, яка забезпечує дуже чистий спосіб написання SQL для Android Apps , в основному рівень абстракції над базовою базою даних 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, який є ORM Java . Він має приємні функції і, здається, досить простий спосіб написання SQL.


Відповідно до документацією номер : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Отже, це не загальна бібліотека ORM для RDBMS. Він в першу чергу призначений для Android Apps.
RafiAlhamd

2

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

Ви можете прочитати його з XML-файлу. Його легко обслуговувати та працювати. Існують стандартні парсери STaX, DOM, SAX, які дозволяють зробити кілька рядків коду в Java.

Робіть більше з атрибутами

Ви можете мати якусь семантичну інформацію з атрибутами на тезі, щоб допомогти зробити більше з SQL. Це може бути ім'я методу, тип запиту або все, що допомагає менше кодувати.

Обслуговування

Ви можете помістити xml за межі банку та легко підтримувати її. Такі ж переваги, як і файл властивостей.

Перетворення

XML розширюється і легко конвертується в інші формати.

Використовуйте кейс

Metamug використовує xml для налаштування REST-файлів ресурсів з sql.


Ви можете використовувати yaml або json, якщо вони вам подобаються. Вони краще, ніж зберігати у простому файлі властивостей
Сортер

Питання в тому, як BUILD SQL. Щоб створити SQL, якщо вам потрібно використовувати XML, Parser, Validation тощо, це перевантаження. Більшість ранніх спроб, що стосуються XML для побудови SQL, відхиляються на користь Анотації. Загальноприйнятий відповідь на 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 Injection актуальною проблемою?
Відар

4
Інжекція SQL завжди актуальна, тому що може статися випадково, а також за наміром.
sleske

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

4
Навіть підготовлений стан повинен бути створений з рядка, ні?
Стюарт

Так, але безпечно створити PreparedStatement з рядка, якщо ви створили безпечний PreparedStatement. Вам, мабуть, слід написати клас PreparedStatementBuilder, щоб генерувати їх, щоб приховати безладдя об'єднаних речей.
JeeBee
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.