Java'da SQL dizisi oluşturmanın en temiz yolu


107

Milyonlarca "+" ve en iyi durumda okunamayan alıntılar kullanan korkunç dizgi concat yöntemi yerine veritabanı manipülasyonu (güncellemeler, silmeler, ekler, seçmeler, bu tür şeyler) yapmak için bir SQL dizisi oluşturmak istiyorum - orada daha iyi bir yol olmalı.

MessageFormat kullanmayı düşünmüştüm - ancak makul bir iş çıkaracağını düşünmeme rağmen kullanıcı mesajları için kullanılması gerekiyordu - ama sanırım java sql kitaplıklarında SQL tipi işlemlerle daha uyumlu bir şeyler olmalı.

Groovy iyi olur mu?

Yanıtlar:


76

Öncelikle hazırlanan ifadelerde sorgu parametrelerini kullanmayı düşünün:

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

Yapılabilecek diğer bir şey, tüm sorguları özellikler dosyasında tutmaktır. Örneğin bir queries.properties dosyasına yukarıdaki sorguyu yerleştirebilirsiniz:

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

Ardından basit bir yardımcı sınıfın yardımıyla:

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

}

sorgularınızı aşağıdaki gibi kullanabilirsiniz:

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

Bu oldukça basit bir çözüm, ancak iyi çalışıyor.


1
Bunun gibi temiz bir SQL oluşturucu kullanmayı tercih ederim: mentabean.soliveirajr.com
TraderJoeChicago

2
İfadenin InputStreamiçini koymanızı, if (props == null)böylece gerekmediğinde onu somutlaştırmamanızı önerebilir miyim ?
SyntaxRules

64

Keyfi SQL için jOOQ kullanın . jOOQ şu anda destekler SELECT, INSERT, UPDATE, DELETE, TRUNCATE, ve MERGE. Bunun gibi SQL oluşturabilirsiniz:

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 dizesini elde etmek yerine, jOOQ kullanarak da çalıştırabilirsiniz. Görmek

http://www.jooq.org

(Sorumluluk reddi: jOOQ'un arkasındaki şirket için çalışıyorum)


dbms'nin ifadeyi "5", "8" vb. için farklı değerlerle önceden ayrıştırmasına izin veremeyeceğiniz için bu çoğu durumda kötü bir çözüm olmaz mı? Sanırım jooq ile çalıştırmak sorunu çözer mi?
Vegard

@Vegard: Sen nasıl üzerinde tam kontrole sahip jOOQ : kendi SQL çıktı değerleri bağlamak oluşturması gerektiğini jooq.org/doc/3.1/manual/sql-building/bind-values . Başka bir deyişle, "?"değerlerin satır içi olarak oluşturulup oluşturulmayacağını seçersiniz.
Lukas Eder

evet, ancak sql oluşturmanın temiz yolları söz konusu olduğunda, JOOQ'u yürütmek için kullanmıyorsanız, bu benim gözümde biraz karışık bir kod olabilir. bu örnekte A'yı 1'e, B'yi 2'ye vb. ayarlarsınız, ancak JOOQ ile çalışmıyorsanız, yürütürken bunu bir kez daha yapmanız gerekir.
Vegard

1
@Vegard: Hiçbir şey sizi bir değişkeni jOOQ API'ye geçirmekten alıkoyamaz ve SQL deyimini yeniden oluşturmaz. Ayrıca jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() kullanarak bağlama değerlerini veya jooq.org/javadoc/latest/org/jooq kullanarak adlarına göre adlandırılmış bağlama değerlerini ayıklayabilirsiniz. /Query.html#getParams () . Cevabım çok basit bir örnek içeriyor ... Yine de bunun endişelerinize cevap verip vermediğinden emin değilim?
Lukas Eder

2
Pahalı bir çözüm.
Sıralayıcı

15

Dikkate almanız gereken bir teknoloji, SQL deyimlerini doğrudan Java'ya yerleştirmenin bir yolu olan SQLJ'dir . Basit bir örnek olarak, TestQueries.sqlj adlı bir dosyada aşağıdakilere sahip olabilirsiniz:

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

.Sqlj dosyalarınızı alan ve onları saf Java'ya çeviren ek bir ön derleme adımı vardır - kısacası, ile ayrılmış özel blokları arar.

#sql
{
    ...
}

ve bunları JDBC çağrılarına dönüştürür. SQLJ kullanmanın birkaç temel avantajı vardır:

  • JDBC katmanını tamamen soyutlar - programcıların yalnızca Java ve SQL hakkında düşünmesi gerekir
  • çevirmen, sözdizimi vb. için sorgularınızı derleme zamanında veritabanına göre kontrol etmek için yapılabilir.
  • Java değişkenlerini sorgularda ":" önekini kullanarak doğrudan bağlama yeteneği

Çoğu büyük veritabanı satıcıları için çevirmen uygulamaları vardır, bu nedenle ihtiyacınız olan her şeyi kolayca bulabilmelisiniz.


Bu, Wikipedia'ya göre artık modası geçmiş.
Zeus,

1
Yazma sırasında (Ocak 2016) SQLJ'den Wikipedia'da herhangi bir referans olmaksızın "modası geçmiş" olarak . Resmi olarak terk mi edildi? Eğer öyleyse, bu cevabın başına bir uyarı koyacağım.
Ashley Mercer

NB Teknoloji, örneğin Oracle'ın en son sürümü olan 12c'de hala desteklenmektedir . Bunun en modern standart olmadığını kabul ediyorum, ancak yine de çalışıyor ve diğer sistemlerde bulunmayan bazı faydaları (DB'ye karşı sorguların derleme zamanı doğrulaması gibi) var.
Ashley Mercer

12

Squiggle gibi bir şeyin peşinde misiniz diye merak ediyorum . Ayrıca jDBI çok kullanışlı bir şeydir . Yine de sorgularda size yardımcı olmayacak.


9

Spring JDBC'ye bir göz atardım . SQL'leri programlı olarak yürütmem gerektiğinde kullanıyorum. Misal:

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

Özellikle sorgulama olmak üzere her tür sql yürütmesi için gerçekten harika; tam bir ORM'nin karmaşıklığını eklemeden sonuç kümelerini nesnelere eşlemenize yardımcı olur.


gerçek çalıştırılan sql sorgusunu nasıl alabilirim? Günlüğe kaydetmek istiyorum.
kodmanyagha

5

Spring'in Adlandırılmış JDBC Parametrelerini kullanma eğilimindeyim, böylece "blah'dan colX = ': someValue'" gibi standart bir dize yazabilirim; Bence bu oldukça okunabilir.

Bir alternatif, dizeyi ayrı bir .sql dosyasında sağlamak ve içeriği bir yardımcı program yöntemi kullanarak okumak olabilir.

Ayrıca Squill'e bir göz atmaya değer: https://squill.dev.java.net/docs/tutorial.html


BeanPropertySqlParameterSource kullandığınızı varsayıyorum? Neredeyse size katılıyorum, az önce bahsettiğim sınıf kesinlikle fasulye kullanırken harika, ancak aksi takdirde nesneleri oluşturmak için özel ParameterizedRowMapper kullanmanızı öneririm.
Esko

Pek değil. Adlandırılmış JDBC Parametreleri ile herhangi bir SqlParameterSource kullanabilirsiniz. Fasulye çeşidi yerine MapSqlParameterSource kullanma ihtiyaçlarımı karşıladı. Her iki durumda da, bu iyi bir çözüm. Ancak RowMappers, SQL bulmacasının diğer tarafı ile ilgilenir: sonuç kümelerini nesnelere dönüştürmek.
GaryF

4

Hazırda Bekletme gibi bir ORM kullanmak için önerileri ikinci olarak görüyorum. Bununla birlikte, kesinlikle işe yaramayan durumlar var, bu yüzden bu fırsatı yazmamda yardımcı olduğum bazı şeyleri açıklamak için kullanacağım : SqlBuilder , "oluşturucu" stilini kullanarak dinamik olarak sql ifadeleri oluşturmak için bir java kitaplığıdır. oldukça güçlü ve oldukça esnektir.


4

Adhoc raporlama amaçları için çok dinamik SQL ifadeleri oluşturması gereken bir Java servlet uygulaması üzerinde çalışıyorum. Uygulamanın temel işlevi, önceden kodlanmış bir sorguya bir dizi adlandırılmış HTTP istek parametresi beslemek ve güzel biçimlendirilmiş bir çıktı tablosu oluşturmaktır. Tüm SQL sorgularımı XML dosyalarında depolamak ve bunları tablo biçimlendirme bilgileriyle birlikte raporlama uygulamasına yüklemek için Spring MVC ve bağımlılık enjeksiyon çerçevesini kullandım. Sonunda, raporlama gereksinimleri, mevcut parametre eşleme çerçevelerinin yeteneklerinden daha karmaşık hale geldi ve kendiminkini yazmak zorunda kaldım. Bu, geliştirmede ilginç bir alıştırmaydı ve bulabildiğim her şeyden çok daha sağlam bir parametre eşleme çerçevesi oluşturdu.

Yeni parametre eşlemeleri şöyle görünüyordu:

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

Ortaya çıkan çerçevenin güzelliği, HTTP istek parametrelerini uygun tür denetimi ve sınır denetimi ile doğrudan sorguya işleyebilmesiydi. Giriş doğrulaması için fazladan eşleme gerekmez. Yukarıdaki örnek sorguda, serverId adlı parametre , bir tam sayıya dönüştürülebildiğinden ve 0-50 aralığında olduğundan emin olmak için kontrol edilecektir. AppId parametresi , uzunluk sınırı 50 olan bir tamsayı dizisi olarak işlenir. ShowOwnermevcutsa ve "true" olarak ayarlanmışsa, tırnak içindeki SQL bitleri isteğe bağlı alan eşlemeleri için oluşturulan sorguya eklenecektir. field Daha fazla parametre eşlemesine sahip isteğe bağlı SQL segmentleri dahil olmak üzere birçok parametre türü eşlemesi mevcuttur. Geliştiricinin bulabileceği kadar karmaşık bir sorgu eşlemesine izin verir. Rapor yapılandırmasında, belirli bir sorgunun bir PreparedStatement aracılığıyla nihai eşleştirmelere sahip olup olmayacağını veya önceden oluşturulmuş bir sorgu olarak çalıştırılıp çalıştırılmayacağını belirlemek için kontroller bile vardır.

Örnek Http istek değerleri için:

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

Aşağıdaki SQL'i üretir:

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 veya Hibernate'in veya bu çerçevelerden birinin, türleri doğrulayan, diziler gibi karmaşık veri türlerine ve diğer bu tür özelliklere izin veren daha sağlam bir eşleme mekanizması sunması gerektiğini düşünüyorum. Motorumu yalnızca kendi amaçlarım için yazdım, genel sürüm için pek okunmadı. Şu anda yalnızca Oracle sorgularıyla çalışır ve kodun tamamı büyük bir şirkete aittir. Bir gün fikirlerimi alıp yeni bir açık kaynak çerçevesi oluşturabilirim, ancak mevcut büyük oyunculardan birinin bu zorluğu üstleneceğini umuyorum.


3

Neden tüm sql'yi elle oluşturmak istiyorsunuz? Hazırda Bekletme gibi bir ORM'ye baktınız mı? Projenize bağlı olarak, muhtemelen ihtiyacınız olanın en az% 95'ini yapacak, bunu ham SQL'den daha temiz bir şekilde yapacak ve son performans parçasını elde etmeniz gerekiyorsa, Elle ayarlanması gereken SQL sorguları.


3

Ayrıca MyBatis'e ( www.mybatis.org ) da bakabilirsiniz . Java kodunuzun dışında SQL ifadeleri yazmanıza yardımcı olur ve sql sonuçlarını diğer şeylerin yanı sıra java nesnelerinize eşler.


3

Google , temelde SQLite Veritabanının üzerinde bir soyutlama katmanı olan, Android Uygulamaları için SQL yazmanın çok temiz bir yolunu sağlayan Room Persitence Kitaplığı adlı bir kitaplık sağlar . Körük, resmi web sitesinden kısa kod pasajıdır:

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

Kütüphane için resmi belgelerde daha fazla örnek ve daha iyi belgeler var.

Ayrıca Java ORM olan MentaBean adında bir tane var . Güzel özelliklere sahip ve SQL yazmanın oldukça basit bir yolu gibi görünüyor.


Gereğince Oda belgelerinde : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Dolayısıyla, RDBMS için genel bir ORM kitaplığı değildir. Öncelikle Android Uygulamaları içindir.
RafiAlhamd

2

Bir XML dosyası okuyun.

Bir XML dosyasından okuyabilirsiniz. Bakımı ve çalışması kolaydır. Java'da birkaç satır kod yapmak için standart STaX, DOM, SAX ayrıştırıcıları mevcuttur.

Özniteliklerle daha fazlasını yapın

SQL ile daha fazlasını yapmanıza yardımcı olması için etiket üzerindeki özniteliklerle bazı anlamsal bilgilere sahip olabilirsiniz. Bu, yöntem adı veya sorgu türü veya daha az kod yazmanıza yardımcı olan herhangi bir şey olabilir.

Bakım

Xml'yi kavanozun dışına koyabilir ve kolayca koruyabilirsiniz. Özellikler dosyasıyla aynı faydalar.

Dönüştürmek

XML genişletilebilir ve diğer formatlara kolayca dönüştürülebilir.

Kullanım Örneği

Metamug, REST kaynak dosyalarını sql ile yapılandırmak için xml kullanır.


İsterseniz yaml veya json kullanabilirsiniz. Düz bir özellikler dosyasında depolamaktan daha
Sıralayıcısı

Soru SQL'in nasıl OLUŞTURULACAĞIDIR. SQL oluşturmak için, XML, Ayrıştırıcı, Doğrulama, vb. Kullanmanız gerekiyorsa, fazlasıyla yüklüdür. SQL oluşturmak için XML içeren ilk girişimlerin çoğu, Annotation lehine geri çevriliyor. Kabul cevap tarafından Piotr KOCHANSKI olan sade ve zarif ve noktaya - problemi ve sürdürülebilir çözer. NOT: Farklı bir dilde daha iyi bir SQL sağlamanın alternatif bir yolu YOKTUR.
RafiAlhamd

I don't see a reason to make use of XML. Düzenleyemediğim için önceki yorumumu sildim .
RafiAlhamd

1

SQL dizelerini bir özellikler dosyasına koyarsanız ve sonra bunu okursanız, SQL dizelerini bir düz metin dosyasında tutabilirsiniz.

Bu, SQL tipi sorunlarını çözmez, ancak en azından TOAD veya sqlplus'tan kopyalayıp yapıştırmayı çok daha kolay hale getirir.


0

PreparedStatements'taki (bir metin dosyasında kolayca sağlayabileceğiniz ve yine de kaynak olarak yükleyebileceğiniz) uzun SQL dizeleri dışında, birkaç satıra bölünerek dize birleştirme nasıl elde edilir?

Doğrudan SQL dizeleri oluşturmuyorsunuz, değil mi? Programlamadaki en büyük hayır-hayır budur. Lütfen PreparedStatements'ı kullanın ve verileri parametre olarak sağlayın. SQL Enjeksiyon şansını büyük ölçüde azaltır.


Ancak, bir web sayfasını halka açmıyorsanız - SQL Enjeksiyonu önemli bir sorun mu?
Vidar

4
SQL Enjeksiyonu her zaman önemlidir çünkü yanlışlıkla veya kasıtlı olabilir.
sleske

1
@Vidar - web sayfasını şu anda herkese açık etmiyor olabilirsiniz , ancak "her zaman" dahili olacak olan kod bile çoğu zaman bir noktada daha ileride bir tür dış pozlama ile sonuçlanır. Ve bunu ilk seferde doğru yapmak, daha sonra sorunlar için tüm kod tabanını denetlemekten daha hızlı ve daha güvenlidir ...
Andrzej Doyle

4
Bir PreparedStatement'ın bile bir String'den oluşturulması gerekiyor, değil mi?
Stewart

Evet, ancak güvenli bir PreparedStatement oluşturduğunuz sürece bir String'den PreparedStatement oluşturmak güvenlidir. Muhtemelen bunları oluşturmak için bir PreparedStatementBuilder sınıfı yazmalısınız.
JeeBee
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.