Вопрос

Наш текущий проект не использует Hibernate (по разным причинам), и мы используем поддержку SimpleJdbc от Spring для выполнения всех наших операций с базой данных.У нас есть служебный класс, который абстрагирует все операции CRUD, но сложные операции выполняются с использованием пользовательских SQL-запросов.

В настоящее время наши запросы хранятся в виде строковых констант внутри самих сервисных классов и передаются в утилиту, которая выполняется SimpleJdbcTemplate.Мы зашли в тупик, где удобочитаемость должна быть сбалансирована с ремонтопригодностью.SQL-код внутри самого класса более удобен в обслуживании, поскольку он находится вместе с кодом, который его использует.С другой стороны, если мы сохраним эти запросы во внешнем файле (flat или XML), то сам SQL будет более читабельным по сравнению с экранированным строковым синтаксисом java.

Кто-нибудь сталкивался с подобной проблемой?Что такое хороший баланс?Где вы храните свой пользовательский SQL в своем проекте?

Пример запроса выглядит следующим образом:

private static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
"    FROM PRODUCT_SKU T \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.ID) as minimum_id_for_price \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID, S.SALE_PRICE \n" +
"    ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.SALE_PRICE) as minimum_price_for_product \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID \n" +
"    ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) \n" +
"WHERE T.PRODUCT_ID IN (:productIds)";

Вот как это выглядело бы в плоском SQL-файле:

--namedQuery: FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS
FROM PRODUCT_SKU T 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.ID) as minimum_id_for_price 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.SALE_PRICE) as minimum_price_for_product 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID 
) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
WHERE T.PRODUCT_ID IN (:productIds)
Это было полезно?

Решение

Я сохранил SQL как строки в классе Java, так и в виде отдельных файлов, которые были загружены во время выполнения. Я очень предпочел последнее по двум причинам. Во-первых, код более читабелен с большим отрывом. Во-вторых, проще тестировать SQL отдельно, если вы храните его в отдельном файле. Кроме того, в SQL было проще найти кого-то лучше меня, чтобы он помог мне с моими запросами, когда они были в отдельных файлах.

Другие советы

Я тоже столкнулся с этим, в настоящее время по той же причине - проект, основанный на Spring JDBC. По моему опыту, хотя не очень хорошо иметь логику в самом sql, на самом деле нет лучшего места для нее, и вставка кода приложения медленнее, чем это делает db, и не обязательно более понятна.

Самые большие подводные камни, с которыми я столкнулся, это то, что sql начинает распространяться по всему проекту, с многочисленными вариациями. " Получить A, B, C из FOO " ;. "Получить A, B, C, E от Foo" и т. д. Такое распространение особенно вероятно, так как проект достигает определенной критической массы - это может показаться не проблемой с 10 запросами, но, когда в проекте разбросано 500 запросов, выяснить, что вы уже сделали, становится намного сложнее. , Абстрагирование от основных операций CRUD ставит вас далеко впереди игры.

Лучшее решение, AFAIK, должно строго соответствовать кодированному SQL - прокомментировано, протестировано и в согласованном месте. В нашем проекте есть 50 строк без комментариев. Что они имеют в виду? Кто знает?

Что касается запросов во внешних файлах, я не вижу, что это покупает - вы все еще так же полагаетесь на SQL, и за исключением (сомнительного) эстетического улучшения удержания sql вне классов, ваши классы по-прежнему так же зависят от sql -.eg, вы обычно разделяете ресурсы, чтобы получить гибкость для добавления замещающих ресурсов, но вы не можете подключить замещающие SQL-запросы, так как это изменит семантическое значение класса или не работает вообще. Так что это иллюзорная чистота кода.

Довольно радикальным решением было бы использование Groovy для определения ваших запросов. Groovy имеет поддержку на уровне языка для многострочных строк и интерполяции строк (забавно известный как GStrings).

Например, используя Groovy, указанный выше запрос будет просто:

class Queries
    private static final String PRODUCT_IDS_PARAM = ":productIds"

    public static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
    """    FROM PRODUCT_SKU T 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.ID) as minimum_id_for_price 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
        ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.SALE_PRICE) as minimum_price_for_product 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID 
        ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
    WHERE T.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) """

Вы можете получить доступ к этому классу из кода Java, так же, как если бы он был определен в Java, например.

String query = QueryFactory.FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS;

Я признаю, что добавление Groovy в ваш путь к классам только для того, чтобы ваши SQL-запросы выглядели лучше, является чем-то вроде «кувалды, чтобы взломать гайку». решение, но если вы используете Spring, есть большая вероятность, что вы уже Groovy на вашем пути к классам.

Кроме того, в вашем проекте, вероятно, есть много других мест, где вы можете использовать Groovy (вместо Java) для улучшения своего кода (особенно теперь, когда Groovy принадлежит Spring). Примеры включают написание тестовых случаев или замену Java-компонентов на Groovy.

Мы используем хранимые процедуры. Это хорошо для нас, потому что мы используем Oracle Fine Grain Access. Это позволяет нам запретить пользователю просматривать определенный отчет или результаты поиска, ограничивая его доступ к соответствующей процедуре. Это также дает нам небольшой прирост производительности.

Почему бы не использовать хранимые процедуры вместо запросов жесткого кодирования? Хранимые Procs повысят удобство сопровождения и обеспечат большую безопасность для таких вещей, как атаки Interjection SQL.

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

Один из вариантов - использовать iBatis . Он довольно легкий по сравнению с полнофункциональным ORM, таким как Hibernate, но предоставляет средства для хранения ваших запросов SQL вне ваших файлов .java

Мы храним весь наш SQL в классе как набор статических финальных строк. Для удобства чтения мы разбили его на несколько строк, объединенных с помощью +. Кроме того, я не уверен, что вам нужно что-либо скрывать - " Строки " заключены в одинарные кавычки в sql.

У нас был проект, в котором мы использовали именно тот подход, который вы используете, за исключением того, что мы выводили каждый запрос в отдельный текстовый файл. Каждый файл считывался (один раз) с использованием среды ResourceLoader Spring, и приложение работало через такой интерфейс:

public interface SqlResourceLoader {
    String loadSql(String resourcePath);
}

Несомненным преимуществом этого было то, что наличие SQL в неэкранированном формате позволяло упростить отладку - просто считайте файл в инструмент запроса. Если у вас есть более чем несколько запросов средней сложности, то есть тестирование и удаление кода из кода во время тестирования & amp; отладка (особенно для тюнинга) была неоценима.

Мы также должны были поддерживать несколько разных баз данных, чтобы было проще менять платформу.

Исходя из того, что вы объяснили, здесь нет реального выбора, кроме как сохранить его в коде и избавиться от символов / n повсюду. Это единственное, что вы упомянули как влияющие на читабельность, и они абсолютно не нужны.

Если в коде нет других проблем, ваша проблема легко решается.

Я бы предположил, что сохранение запроса во внешнем файле, а затем предоставление приложению возможности прочитать его при необходимости, создает ОГРОМНУЮ брешь в безопасности.

Что произойдет, если злой умысел получит доступ к этому файлу и изменит ваш запрос?

Например, изменения

 select a from A_TABLE;

Для

 drop table A_TABLE;

или

 update T_ACCOUNT set amount = 1000000

Кроме того, это усложняет необходимость выполнения двух задач:Java-код и файлы SQL-запросов.

Редактировать:Да, вы можете изменять свои запросы без перекомпиляции приложения.Я не вижу в этом ничего особенного.Вы могли бы перекомпилировать классы, которые содержат / создают только sqlQueries, если проект слишком большой.Кроме того, если документация некачественная, возможно, вы в конечном итоге измените неправильный файл, и это превратится в огромную молчащую ошибку.Никаких исключений или кодов ошибок выдано не будет, и когда вы поймете, что натворили, может быть слишком поздно.

Другим подходом было бы иметь какой-то SQLQueryFactory, хорошо документированный, и с методами, которые возвращают SQL-запрос, который вы хотите использовать.

Например

public String findCheapest (String tableName){

      //returns query.
}

Здесь вам нужен SQLJ , который является Java-препроцессором SQL. К сожалению, по-видимому, это никогда не взлетело, хотя я видел некоторые IBM и реализации Oracle . Но они довольно устарели.

Если бы я был вами и имел множество запросов в системе, я бы сохранял их в отдельном файле и загружал их в время выполнения .

Исходя из моего опыта, лучше оставить операторы SQL в коде, не разделяя их, чтобы сделать вещи более понятными (например, аннотации или файлы конфигурации), но теперь у меня есть спор об этом с одним из членов команды.

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

У меня есть ярлык для быстрого запуска " панель инструментов, поэтому единственное, что мне нужно сделать, это

Ctrl+C, Click jar, Ctrl+V

Либо, когда я хочу запустить мой " код " в инструмент SQL или наоборот.

У меня обычно есть что-то вроде этого:

String query = 
    "SELECT a.fieldOne, b.fieldTwo \n"+
    "FROM  TABLE_A a, TABLE b \n"+ 
    "... etc. etc. etc";


logger.info("Executing  " + query  );

PreparedStatement pstmt = connection.prepareStatement( query );
....etc.

Который превращается в:

    SELECT a.fieldOne, b.fieldTwo 
    FROM  TABLE_A a, TABLE b
    ... etc. etc. etc

Либо потому, что в некоторых проектах я не могу создавать отдельные файлы, либо потому что я параноик и чувствую, что некоторые биты будут вставлены / удалены при чтении из внешнего файла (обычно это невидимый \ n, который делает

select a,b,c 
from 

в

select a,b,cfrom 

Идея IntelliJ делает то же самое для вас автоматически, но просто от простого к коду.

Вот старая версия, которую я восстановил. Его немного сломано и не справляется?

Дайте мне знать, если кто-то улучшит это.

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Transforms a plain string from the clipboard into a Java 
 * String literal and viceversa.
 * @author <a href="http://stackoverflow.com/users/20654/oscar-reyes">Oscar Reyes</a>
 */
public class ClipUtil{

    public static void main( String [] args ) 
                                throws UnsupportedFlavorException,
                                                     IOException {

        // Get clipboard
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Clipboard clipboard = toolkit.getSystemClipboard();

        // get current content.
        Transferable transferable = clipboard.getContents( new Object() ); 
        String s = ( String ) transferable.getTransferData( 
                                                DataFlavor.stringFlavor );

        // process the content
        String result = process( s );

        // set the result
        StringSelection ss = new StringSelection( result );
        clipboard.setContents( ss, ss );

    }
    /**
     * Transforms the given string into a Java string literal 
     * if it represents plain text and viceversa. 
     */
    private static String process( String  s ){
        if( s.matches( "(?s)^\\s*\\\".*\\\"\\s*;<*>quot; ) ) {
            return    s.replaceAll("\\\\n\\\"\\s*[+]\n\\s*\\\"","\n")
                       .replaceAll("^\\s*\\\"","")
                       .replaceAll("\\\"\\s*;<*>quot;,"");
        }else{
            return     s.replaceAll("\n","\\\\n\\\" +\n \\\" ")
                        .replaceAll("^"," \\\"")
                        .replaceAll("<*>quot;," \\\";");
        }
    }
}

Поскольку вы уже используете Spring, почему бы не поместить SQL в конфигурационный файл Spring и не ввести его в класс DAO?Это простой способ экстернализации строки SQL.

HTH Том

Я предпочитаю внешний вариант. Я поддерживаю несколько проектов, и мне гораздо сложнее поддерживать внутренний SQL, потому что вам приходится компилировать и повторно развертывать каждый раз, когда вы хотите внести небольшие изменения в SQL. Наличие SQL во внешнем файле позволяет легко вносить большие и малые изменения с меньшим риском. Если вы просто редактируете SQL, у вас нет шансов вставить опечатку, которая сломает класс.

Для Java я использую класс Properties, который представляет SQL в файле .properties, который позволяет передавать запросы SQL, если вы хотите повторно использовать запросы, а не читать файл несколько раз.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top