JDBC와 함께 try-with-resources를 어떻게 사용해야합니까?


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

이 코드를 향상시키기 위해 Java 7 try-with-resources 를 어떻게 사용해야 합니까?

아래 코드를 사용해 보았지만 많은 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
두 번째 예제에서는 생성 한 Statement 객체에 의해 ResultSet 객체가 자동으로 닫히try (ResultSet rs = ps.executeQuery()) { 므로 inner가 필요하지 않습니다.
Alexander Farber

2
@AlexanderFarber 불행히도, 스스로 리소스를 닫지 못한 드라이버에는 악명 높은 문제가있었습니다. 하드 의욕을 잃게의 학교는 명시 적으로 항상 가까운 모든 JDBC 리소스에 우리를 가르치고, 주위 시도 --자원을 사용하여보다 쉽게 Connection, PreparedStatement그리고 ResultSet너무. try-with-resource를 사용하면 코드를 쉽게 작성하고 의도에 따라 코드를 자체 문서화 할 수 있기 때문에 실제로 그렇게하지 않아도됩니다.
바질 부르 케

답변:


85

예제에서 외부 시도가 필요하지 않으므로 적어도 3에서 2로 내려갈 수 ;있으며 리소스 목록 끝에서 닫을 필요가 없습니다 . 두 개의 try 블록을 사용하면 모든 코드가 미리 표시되므로 별도의 방법을 참조 할 필요가 없습니다.

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 =
Basil Bourque

1
OP의 질문에 표시된 임시 접근 방식과 달리 일반적으로 메소드에 연결을 삽입하면 연결을 제공하거나 닫기 위해 호출되는 연결 관리 클래스를 사용할 수 있습니다 (풀링 여부). 해당 관리자에서 연결 동작을 지정할 수 있습니다
svarog

@BasilBourque DriverManager.getConnection(myConnectionURL)autoCommit 플래그를 설정하고 연결을 반환하는 메소드로 이동할 수도 있습니다 (또는 createPreparedStatement이전 예제 의 메소드 와 동일하게 설정 ).
rogerdpack

@rogerdpack 그렇습니다. 원하는 대로 메소드 의 DataSource위치를 직접 구현 getConnection하고 연결을 확보 한 후 필요에 따라 구성한 다음 연결을 전달하십시오.
Basil Bourque

1
답변을 명확하게 해주셔서 감사합니다. 선택한 답변으로 이것을 업데이트했습니다.
Jonas

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을 던지도록 선언하지 않은 메소드 내부에서 prepareStatement를 호출한다는 것입니다. 또한 위의 코드에는 준비된 명령문을 닫지 않고 실패 할 수있는 하나 이상의 경로가 있습니다 (setInt를 호출하는 동안 SQLException이 발생하는 경우)
Trejkaz

1
@Trejkaz는 PreparedStatement를 닫지 않을 가능성을 지적합니다. 나는 그것을 생각하지 않았지만 당신은 옳습니다!
Jeanne Boyarsky 2016 년

2
@ArturoTena 예 - 순서는 보장
쟌느 Boyarsky

2
@JeanneBoyarsky 다른 방법이 있습니까? 나는 각 SQL 문장에 대한 특정의 createPreparedStatement 방법을 만들 필요가없는 경우
존 알렉산더 베츠

1
Trejkaz의 의견에 대해서는 createPreparedStatement사용 방법에 관계없이 안전하지 않습니다. 이 문제를 해결하려면 setInt (...) 주위에 try-catch를 추가하고 any를 잡고 SQLExceptionps.close ()를 호출하고 예외를 다시 발생시켜야합니다. 그러나 이로 인해 OP가 개선하기를 원하는 코드만큼 길고 우아하지 않은 코드가 생길 수 있습니다.
Florian F

4

다음은 람다와 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

이 경우 SQLException을 명시 적으로 잡아야한다고 생각하지 않습니다. try-with-resources에서 실제로 "선택적"입니다. 다른 답변은 이것을 언급하지 않습니다. 따라서이를 더 단순화 할 수 있습니다.
djangofan

만약 DriverManager.getConnection (JDBC_URL, prop); null을 반환합니까?
gaurav

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


그런 다음 호출 클래스에서 다음과 같이 prepareStatement 메소드를 구현할 수 있습니다.

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
위의 의견에서 아무것도 말하지 않습니다.
Trejkaz

2

다른 사람들이 말했듯이 외부 try는 필요하지 않지만 코드는 기본적으로 정확합니다 . 몇 가지 생각이 더 있습니다.

DataSource

여기의 다른 답변은 bpgergo 의 허용되는 답변 과 같이 정확하고 좋습니다 . 그러나 현대 자바에서의 DataSource사용보다 일반적으로 권장 되는의 사용을 보여주지는 않습니다 DriverManager.

완성도를 높이기 위해 데이터베이스 서버에서 현재 날짜를 가져 오는 전체 예제가 있습니다. 여기에 사용 된 데이터베이스는 Postgres 입니다. 다른 데이터베이스도 비슷하게 작동합니다. 사용을 데이터베이스 org.postgresql.ds.PGSimpleDataSourceDataSource적합한 구현으로 대체하십시오 . 해당 경로로 이동하면 특정 드라이버 또는 연결 풀에서 구현이 제공 될 수 있습니다.

DataSource구현은 필요 하지 가 "오픈 없다"결코 때문에 폐쇄 될 수있다. A DataSource는 리소스가 아니며 데이터베이스에 연결되어 있지 않으므로 데이터베이스 서버의 네트워킹 연결이나 리소스를 보유하지 않습니다. A DataSource는 데이터베이스 서버의 네트워크 이름 또는 주소, 사용자 이름, 사용자 암호 및 연결시 원하는 다양한 옵션을 사용하여 데이터베이스에 연결할 때 필요한 정보입니다. 따라서 DataSource구현 객체는 리소스 사용 시도 괄호 안에 들어 가지 않습니다 .

중첩 된 리소스 사용 가능

코드는 중첩 된 try-with-resources 문을 올바르게 사용합니다.

아래 예제 코드에서 try-with-resources 구문을 두 번 사용 하고 하나는 다른 하나 안에 중첩되어 있습니다. 외부는 try두 가지 자원을 정의 Connection하고 PreparedStatement. 내부 tryResultSet리소스를 정의합니다 . 이것은 일반적인 코드 구조입니다.

내부 예외에서 예외가 발생하여 발견되지 않으면 ResultSet자원이 자동으로 닫힙니다 (있는 경우 null이 아님). 그 후에 는 닫히고 PreparedStatement마지막으로 Connection닫힙니다. 리소스는 try-with-resource 문에서 선언 된 순서와 반대로 자동으로 닫힙니다.

여기 예제 코드는 지나치게 단순합니다. 작성된 것처럼, 단일 try-with-resources 문으로 실행될 수 있습니다. 그러나 실제 작업에서는 중첩 된 try통화 쌍간에 더 많은 작업을 수행 할 수 있습니다 . 예를 들어, 사용자 인터페이스 또는 POJO에서 값을 추출한 후 메소드 ?호출을 통해 SQL 내에서 자리 표시자를 이행하도록 값을 전달할 수 PreparedStatement::set…있습니다.

구문 메모

세미콜론 후행

try-with-resources의 괄호 안에 마지막 자원 설명 뒤에 오는 세미콜론은 선택 사항입니다. 일관성과 완성도를 높이고 줄 끝 세미콜론에 대해 걱정할 필요없이 여러 줄을 쉽게 복사하여 붙여 넣을 수 있습니다. IDE는 마지막 세미콜론을 불필요한 것으로 플래그 지정할 수 있지만, 그대로두면 아무런 해가 없습니다.

Java 9 – try-with-resources에서 기존 변수 사용

Java 9의 새로운 기능은 리소스를 사용하여 시도하는 구문이 향상되었습니다. 이제 try문장 의 괄호 밖에서 리소스를 선언하고 채울 수 있습니다 . 나는 이것이 JDBC 자원에 유용하다는 것을 아직 알지 못했지만 자신의 작업에서 명심하십시오.

ResultSet 자체를 닫아야하지만 그렇지 않을 수 있습니다

이상적인 세계 ResultSet에서는 문서가 약속 한대로 닫힙니다.

ResultSet 오브젝트는이를 생성 한 Statement 오브젝트가 닫히거나 다시 실행되거나 여러 결과 시퀀스에서 다음 결과를 검색하는 데 사용될 때 자동으로 닫힙니다.

불행하게도, 과거에 일부 JDBC 드라이버는이 약속을 이행하지 못한 것으로 악명 높았습니다. 그 결과, 많은 JDBC 프로그래머는 다음과 같은 명시 적으로 가까운 모든 JDBC 리소스에 배운 Connection, PreparedStatementResultSet도. 현대의 리소스에 대한 try-with-resources 구문을 사용하면보다 쉽고 간단한 코드를 사용할 수 있습니다. Java 팀은으로 표시하는 것을 귀찮게 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." );
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.