Код SQL внутри классов Java - PullRequest
11 голосов
/ 07 мая 2009

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

В настоящее время наши запросы хранятся в виде строковых констант внутри самих классов обслуживания и передаются в утилиту, которая будет выполняться SimpleJdbcTemplate. Мы находимся в тупике, где читаемость должна быть сбалансирована с ремонтопригодностью. Код SQL внутри самого класса более удобен в обслуживании, поскольку он находится в коде, который его использует. С другой стороны, если мы храним эти запросы во внешнем файле (плоский или 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)

Ответы [ 16 ]

8 голосов
/ 07 мая 2009

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

5 голосов
/ 07 мая 2009

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

2 голосов
/ 07 мая 2009

Довольно радикальным решением было бы использование 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.

1 голос
/ 07 мая 2009

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

0 голосов
/ 28 сентября 2009

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

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

0 голосов
/ 14 мая 2009

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

НТН Том

0 голосов
/ 09 мая 2009

У меня есть небольшая программа, которая обращается к буферу обмена и экранирует / выводит текст с помощью строковых литералов 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*;$" ) ) {
            return    s.replaceAll("\\\\n\\\"\\s*[+]\n\\s*\\\"","\n")
                       .replaceAll("^\\s*\\\"","")
                       .replaceAll("\\\"\\s*;$","");
        }else{
            return     s.replaceAll("\n","\\\\n\\\" +\n \\\" ")
                        .replaceAll("^"," \\\"")
                        .replaceAll("$"," \\\";");
        }
    }
}
0 голосов
/ 07 мая 2009

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

0 голосов
/ 07 мая 2009

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

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

0 голосов
/ 07 мая 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...