Kaynakları denemeyi JDBC ile nasıl kullanmalıyım?


148

JDBC ile bir veritabanından kullanıcı almak için bir yöntem var:

public List<User> getUser(int userId) {
    String sql = "SELECT id, name FROM users WHERE id = ?";
    List<User> users = new ArrayList<User>();
    try {
        Connection con = DriverManager.getConnection(myConnectionURL);
        PreparedStatement ps = con.prepareStatement(sql); 
        ps.setInt(1, userId);
        ResultSet rs = ps.executeQuery();
        while(rs.next()) {
            users.add(new User(rs.getInt("id"), rs.getString("name")));
        }
        rs.close();
        ps.close();
        con.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}

Bu kodu geliştirmek için Java 7 kaynakları denemeyi nasıl kullanmalıyım ?

Aşağıdaki kod ile denedim, ama birçok tryblok kullanır ve okunabilirliği fazla geliştirmez . try-with-resourcesBaşka bir şekilde mi kullanmalıyım ?

public List<User> getUser(int userId) {
    String sql = "SELECT id, name FROM users WHERE id = ?";
    List<User> users = new ArrayList<>();
    try {
        try (Connection con = DriverManager.getConnection(myConnectionURL);
             PreparedStatement ps = con.prepareStatement(sql);) {
            ps.setInt(1, userId);
            try (ResultSet rs = ps.executeQuery();) {
                while(rs.next()) {
                    users.add(new User(rs.getInt("id"), rs.getString("name")));
                }
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}

5
İkinci örneğinizde, iç kısma ihtiyacınız yoktur try (ResultSet rs = ps.executeQuery()) {çünkü A ResultSet nesnesi, onu oluşturan Statement nesnesi tarafından otomatik olarak kapatılır
Alexander Farber

2
@AlexanderFarber Ne yazık ki, sürücüleri kendi başlarına kapatamayan kötü şöhretli sorunlar var. Sert Knocks Okulu açıkça zaman yakın tüm JDBC kaynaklarına bize öğrettiği, etrafında deneyin-kaynaklarla kullanarak kolaylaştırdı Connection, PreparedStatementve ResultSetde. Gerçekten denememek için hiçbir neden yok, çünkü kaynaklarla denemek bunu kolaylaştırıyor ve kodlarımızı niyetlerimizle ilgili daha kendi kendine belgelendiriyor.
Basil Bourque

Yanıtlar:


85

Örneğinizde dış denemeye gerek yoktur, bu nedenle en azından 3'ten 2'ye inebilirsiniz ve ayrıca ;kaynak listesinin sonunda kapanmanız gerekmez . İki deneme bloğu kullanmanın avantajı, tüm kodunuzun ön tarafta bulunmasıdır, böylece ayrı bir yönteme başvurmanız gerekmez:

public List<User> getUser(int userId) {
    String sql = "SELECT id, username FROM users WHERE id = ?";
    List<User> users = new ArrayList<>();
    try (Connection con = DriverManager.getConnection(myConnectionURL);
         PreparedStatement ps = con.prepareStatement(sql)) {
        ps.setInt(1, userId);
        try (ResultSet rs = ps.executeQuery()) {
            while(rs.next()) {
                users.add(new User(rs.getInt("id"), rs.getString("name")));
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return users;
}

5
Nasıl Connection::setAutoCommitarıyorsun? Böyle bir çağrı içinde izin verilmez tryarasında con = ve ps =. Bir bağlantı havuzuyla yedeklenebilecek bir DataSource'tan bir Bağlantı alırken, autoCommit'in nasıl ayarlandığını kabul edemeyiz.
Basil Bourque

1
bağlantıyı genellikle yönteme enjekte edersiniz (OP'nin sorusunda gösterilen geçici yaklaşımın aksine), bağlantı sağlamak veya kapatmak için çağrılacak bir bağlantı yönetim sınıfı kullanabilirsiniz (havuzda olsun olmasın). bu yöneticide bağlantı davranışınızı belirleyebilirsiniz
svarog

@BasilBourque DriverManager.getConnection(myConnectionURL), autoCommit işaretini de ayarlayan ve bağlantıyı döndüren bir yönteme geçebilir (veya createPreparedStatementönceki örnekteki yönteme eşdeğer olarak ayarlayabilirsiniz ...)
rogerdpack

@rogerdpack Evet, bu mantıklı. Kendi uygulaması var DataSourcenerede getConnectiono zaman bağlantıda geçen bağlantı almak demek, ve gerektiği şekilde yapılandırmak üzere metod yok.
Basil Bourque

1
@rogerdpack Cevabınızdaki açıklama için teşekkürler. Bunu seçilen cevaba güncelledim.
Jonas

187

Bunun uzun zaman önce yanıtlandığını fark ettim ama iç içe geçmiş kaynaklarla deneme çift bloğunu önleyen ek bir yaklaşım önermek istiyorum.

public List<User> getUser(int userId) {
    try (Connection con = DriverManager.getConnection(myConnectionURL);
         PreparedStatement ps = createPreparedStatement(con, userId); 
         ResultSet rs = ps.executeQuery()) {

         // process the resultset here, all resources will be cleaned up

    } catch (SQLException e) {
        e.printStackTrace();
    }
}

private PreparedStatement createPreparedStatement(Connection con, int userId) throws SQLException {
    String sql = "SELECT id, username FROM users WHERE id = ?";
    PreparedStatement ps = con.prepareStatement(sql);
    ps.setInt(1, userId);
    return ps;
}

24
Hayır, kapsanıyor, sorun yukarıdaki kodun SQLException'ı atmayı bildirmeyen bir yöntemin içinden preparStatement çağırmasıdır. Ayrıca, yukarıdaki kod bu hazırlanmış deyimi kapatmadan başarısız olabilir, en az bir yola (SETINT arama sırasında bir SQLException oluşursa.)
Trejkaz

1
@Trejkaz, PreparedStatement'ı kapatmama olasılığı iyi. Bunu düşünmedim, ama haklısın!
Jeanne Boyarsky

2
@ArturoTena evet - sipariş garantilidir
Jeanne Boyarsky

2
@JeanneBoyarsky bunu yapmanın başka bir yolu var mı? Değilse, her sql cümle için belirli bir createPreparedStatement yöntemi oluşturmak gerekir
John Alexander Betts

1
Trejkaz'ın yorumuyla ilgili olarak, createPreparedStatementnasıl kullanırsanız kullanın güvenli değildir. Bunu düzeltmek için setInt (...) etrafına bir try-catch eklemeniz, herhangi birini yakalamanız SQLExceptionve gerçekleştiğinde ps.close () öğesini çağırmanız ve istisnayı yeniden oluşturmanız gerekir. Ancak bu, OP'nin geliştirmek istediği kod kadar uzun ve belirsiz bir kodla sonuçlanır.
Florian F

4

Dış denemede her şeye uyması için lambdas ve JDK 8 Tedarikçi'yi kullanmanın özlü bir yolu:

try (Connection con = DriverManager.getConnection(JDBC_URL, prop);
    PreparedStatement stmt = ((Supplier<PreparedStatement>)() -> {
    try {
        PreparedStatement s = con.prepareStatement("SELECT userid, name, features FROM users WHERE userid = ?");
        s.setInt(1, userid);
        return s;
    } catch (SQLException e) { throw new RuntimeException(e); }
    }).get();
    ResultSet resultSet = stmt.executeQuery()) {
}

5
Bu @bpgergo tarafından tarif edildiği gibi "klasik yaklaşım" dan daha özlü mü? Ben öyle düşünmüyorum ve kodu anlamak daha zordur. Lütfen bu yaklaşımın avantajını açıklayınız.
rmuller

Bu durumda, SQLException'ı açıkça yakalamanız gerektiğini düşünmüyorum. Kaynaklarla denemede aslında "isteğe bağlıdır". Bundan başka cevap gelmiyor. Yani, muhtemelen bunu daha da basitleştirebilirsiniz.
djangofan

ne DriverManager.getConnection (JDBC_URL, prop) olursa; null döndürüyor mu?
gaurav

2

Ek bir sarmalayıcı sınıfı oluşturmaya ne dersiniz?

package com.naveen.research.sql;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public abstract class PreparedStatementWrapper implements AutoCloseable {

    protected PreparedStatement stat;

    public PreparedStatementWrapper(Connection con, String query, Object ... params) throws SQLException {
        this.stat = con.prepareStatement(query);
        this.prepareStatement(params);
    }

    protected abstract void prepareStatement(Object ... params) throws SQLException;

    public ResultSet executeQuery() throws SQLException {
        return this.stat.executeQuery();
    }

    public int executeUpdate() throws SQLException {
        return this.stat.executeUpdate();
    }

    @Override
    public void close() {
        try {
            this.stat.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


Daha sonra çağıran sınıfta preparStatement yöntemini şu şekilde uygulayabilirsiniz:

try (Connection con = DriverManager.getConnection(JDBC_URL, prop);
    PreparedStatementWrapper stat = new PreparedStatementWrapper(con, query,
                new Object[] { 123L, "TEST" }) {
            @Override
            protected void prepareStatement(Object... params) throws SQLException {
                stat.setLong(1, Long.class.cast(params[0]));
                stat.setString(2, String.valueOf(params[1]));
            }
        };
        ResultSet rs = stat.executeQuery();) {
    while (rs.next())
        System.out.println(String.format("%s, %s", rs.getString(2), rs.getString(1)));
} catch (SQLException e) {
    e.printStackTrace();
}


2
Yukarıdaki yorumda yer alan hiçbir şey bunu söylemiyor.
Trejkaz

2

Diğerlerinin de belirttiği gibi, dış kod gerekmediği halde kodunuz temel olarak doğrudur try. İşte birkaç düşünce daha.

DataSource

Burada diğer cevaplar doğru ve iyi, bpgergo tarafından kabul edilen cevap . Ancak hiçbiri modern Java'da DataSourceyaygın olarak kullanılması tavsiye edilmez DriverManager.

Yani eksiksizlik uğruna, veritabanı sunucusundan geçerli tarihi alan eksiksiz bir örnek. Burada kullanılan veritabanı Postgres'dir . Diğer tüm veritabanları benzer şekilde çalışır. Kullanımını veritabanınıza uygun org.postgresql.ds.PGSimpleDataSourcebir uygulama ile değiştirirsiniz DataSource. Bir uygulama, muhtemelen bu rotaya giderseniz sürücünüz veya bağlantı havuzunuz tarafından sağlanır.

Bir DataSourceuygulama gerek yok o “açıldı” hiçbir zaman, çünkü kapatılacak. A DataSourcebir kaynak değildir, veritabanına bağlı değildir, bu nedenle veritabanı sunucusundaki ağ bağlantılarını veya kaynakları tutmaz. A DataSource, veritabanına bağlantı yapılırken, veritabanı sunucusunun ağ adı veya adresi, kullanıcı adı, kullanıcı şifresi ve bir bağlantı yapıldığında belirtilmesini istediğiniz çeşitli seçeneklerle basitçe gerekli olan bilgidir. Yani DataSourceuygulama nesnesi yok değil sizin Try-ile-kaynaklar parantez içine girin.

İç içe kaynaklar ile deneme

Kodunuz, kaynaklarla denenmiş iç içe ifadelerin doğru şekilde kullanılmasını sağlar.

Aşağıdaki örnek kodda , kaynaklarla deneme sözdizimini iki kez kullandığımıza dikkat edin ; biri iç içe. Dış tryiki kaynağı tanımlar: Connectionve PreparedStatement. İç kısım kaynağı trytanımlar ResultSet. Bu yaygın bir kod yapısıdır.

İçinden bir istisna atılır ve orada yakalanmazsa, ResultSetkaynak otomatik olarak kapatılır (varsa, boş olmaz). Bunu takiben, PreparedStatementkapanacak ve son Connectionolarak kapalı olacak. Kaynaklar, kaynak denemeli ifadeler içinde bildirildikleri ters sırada otomatik olarak kapatılır.

Buradaki örnek kod aşırı derecede basittir. Yazıldığı gibi, tek bir kaynaklarla deneme ifadesi ile yürütülebilir. Ancak gerçek bir işte, iç içe tryçağrılar arasında daha fazla iş yapacaksınız . Örneğin, kullanıcı arabiriminizden veya POJO'nuzdan değerler çıkarıyor ve daha sonra yöntemlere ?çağrılar yoluyla SQL'inizdeki yer tutucuları yerine getirmek için bunları PreparedStatement::set…geçiriyor olabilirsiniz.

Sözdizimi notları

Sondaki noktalı virgül

Kaynaklarla deneme parantezindeki son kaynak deyimini izleyen noktalı virgülün isteğe bağlı olduğuna dikkat edin. İki nedenden ötürü kendi çalışmamda kullanıyorum: Tutarlılık ve tam görünüyor ve satır sonu noktalı virgülleri hakkında endişelenmenize gerek kalmadan kopyalamayı yapıştırma işlemini kolaylaştırır. IDE'niz son noktalı virgülü gereksiz olarak işaretleyebilir, ancak ayrılmanın bir zararı yoktur.

Java 9 - Kaynaklarla denemede mevcut değişkenleri kullanma

Java 9'daki yenilikler, kaynaklarla deneme sözdiziminin bir geliştirmesidir. Artık, tryifadenin parantezlerinin dışındaki kaynakları bildirebilir ve doldurabiliriz . Bunu JDBC kaynakları için henüz yararlı bulmadım, ancak kendi çalışmanızda aklınızda bulundurun.

ResultSet kendini kapatmalı ama olmayabilir

İdeal bir dünyada ResultSet, belgelerin vaat ettiği gibi kendini kapatır:

Bir ResultSet nesnesi, onu oluşturan Statement nesnesi kapatıldığında, yeniden çalıştırıldığında veya bir sonraki sonucu birden çok sonuç dizisinden almak için kullanıldığında otomatik olarak kapatılır.

Ne yazık ki, geçmişte bazı JDBC sürücüleri bu sözü yerine getiremedi. Sonuç olarak, birçok JDBC programcılar dahil mutlaka kapatılması tüm JDBC kaynaklarına öğrenilen Connection, PreparedStatementve ResultSetde. Modern kaynaklarla deneme sözdizimi bunu daha kolay ve daha kompakt bir kodla yapmıştır. Bildirim Java ekibi işaretleme rahatsız gitti ResultSetolarak AutoCloseable, ben bunu faydalanmak düşündürmektedir. Tüm JDBC kaynaklarınız etrafında kaynaklarla deneme özelliğini kullanmak, kodunuzu niyetleriniz konusunda daha fazla kendi kendine belgelendirir.

Kod örneği

package work.basil.example;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.util.Objects;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.doIt();
    }

    private void doIt ( )
    {
        System.out.println( "Hello World!" );

        org.postgresql.ds.PGSimpleDataSource dataSource = new org.postgresql.ds.PGSimpleDataSource();

        dataSource.setServerName( "1.2.3.4" );
        dataSource.setPortNumber( 5432 );

        dataSource.setDatabaseName( "example_db_" );
        dataSource.setUser( "scott" );
        dataSource.setPassword( "tiger" );

        dataSource.setApplicationName( "ExampleApp" );

        System.out.println( "INFO - Attempting to connect to database: " );
        if ( Objects.nonNull( dataSource ) )
        {
            String sql = "SELECT CURRENT_DATE ;";
            try (
                    Connection conn = dataSource.getConnection() ;
                    PreparedStatement ps = conn.prepareStatement( sql ) ;
            )
            {
                … make `PreparedStatement::set…` calls here.
                try (
                        ResultSet rs = ps.executeQuery() ;
                )
                {
                    if ( rs.next() )
                    {
                        LocalDate ld = rs.getObject( 1 , LocalDate.class );
                        System.out.println( "INFO - date is " + ld );
                    }
                }
            }
            catch ( SQLException e )
            {
                e.printStackTrace();
            }
        }

        System.out.println( "INFO - all done." );
    }
}
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.