मुझे JDBC के साथ कोशिश-के-संसाधनों का उपयोग कैसे करना चाहिए?


148

मेरे पास JDBC वाले डेटाबेस से उपयोगकर्ताओं को प्राप्त करने की एक विधि है:

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

मुझे इस कोड को बेहतर बनाने के लिए जावा 7 कोशिश-के-संसाधनों का उपयोग कैसे करना चाहिए ?

मैंने नीचे दिए गए कोड के साथ कोशिश की है, लेकिन यह कई tryब्लॉकों का उपयोग करता है, और पठनीयता में बहुत सुधार नहीं करता है। क्या मुझे try-with-resourcesदूसरे तरीके से उपयोग करना चाहिए ?

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
आपके दूसरे उदाहरण में, आपको आंतरिक की आवश्यकता नहीं है try (ResultSet rs = ps.executeQuery()) {क्योंकि A ResultSet ऑब्जेक्ट स्वचालित रूप से स्टेटमेंट ऑब्जेक्ट द्वारा इसे बंद कर दिया गया है
अलेक्जेंडर फार्बर

2
@AlexanderFarber दुर्भाग्य से, ड्राइवरों के साथ कुख्यात समस्याएं रही हैं जो अपने दम पर संसाधनों को बंद करने में विफल रहे। मुश्किल दस्तक के स्कूल स्पष्ट रूप से हमेशा सभी पास JDBC संसाधनों के लिए हमें सिखाता है, चारों ओर कोशिश-संसाधनों के साथ का उपयोग कर आसान बना दिया Connection, PreparedStatementऔर ResultSetभी। वास्तव में कोई कारण नहीं है, क्योंकि कोशिश-के-संसाधन इसे आसान बनाते हैं और हमारे इरादों के अनुसार हमारे कोड को अधिक स्व-दस्तावेजीकरण बनाते हैं।
तुलसी बोर्क

जवाबों:


85

आपके उदाहरण में बाहरी प्रयास की कोई आवश्यकता नहीं है, इसलिए आप कम से कम 3 से 2 तक नीचे जा सकते हैं, और आपको ;संसाधन सूची के अंत में समापन की भी आवश्यकता नहीं है । दो कोशिश ब्लॉकों का उपयोग करने का लाभ यह है कि आपके सभी कोड सामने मौजूद हैं, इसलिए आपको एक अलग विधि का संदर्भ नहीं देना है:

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
आप कैसे कॉल करते हैं Connection::setAutoCommit? इस तरह के एक कॉल के भीतर अनुमति नहीं है tryके बीच con = और ps =। जब एक DataSource से एक कनेक्शन मिल रहा है जो एक कनेक्शन पूल के साथ समर्थित हो सकता है, तो हम यह नहीं मान सकते हैं कि ऑटोकॉमिट कैसे सेट है।
बेसिल बोर्ख

1
आप आमतौर पर कनेक्शन को विधि में इंजेक्ट करेंगे (ओपी के प्रश्न में दिखाए गए तदर्थ दृष्टिकोण के विपरीत), आप एक कनेक्शन प्रबंधन वर्ग का उपयोग कर सकते हैं जिसे कनेक्शन प्रदान करने या बंद करने के लिए बुलाया जाएगा (यह पूल किया गया है या नहीं)। उस प्रबंधक में आप अपने कनेक्शन व्यवहार को निर्दिष्ट कर सकते हैं
svarog

@BasilBourque आप DriverManager.getConnection(myConnectionURL)एक ऐसी विधि में स्थानांतरित हो सकते हैं जो ऑटोकॉमिट ध्वज को भी सेट करता है और कनेक्शन लौटाता है (या इसे createPreparedStatementपूर्ववर्ती उदाहरण में विधि के बराबर में सेट करता है ...)
rogerdpack

@rogerdpack हाँ, यह समझ में आता है। DataSourceजहां getConnectionआप कहते हैं कि विधि का अपना कार्यान्वयन है , कनेक्शन प्राप्त करें और आवश्यकतानुसार इसे कॉन्फ़िगर करें, फिर कनेक्शन पर गुजर रहा है।
तुलसी Bourque

1
उत्तर में स्पष्टीकरण के लिए @rogerdpack धन्यवाद। मैंने इसे चयनित उत्तर में अद्यतन किया है।
जोनास

187

मुझे लगता है कि यह बहुत पहले उत्तर दिया गया था, लेकिन एक अतिरिक्त दृष्टिकोण का सुझाव देना चाहता है जो नेस्टेड कोशिश-विथ-रिसोर्स डबल ब्लॉक से बचा जाता है।

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
नहीं, यह कवर किया गया है, समस्या यह है कि ऊपर दिया गया कोड एक विधि के अंदर से तैयारी को बुला रहा है जो SQLException को फेंकने की घोषणा नहीं करता है। इसके अलावा, ऊपर दिए गए कोड में कम से कम एक पथ है जहां यह तैयार किए गए कथन को बंद किए बिना विफल हो सकता है (यदि सेटआईंट कॉल करते समय एक SQLException होती है।)
ट्रेकजैक

1
रेडीस्टेडमेंट बंद नहीं होने की संभावना पर @Trejkaz अच्छा पॉइंट। मैंने ऐसा नहीं सोचा था, लेकिन आप सही हैं!
ज्यां बोयार्स्की

2
@ArturoTena हाँ - आदेश की गारंटी है
जैन Boyarsky

2
@JeanneBoyarsky ऐसा करने का एक और तरीका है? यदि मुझे प्रत्येक वर्ग वाक्य के लिए एक विशिष्ट createPreparedStatement विधि बनाने की आवश्यकता नहीं है
जॉन अलेक्जेंडर बेट्स

1
तर्जुक की टिप्पणी के बारे में, createPreparedStatementयह असुरक्षित है कि आप इसका उपयोग कैसे करते हैं। इसे ठीक करने के लिए आपको सेटआईंट (...) के चारों ओर एक ट्राइ-कैच जोड़ना होगा, किसी को भी पकड़ना होगा SQLException, और जब ऐसा होता है तो कॉल ps.close () हो जाता है और अपवाद को रीथ्रो करता है। लेकिन उस कोड में लगभग उतना ही लंबा और अटूट परिणाम होगा जितना कि ओपी में सुधार करना चाहता था।
फ्लोरियन एफ

4

यहाँ एक संक्षिप्त तरीका है lambdas और JDK 8 आपूर्तिकर्ता का उपयोग करके बाहरी कोशिश में सब कुछ फिट करने के लिए:

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
यह "क्लासिक दृष्टिकोण" से अधिक संक्षिप्त है जैसा कि @bpgergo द्वारा वर्णित है? मुझे ऐसा नहीं लगता और कोड को समझना ज्यादा मुश्किल है। तो कृपया इस दृष्टिकोण का लाभ बताएं।
rmuller 19

मुझे नहीं लगता, इस मामले में, कि आपको SQLException को स्पष्ट रूप से पकड़ना आवश्यक है। यह एक कोशिश के साथ संसाधनों पर वास्तव में "वैकल्पिक" है। कोई अन्य उत्तर इसका उल्लेख नहीं करता है। तो, आप शायद इसे और सरल कर सकते हैं।
djangofan

क्या होगा अगर DriverManager.getConnection (JDBC_URL, प्रोप); शून्य देता है?
गौरव

2

एक अतिरिक्त आवरण वर्ग बनाने के बारे में क्या?

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


फिर कॉलिंग क्लास में आप निम्न प्रकार से तैयारी विधि लागू कर सकते हैं:

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
ऊपर टिप्पणी में कुछ भी कभी नहीं कहता है कि यह नहीं है।
ट्रेकजाज़

2

जैसा कि दूसरों ने कहा है, आपका कोड मूल रूप से सही है, हालांकि बाहरी tryअनावश्यक है। यहाँ कुछ और विचार हैं।

DataSource

यहाँ अन्य उत्तर सही और अच्छे हैं, जैसे bpgergo द्वारा स्वीकृत उत्तर । लेकिन आधुनिक जावा में DataSourceआमतौर पर अनुशंसित शो का उपयोग नहीं किया जाता है DriverManager

तो पूर्णता के लिए, यहां एक पूर्ण उदाहरण है जो डेटाबेस सर्वर से वर्तमान दिनांक को प्राप्त करता है। यहाँ उपयोग किया जाने वाला डेटाबेस पोस्टग्रेज है । कोई भी अन्य डेटाबेस इसी तरह काम करेगा। आप अपने डेटाबेस org.postgresql.ds.PGSimpleDataSourceमें DataSourceउपयुक्त के कार्यान्वयन के साथ उपयोग की जगह लेंगे । यदि आप उस मार्ग पर जाते हैं तो आपके विशेष ड्राइवर, या कनेक्शन पूल द्वारा एक कार्यान्वयन की संभावना प्रदान की जाती है।

एक DataSourceकार्यान्वयन की जरूरत नहीं , बंद कर दिया है क्योंकि यह "खोला" कभी नहीं किया गया है। A DataSourceसंसाधन नहीं है, डेटाबेस से जुड़ा नहीं है, इसलिए यह डेटाबेस सर्वर पर न तो नेटवर्किंग कनेक्शन और न ही संसाधन पकड़ रहा है। एक DataSourceबस की जरूरत है जब डेटाबेस से संबंध, डेटाबेस सर्वर के नेटवर्क नाम या पता, उपयोगकर्ता नाम, उपयोगकर्ता पासवर्ड, और विभिन्न विकल्पों आपके द्वारा निर्दिष्ट करना चाहते हैं जब एक कनेक्शन अंत में किया जाता है के साथ बनाने की जानकारी है। इसलिए आपका DataSourceकार्यान्वयन ऑब्जेक्ट आपके प्रयास के साथ संसाधन कोष्ठक के अंदर नहीं जाता है।

नेस्टेड कोशिश-के साथ संसाधनों

आपका कोड नेस्टेड-विथ-रिसोर्स स्टेटमेंट का उचित उपयोग करता है।

नीचे दिए गए उदाहरण कोड में ध्यान दें कि हम दो बार try-with-resource syntax का उपयोग करते हैं , एक दूसरे के अंदर नेस्टेड का। बाहरी tryदो संसाधनों को परिभाषित करता है: Connectionऔर PreparedStatement। आंतरिक संसाधन tryको परिभाषित करता है ResultSet। यह एक सामान्य कोड संरचना है।

यदि कोई अपवाद आंतरिक एक से फेंका गया है, और वहां नहीं पकड़ा गया है, तो ResultSetसंसाधन स्वचालित रूप से बंद हो जाएगा (यदि यह मौजूद है, तो अशक्त नहीं है)। उसके बाद, PreparedStatementवसीयत बंद हो जाती है, और अंत में Connectionबंद हो जाती है। संसाधनों को स्वचालित रूप से रिवर्स ऑर्डर में बंद कर दिया जाता है, जिसमें उन्हें प्रयास-संसाधन विवरणों के भीतर घोषित किया गया था।

यहाँ उदाहरण कोड अत्यधिक सरलीकृत है। जैसा कि लिखा गया है, इसे एकल-प्रयास-संसाधन विवरण के साथ निष्पादित किया जा सकता है। लेकिन वास्तविक काम में आप संभवत: नेस्टेड tryकॉल के बीच अधिक काम कर रहे हैं। उदाहरण के लिए, आप अपने उपयोगकर्ता-इंटरफ़ेस या POJO से मान निकाल सकते हैं, और फिर ?कॉल के माध्यम से अपने SQL में प्लेसहोल्डर्स को पूरा करने के लिए उन PreparedStatement::set…तरीकों से गुजर रहे हैं ।

सिंटेक्स नोट्स

अनुगामी अर्धविराम

ध्यान दें कि कोशिश-के-संसाधनों के कोष्ठक के भीतर अंतिम संसाधन विवरण को भरने वाला अर्धविराम वैकल्पिक है। मैं इसे दो कारणों से अपने काम में शामिल करता हूं: संगति और यह पूर्ण दिखता है, और यह एंड-ऑफ-लाइन अर्धविरामों के बारे में चिंता किए बिना लाइनों के मिश्रण को कॉपी-पेस्ट करना आसान बनाता है। आपका आईडीई अंतिम अर्धविराम को बहुत ही कम कर सकता है, लेकिन इसे छोड़ने में कोई बुराई नहीं है।

जावा 9 - कोशिश में संसाधनों के साथ मौजूदा संस्करणों का उपयोग करें

जावा 9 में नया, संसाधनों के साथ-साथ वाक्य रचना के लिए एक वृद्धि है। अब हम tryबयान के कोष्ठकों के बाहर संसाधनों को घोषित और आबाद कर सकते हैं । मैंने अभी तक जेडीबीसी संसाधनों के लिए इसे उपयोगी नहीं पाया है, लेकिन इसे अपने काम में ध्यान में रखें।

ResultSet खुद को बंद करना चाहिए, लेकिन नहीं हो सकता है

एक आदर्श दुनिया ResultSetमें प्रलेखन वादे के रूप में खुद को बंद कर देगा:

जब रिजल्ट जेनरेट होता है, तो रिजल्टसेट ऑब्जेक्ट स्वचालित रूप से बंद हो जाता है, इसे बंद किया जाता है, फिर से निष्पादित किया जाता है, या कई परिणामों के अनुक्रम से अगला परिणाम प्राप्त करने के लिए उपयोग किया जाता है।

दुर्भाग्य से, अतीत में कुछ JDBC ड्राइवर बदनाम हुए थे और इस वादे को पूरा करने में विफल रहे। परिणामस्वरूप, कई JDBC प्रोग्रामर ने स्पष्ट रूप से अपने सभी JDBC संसाधनों को बंद करना सीख लिया Connection, जिनमें शामिल हैं PreparedStatement, और ResultSetभी। आधुनिक कोशिश-के साथ संसाधनों के सिंटैक्स ने ऐसा करना आसान बना दिया है, और अधिक कॉम्पैक्ट कोड के साथ। ध्यान दें कि जावा टीम के ResultSetरूप में चिह्नित करने की परेशान करने के लिए चला गया AutoCloseable, और मेरा सुझाव है कि हम उस का उपयोग करें। अपने सभी JDBC संसाधनों के आसपास एक कोशिश के साथ संसाधनों का उपयोग करना आपके इरादों के अनुसार आपके कोड को अधिक स्व-दस्तावेजीकरण बनाता है।

कोड उदाहरण

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." );
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.