Najczystszy sposób tworzenia łańcucha SQL w Javie

107

Chcę zbudować ciąg SQL, aby wykonywać operacje na bazie danych (aktualizacje, usuwanie, wstawianie, wybieranie, tego typu rzeczy) - zamiast okropnej metody łączenia ciągów używającej milionów znaków „+” i cudzysłowów, która jest w najlepszym przypadku nieczytelna - tam musi być lepszy sposób.

Myślałem o użyciu MessageFormat - ale ma być używany do wiadomości użytkownika, chociaż myślę, że wykonałby rozsądną robotę - ale myślę, że powinno być coś bardziej dostosowanego do operacji typu SQL w bibliotekach java sql.

Czy Groovy byłby dobry?

Vidar
źródło

Odpowiedzi:

76

Przede wszystkim rozważ użycie parametrów zapytania w przygotowywanych zestawieniach:

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

Inną rzeczą, którą można zrobić, jest przechowywanie wszystkich zapytań w pliku właściwości. Na przykład w pliku queries.properties można umieścić powyższe zapytanie:

update_query=UPDATE user_table SET name=? WHERE id=?

Następnie za pomocą prostej klasy użytkowej:

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

}

możesz użyć swoich zapytań w następujący sposób:

PreparedStatement stm = c.prepareStatement(Queries.getQuery("update_query"));

To dość proste rozwiązanie, ale działa dobrze.

Piotr Kochański
źródło
1
Wolę używać czystego konstruktora SQL, takiego jak ten: mentabean.soliveirajr.com
TraderJoeChicago
2
Mogę zasugerować, abyś umieścił InputStreamwnętrze if (props == null)instrukcji, aby nie tworzyć jej wystąpienia, gdy nie jest potrzebne.
SyntaxRules
64

W przypadku dowolnego kodu SQL użyj jOOQ . jOOQ obsługuje obecnie SELECT, INSERT, UPDATE, DELETE, TRUNCATE, i MERGE. Możesz utworzyć SQL w ten sposób:

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

Zamiast uzyskiwać ciąg SQL, możesz po prostu wykonać go za pomocą jOOQ. Widzieć

http://www.jooq.org

(Zastrzeżenie: pracuję dla firmy stojącej za jOOQ)

Lukas Eder
źródło
czy nie byłoby to w wielu przypadkach złym rozwiązaniem, ponieważ nie można pozwolić dbms wcześniej przeanalizować instrukcji z różnymi wartościami dla „5”, „8” itd.? Myślę, że wykonanie z jooq rozwiązałoby to?
Vegard,
@Vegard: Masz pełną kontrolę nad tym, jak jOOQ powinien renderować wartości powiązań w swoich danych wyjściowych SQL: jooq.org/doc/3.1/manual/sql-building/bind-values . Innymi słowy, możesz wybrać, czy chcesz renderować, "?"czy też łączyć wartości wbudowane.
Lukas Eder
tak, ale jeśli chodzi o czyste sposoby budowania sql, byłby to trochę niechlujny kod w moich oczach, jeśli nie używasz JOOQ do wykonania. w tym przykładzie ustawiasz A na 1, B na 2 itd., ale musisz to zrobić jeszcze raz, gdy wykonujesz, jeśli nie wykonujesz z JOOQ.
Vegard
1
@Vegard: Nic nie stoi na przeszkodzie, aby przekazać zmienną do interfejsu API jOOQ i odbudować instrukcję SQL. Możesz także wyodrębnić wartości powiązań w ich kolejności za pomocą jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() lub nazwać wartości powiązań według ich nazw, używając jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Moja odpowiedź zawiera tylko bardzo uproszczony przykład ... Nie jestem jednak pewien, czy to odpowiedź na Twoje obawy?
Lukas Eder,
2
To kosztowne rozwiązanie.
Sorter
15

Jedną z technologii, którą należy wziąć pod uwagę, jest SQLJ - sposób na osadzenie instrukcji SQL bezpośrednio w Javie. Jako prosty przykład możesz mieć następujący plik w pliku o nazwie TestQueries.sqlj:

public class TestQueries
{
    public String getUsername(int id)
    {
        String username;
        #sql
        {
            select username into :username
            from users
            where pkey = :id
        };
        return username;
    }
}

Istnieje dodatkowy krok prekompilacji, który pobiera pliki .sqlj i tłumaczy je na czystą Javę - w skrócie, szuka specjalnych bloków rozdzielonych

#sql
{
    ...
}

i zamienia je w wywołania JDBC. Korzystanie z SQLJ ma kilka kluczowych zalet:

  • całkowicie abstrahuje warstwę JDBC - programiści muszą myśleć tylko o Javie i SQL
  • tłumacz może być zmuszony do sprawdzania zapytań pod kątem składni itp. w bazie danych w czasie kompilacji
  • możliwość bezpośredniego wiązania zmiennych Java w zapytaniach za pomocą przedrostka „:”

Istnieją implementacje translatora dla większości głównych dostawców baz danych, więc powinieneś być w stanie łatwo znaleźć wszystko, czego potrzebujesz.

Ashley Mercer
źródło
Ten jest teraz nieaktualny, jak na wikipedii.
Zeus
1
W chwili pisania tego tekstu (styczeń 2016 r.) SQLJ jest określany na Wikipedii jako „przestarzały” bez żadnych odniesień. Czy został oficjalnie porzucony? Jeśli tak, umieszczę ostrzeżenie u góry tej odpowiedzi.
Ashley Mercer
Uwaga: technologia jest nadal obsługiwana, na przykład w najnowszej wersji Oracle, 12c . Przyznaję, że nie jest to najnowocześniejszy standard, ale nadal działa i ma pewne zalety (takie jak weryfikacja zapytań w czasie kompilacji względem bazy danych), które nie są dostępne w innych systemach.
Ashley Mercer
12

Zastanawiam się, czy szukasz czegoś takiego jak Squiggle . Bardzo przydatnym jest również jDBI . Nie pomoże ci to jednak w zapytaniach.

tcurdt
źródło
9

Chciałbym rzucić okiem na Spring JDBC . Używam go zawsze, gdy potrzebuję programowo wykonywać SQL. Przykład:

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

Jest naprawdę świetny do wszelkiego rodzaju wykonywania sql, szczególnie do zapytań; pomoże ci odwzorować zestawy wyników na obiekty, bez dodawania złożoności pełnego ORM.

Bent André Solheim
źródło
jak mogę uzyskać prawdziwe wykonane zapytanie sql? Chcę to zarejestrować.
kodmanyagha
5

Zwykle używam nazwanych parametrów JDBC Springa, więc mogę napisać standardowy ciąg, taki jak "wybierz * z bla, gdzie colX = ': someValue'"; Myślę, że to całkiem czytelne.

Alternatywą byłoby dostarczenie ciągu znaków w oddzielnym pliku .sql i odczytanie zawartości za pomocą metody narzędzia.

Och, warto też zajrzeć na Squill: https://squill.dev.java.net/docs/tutorial.html

GaryF
źródło
Zakładam, że masz na myśli, że używasz BeanPropertySqlParameterSource? Prawie się z tobą zgadzam, klasa, o której właśnie wspomniałem, jest fajna, gdy używam ściśle fasoli, ale w przeciwnym razie polecam użycie niestandardowego ParameterizedRowMapper do konstruowania obiektów.
Esko
Nie do końca. Można użyć dowolnego źródła SqlParameterSource z nazwanymi parametrami JDBC. Odpowiadało moim potrzebom użycie MapSqlParameterSource zamiast odmiany fasoli. Tak czy inaczej, to dobre rozwiązanie. RowMappers zajmują się jednak drugą stroną zagadki SQL: przekształcaniem zestawów wyników w obiekty.
GaryF,
4

Popieram zalecenia dotyczące używania ORM, takiego jak Hibernate. Jednak z pewnością są sytuacje, w których to nie działa, więc skorzystam z okazji, aby pochwalić kilka rzeczy, które pomogłem napisać: SqlBuilder to biblioteka Java do dynamicznego budowania instrukcji sql przy użyciu stylu „builder”. jest dość potężny i dość elastyczny.

James
źródło
4

Pracowałem nad aplikacją serwletową Java, która musi konstruować bardzo dynamiczne instrukcje SQL do celów raportowania adhoc. Podstawową funkcją aplikacji jest dostarczenie zestawu nazwanych parametrów żądania HTTP do wstępnie zakodowanego zapytania i wygenerowanie ładnie sformatowanej tabeli danych wyjściowych. Użyłem Spring MVC i struktury iniekcji zależności, aby przechowywać wszystkie moje zapytania SQL w plikach XML i ładować je do aplikacji raportującej wraz z informacjami o formatowaniu tabeli. Ostatecznie wymagania dotyczące raportowania stały się bardziej skomplikowane niż możliwości istniejących ram mapowania parametrów i musiałem napisać własne. Było to interesujące ćwiczenie w rozwoju i stworzyło ramy do mapowania parametrów o wiele bardziej solidne niż cokolwiek innego, co mogłem znaleźć.

Nowe mapowania parametrów wyglądały następująco:

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

Piękno powstałej struktury polegało na tym, że mógł przetwarzać parametry żądania HTTP bezpośrednio w zapytaniu z odpowiednim sprawdzaniem typu i sprawdzaniem limitów. Do weryfikacji danych wejściowych nie są wymagane żadne dodatkowe mapowania. W powyższym przykładowym zapytaniu parametr o nazwie serverId zostanie sprawdzony, aby upewnić się, że może rzutować na liczbę całkowitą i mieści się w zakresie od 0 do 50. Parametr appId byłby przetwarzany jako tablica liczb całkowitych z ograniczeniem długości do 50. Jeśli pole showOwnerjest obecny i ustawiony na „true”, bity SQL w cudzysłowach zostaną dodane do wygenerowanego zapytania dla opcjonalnych mapowań pól. field Dostępnych jest kilka innych mapowań typów parametrów, w tym opcjonalne segmenty SQL z dodatkowymi mapowaniami parametrów. Pozwala na tak złożone mapowanie zapytań, jak tylko może wymyślić programista. Ma nawet kontrolki w konfiguracji raportu, które pozwalają określić, czy dane zapytanie będzie miało ostateczne mapowania za pośrednictwem PreparedStatement, czy po prostu uruchomione jako wstępnie zbudowane zapytanie.

Przykładowe wartości żądania HTTP:

showOwner: true
serverId: 20
appId: 1,2,3,5,7,11,13

Spowoduje to wygenerowanie następującego kodu 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

Naprawdę uważam, że Spring lub Hibernate lub jeden z tych frameworków powinien oferować bardziej niezawodny mechanizm mapowania, który weryfikuje typy, pozwala na złożone typy danych, takie jak tablice i inne tego typu funkcje. Napisałem swój silnik tylko do moich celów, nie jest on do końca czytany do ogólnego wydania. Obecnie działa tylko z zapytaniami Oracle, a cały kod należy do dużej korporacji. Któregoś dnia mogę wziąć swoje pomysły i zbudować nowy framework open source, ale mam nadzieję, że jeden z istniejących dużych graczy podejmie wyzwanie.

Natalia
źródło
3

Dlaczego chcesz ręcznie generować wszystkie pliki sql? Czy spojrzałeś na ORM, taki jak Hibernacja? Zapytania SQL, które muszą być ręcznie dostrajane.

Jared
źródło
3

Można też spojrzeć na MyBatis ( www.mybatis.org ). Pomaga w pisaniu instrukcji SQL poza kodem Java i mapuje wyniki sql między innymi na obiekty Java.

joshua
źródło
3

Google udostępnia bibliotekę zwaną Room Persitence Library, która zapewnia bardzo czysty sposób pisania SQL dla aplikacji na Androida , w zasadzie warstwę abstrakcji nad bazową bazą danych SQLite . Poniżej znajduje się krótki fragment kodu z oficjalnej strony internetowej:

@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);
}

W oficjalnych dokumentach biblioteki jest więcej przykładów i lepsza dokumentacja.

Istnieje również jeden o nazwie MentaBean, który jest Java ORM . Ma fajne funkcje i wydaje się być całkiem prostym sposobem pisania SQL.

CasualCoder3
źródło
Zgodnie z dokumentacją pokoju : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Nie jest to więc ogólna biblioteka ORM dla RDBMS. Jest przeznaczony przede wszystkim dla aplikacji na Androida.
RafiAlhamd
2

Przeczytaj plik XML.

Możesz go przeczytać z pliku XML. Jest łatwy w utrzymaniu i pracy. Dostępne są standardowe parsery STaX, DOM, SAX, które tworzą kilka linii kodu w java.

Zrób więcej dzięki atrybutom

Możesz mieć pewne informacje semantyczne z atrybutami na znaczniku, aby pomóc zrobić więcej z SQL. Może to być nazwa metody, typ zapytania lub cokolwiek, co pomaga w mniejszym kodowaniu.

Utrzymaj

Możesz umieścić xml poza słoikiem i łatwo go konserwować. Te same korzyści, co w przypadku pliku właściwości.

Konwersja

XML można rozszerzać i łatwo konwertować na inne formaty.

Przypadek użycia

Metamug używa XML do konfigurowania plików zasobów REST za pomocą sql.

Sorter
źródło
Jeśli chcesz, możesz użyć yaml lub json. Są lepsze niż przechowywanie w zwykłym pliku właściwości
Sorter
Pytanie brzmi, jak ZBUDOWAĆ SQL. Aby zbudować SQL, jeśli potrzebujesz użyć XML, Parser, Validation itp., Jest to przeciążenie. Większość wczesnych prób, które wykorzystywały XML do zbudowania SQL, została odrzucona na rzecz Annotation. Akceptowane odpowiedź przez Piotr Kochański jest prosty i elegancki , a do punktu - rozwiązuje problem i utrzymaniu. UWAGA: Nie ma innego sposobu na utrzymanie lepszego SQL w innym języku.
RafiAlhamd
Usunąłem swój poprzedni komentarz I don't see a reason to make use of XML. , ponieważ nie mogłem go edytować.
RafiAlhamd
1

Jeśli umieścisz ciągi SQL w pliku właściwości, a następnie przeczytasz, możesz zachować ciągi SQL w zwykłym pliku tekstowym.

To nie rozwiązuje problemów związanych z typami SQL, ale przynajmniej znacznie ułatwia kopiowanie i wklejanie z TOAD lub sqlplus.

Jarzębina
źródło
0

Jak uzyskać konkatenację ciągów, poza długimi ciągami SQL w PreparedStatements (które i tak można łatwo podać w pliku tekstowym i załadować jako zasób), które można podzielić na kilka wierszy?

Nie tworzysz bezpośrednio łańcuchów SQL, prawda? To największe nie-nie w programowaniu. Skorzystaj z PreparedStatements i podaj dane jako parametry. Znacznie zmniejsza ryzyko wstrzyknięcia kodu SQL.

JeeBee
źródło
Ale jeśli nie udostępniasz publicznie strony internetowej - czy SQL Injection jest istotnym problemem?
Vidar
4
SQL Injection jest zawsze istotny, ponieważ może się zdarzyć zarówno przypadkowo, jak i celowo.
sleske
1
@Vidar - nie może być wystawienie stronę internetową publicznie teraz , ale nawet kod, który będzie „zawsze” być wewnętrzne często kończy się pewnego rodzaju narażenia zewnętrznego jakiś punkt dalej w dół. I szybsze i bezpieczniejsze jest zrobienie tego dobrze za pierwszym razem niż późniejsze sprawdzanie całej bazy kodu pod kątem problemów ...
Andrzej Doyle
4
Nawet PreparedStatement musi zostać utworzony z String, prawda?
Stewart
Tak, ale można bezpiecznie zbudować PreparedStatement na podstawie String, o ile utworzysz bezpieczny PreparedStatement. Prawdopodobnie powinieneś napisać klasę PreparedStatementBuilder, aby je wygenerować, aby ukryć bałagan związany z łączeniem rzeczy.
JeeBee