감 잃지말고 개발하기

[Error][Java][DB] java.sql.SQLException: org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement with address: "NULL" is closed. 본문

Java/Error

[Error][Java][DB] java.sql.SQLException: org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement with address: "NULL" is closed.

persii 2023. 5. 20. 01:01

ERROR

java.sql.SQLException :
org.apache.tomcat.dbcp.dbcp2.DelegatingPreparedStatement with address: "NULL" is closed.

 

 


 

배경

아래 CartDAO 클래스의 insertCart() 메서드를 실행하는 도중 에러가 발생했다.

 

insertCart() 메서드는 ArrayList<Integer> 타입의 idList와 String 타입의 s_userId를 인자로 받아 idList의 배열 길이만큼 for문을 돌리면서 idList의 요소를 DB 테이블에 저장하는 역할을 수행한다. 

public class CartDAO {

	Connection conn;
    
	/** 도서 아이디(배열)와 로그인 아이디를 DB에 추가하는 메서드 
	 * @throws SQLException */
	public int insertCart(ArrayList<Integer> idList, String s_userId) throws SQLException {

		int insertCount = 0;
		String sql = "INSERT INTO jspbookshop.cart VALUES(NULL,?,?,?)";
		PreparedStatement pstmt = conn.prepareStatement(sql);
        
		for (int id : idList) {
			try {
				pstmt.setString(1, s_userId);
				pstmt.setInt(2, id);
				pstmt.setInt(3, 1);

				insertCount += pstmt.executeUpdate();
			} catch (Exception e) {
				System.out.println(" Ca.DAO: insertCart(idList) ERROR: " + e);
			} finally {
				JdbcUtil.close(pstmt);
			}
		}
		return insertCount;
	}
}

 

 

원인

It suggests that the PreparedStatement object is being closed before it is used.

 

즉, PreparedStatement 객체가 필요한만큼 사용되기도 전에 닫혀버려 일어난 에러였다.

위 코드에서 첫 번째 for문을 돌 때 finally 문에서 clost(pstmt) 메서드로 PreparedStatement 객체를 닫아버려(리소스 해제) 사용할 PreparedStatement 객체가 없어진 것이다.

 

 

해결

자바 7부터 제공하는 try-with-resources 문을 사용하여 try문의 괄호 안에 사용할 리소스(PreparedStatement 객체)를 선언한다.

 

try문 괄호 안에 PreparedStatement 객체를 선언하면,

루프를 돌 때마다 새로운 PreparedStatement 객체가 try-with-resources 블록 안에 생성되게 된다.

그런 후 PreparedStatement 객체가 완전히 사용되면 예외가 발생하더라도 PreparedStatement 인터페이스가 상속한 AutoCloseable 인터페이스의 close() 메서드로 인해 정상적으로 해제되게 된다. 

public class CartDAO {

	Connection conn;
    
	/** 도서 아이디(배열)와 로그인 아이디를 DB에 추가하는 메서드 
	 * @throws SQLException */
	public int insertCart(ArrayList<Integer> idList, String s_userId) throws SQLException {

		int insertCount = 0;
		String sql = "INSERT INTO jspbookshop.cart VALUES(NULL,?,?,?)";
		PreparedStatement pstmt = conn.prepareStatement(sql);
        
		for (int id : idList) {
		    try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
		        pstmt.setString(1, s_userId);
		        pstmt.setInt(2, id);
		        pstmt.setInt(3, 1);

		        insertCount += pstmt.executeUpdate();
		    } catch (Exception e) {
		        System.out.println(" Ca.DAO: insertCart(idList) ERROR: " + e);
		    }
		}
		return insertCount;
	}
}

 

 


 

 추가 설명 

1. try-with-resources 문

자바 7부터 제공하는 try-with-resources 문은 close() 메서드를 명시적으로 호출하지 않아도 try 블록 내에서 열린 리소스를 자동으로 닫도록 만들어준다.

단, try-with-resources 문을 사용하기 위해선 해당 리소스가 AutoCloseable 인터페이스를 구현해야 한다.

 

JavaDoc에서 해당 리소스 PreparedStatement 인터페이스를 살펴보면 해당 인터페이스는 Statement 인터페이스를 상속하고 있고, 이 Statement 인터페이스가 AutoCloseable 인터페이스를 상속하고 있음을 알 수 있다. 

PreparedStatement javadoc

 

Statement javadoc

 

2. AutoCloseable 인터페이스

An object that may hold resources (such as file or socket handles) until it is closed. The close() method of an AutoCloseable object is called automatically when exiting a try-with-resources block for which the object has been declared in the resource specification header. This construction ensures prompt release, avoiding resource exhaustion exceptions and errors that may otherwise occur.
                                                                                                                                                                    - javadoc

 

AutoCloseable 객체는 파일, 소켓 등의 자원을 보유할 수 있는데,

해당 객체의 close() 메서드는 해당 객체를 상속한 리소스가 try-with-resources 블록을 벗어날 때 자동으로 호출된다.

그래서 만약 예외가 발생할 시 리소스의 close() 메서드가 먼저 호출된 이후에 예외 블록 부분(catch 블록)이 수행된다.

이 구조는 리소스 고갈 예외와 오류를 방지하기 위해 즉시 해제되도록 보장한다. 


♠ Java 8 JavaDoc 페이지 ♠

https://docs.oracle.com/javase/8/docs/api/index.html

 

Java Platform SE 8

 

docs.oracle.com


 

끝.