▸JSP & Servlet/기본 문법

JDBC를 이용한 DB작업_DB 연동 및 데이터 작업 [2/3]

코데방 2020. 2. 19.
728x90

[ JDBC ]

  • Java DataBase Connecivity
  • 자바 프로그램 안에서 SQL을 실행하기 위해 데이터베이스를 연결해주는 기능
  • 오라클 뿐만 아니라 대부분의 DB에서 JDBC 기능(드라이버)을 제공해줌

JDBC는 DB를 사용하기 위한 외부 라이브러리입니다. 오라클용 JDBC 설정은 아래 링크글을 참조하시면 됩니다.

 

[· JSP & Servlet/- 개발 TIP] - 자바 외부 라이브러리 사용 방법 (JDBC 설정 방법 포함)

 

 

 

 

 


 

[ DB 연결 ]

 

1. 드라이버 로드

  • java.lang.Class의 static forName() 메소드 사용
  • Class.forName("oracle.jdbc.driver.OracleDriver");

'oracle.jdbc.driver.OracleDriver'는 외부 라이브러리인 ojdbc.jar에 포함된 패키지 및 클래스명입니다. java.lang에 속한 기본 클래스라 import가 필요없고 static 메소드이기 때문에 그냥 바로 사용하면 됩니다.

 

Class.forName("oracle.jdbc.driver.OracleDriver");

 

이 메소드는 제공된 클래스를 인스턴스화해서 내부적으로 저장해서 메모리에 해당 클래스를 로드시킵니다. 즉, 클래스(드라이버)를 사용할 수 있게끔 하는 건데 new를 이용한 인스턴스 생성과 같다고 볼 수 있습니다.

 

jdbc를 이런 방식으로 올리는 이유는 좀 더 편하게 jdbc를 사용할 수 있게 하기 위함입니다. Java가 만들어둔 틀(구조, 규약, 인터페이스)을 이용해 각 DB에서는 jdbc 라이브러리(드라이버)를 만들어서 제공하고, 드라이버를 사용할 수 있도록 초기화 해주는 작업을 forName() 메소드에서 진행해줍니다. 다만 JAVA 1.6 및 jdbc 4.0 버전 이상에서는 자동으로 드라이버 초기화가 진행되기 때문에 forName() 메소드를 사용하지 않아도 됩니다.

 

 


 

 

2. Connection 객체 생성

  • java.sql.Connection 클래스
  • java.sql.DriverManager 클래스의 static 메소드로 생성
  • DriverManager.getConnection(String url, String user, String password)

input / output 스트림이나 채널(Channel)과 같은 기능으로 DB와 연결되는 통로를 생성합니다. 위에서 말한 forName() 메소드를 통하거나 아니면 자동으로 드라이버가 로드될 때 jdbc 드라이버는 DriverManager에 자신을 등록합니다. 따라서 별도의 작업을 하지 않고도 Connection 객체를 얻어올 수 있습니다.

 

파라미터는 다음과 같습니다.

  • url : "jdbc:드라이버 종류://IP:포트번호:DB명"
  • user / password : DB 접속 계정 정보

DBMS 안에는 여러 DB들이 있기 때문에 DB명을 붙여줘야 합니다. 하지만 DB가 하나밖에 없다면 붙이지 않아도 작동합니다. IP 대신 골뱅이(@)를 붙여 DNS 또는 HOST 파일에 등록된 호스트네임 또는 URL주소로 대신 사용할 수 있습니다. 실제 DB의 IP가 바뀌는 경우들이 있을 수 있기 때문에 대부분의 연결작업은 IP로 직접하기보다는 간접적인 방식으로 설정해두고 DNS에서 IP 매핑을 시켜두는 경우가 많습니다. 

 

String url = "jdbc:oracle:thin:@localhost:1521:xe";
String uid = "admin";
String upw = "oracle";
connection = DriverManager.getConnection(url, uid, upw);

 

 

스레드풀과 같이 커넥션 객체 또한 커넥션풀을 생성해 객체 생성 및 삭제에 따른 오버헤드를 줄일 수 있습니다. 아래 글을 참조하시면 됩니다.

 

[· JSP & Servlet/- 기본 문법] - JDBC를 이용한 DB작업_커넥션풀(Connection Pool) [3/3]

 

 

또한 Connection 객체는 디폴트로 오토커밋이 활성화 돼있습니다. 이 경우 insert와 같은 쿼리를 입력하면 바로 커밋이 되기 때문에 문제 발생 시 롤백이 어렵게 됩니다. 이 때 오토커밋을 비활성화하고 직접 커밋해주는 방법을 사용할 수 있습니다. DB작업 중 문제가 생길 경우 롤백을 해줘야 하기 때문에, 오토커밋을 해제하고 수동으로 사용할 것을 권장합니다. 예시 코드는 아래와 같습니다.

 

Connection의 커밋 관련 메소드 설명
void setAutoCommit(boolean autoCommit) 매개변수를 false로 주면 오토커밋 해제
void commit() 커밋 수행
void rollback() 롤백 수행

 

				// 커밋하고 리턴
				preStmt.executeUpdate();
				conn.commit();
				return Ctrl.TRUE;
			}
		
		// DB접속 및 쿼리 과정에서 예외 발생 시 롤백 수행
		} catch (Exception e) {
			try {
				conn.rollback();
			} catch (Exception rollbackEx) {
				System.out.println("rollback Exception");
				rollbackEx.printStackTrace();
			}

 

 


 

 

3-1. Statement 객체 생성

  • java.sql.Statement 클래스
  • Connection 인스턴스 메소드로 생성
  • Connection 인스턴스.createStatement()

DB와의 커넥션이 생성되면 정보를 주고 받을 역할의 Connection 객체를 생성합니다. 이 객체를 통해 쿼리를 전달하고 결과값을 반환받습니다. DBMS에서 작업할 때는 쿼리 끝에 세미콜론(;)을 붙여주지만 Java에서 작성할 때 쿼리문에는 세미콜론을 붙이지 않습니다. 메소드에서 마지막 처리를 알아서 해줍니다. 커밋 또한 자동으로 해주기 때문에 굳이 신경쓸 필요는 없습니다.

 

Statement 주요 메소드 설명
ResultSet executeQuery(String sql) select를 통한 정보 조회 쿼리 전송 및 결과 객체 반환
int executeUpdate(String sql) Insert, Update, Delete 쿼리 전송 및 변경된 레코드 수 반환

 

 


 

 

3.2 - PreparedStatement 객체 생성

  • java.sql.PreparedStatement 클래스
  • Statement 객체와 동일하나, 쿼리를 좀 더 편리하고 정확하게 사용할 수 있도록 해줌

Statement에 비해서 쿼리문을 사용하는 방식을 조금 더 유연하게 작성할 수 있도록 해주는 클래스입니다. 만약 Statement 객체를 이용해 insert 쿼리를 날리게 되면 아래와 같이 작성해야 합니다. 콤마와 따옴표를 하나하나 지정해줘야 해서 잘 못 입력할 확률이 높습니다.

statement = connection.createStatement();
String user_id = "coconut";
String user_pw = "12345";
String insertQuery = "insert into ORG_USER (user_id, user_pw) "
					+ "values('" + user_id + "','" + user_pw + "')";
resultCount = statement.executeUpdate(insertQuery);

 

이 과정을 정형화 시켜둔 PreparedStatement 클래스입니다. 가시성도 좋아지고 실수할 가능성이 줄어들게 됩니다. 아래 코드와 같은 형태로 작성됩니다. 변경되는 데이터값을 물음표(?)로 설정해두고 set을 통해 넣어주는 방식입니다. 그러면 객체에서 자동으로 쿼리문을 완성시켜줍니다. 물음표 순서는 1부터 시작합니다.

String user_id = "coconut";
String user_pw = "12345";
String insertQuery = 
		"insert into ORG_USER (user_id, user_pw) values (?, ?)";
PreparedStatement preState = connection.prepareStatement(insertQuery);
preState.setString(1, user_id);
preState.setString(2, user_pw);
preState.executeUpdate();

 

PreparedStatement 주요 메소드 설명
ResultSet executeQuery() select를 통한 정보 조회 쿼리 전송 및 결과 객체 반환
int executeUpdate(String sql) Insert, Update, Delete 쿼리 전송 및 변경된 레코드 수 반환

 

 


 

 

4. ResultSet 객체 반환

  • java.sql.ResultSet 클래스
  • select 쿼리의 결과값을 모두 가지고 있는 객체

ResultSet 객체는 Iterator나 Enumeration 클래스와 비슷한 역할이고 사용법도 비슷합니다. DB의 내용을 처음부터 순회하면서 내용을 가져올 수 있습니다. 

 

ResultSet 주요 메소드 설명
boolean next() 읽어올 레코드(행)가 있으면 true, 없으면 false 반환
boolean previous() 이전 레코드로 이동 (가장 첫 행이면 false 반환)
boolean first() 처음 위치로 이동 (레코드가 없을 경우 false 반환)
boolean last() 마지막 위치로 이동 (레코드가 없을 경우 false 반환)
String getString(String columnLabel) (현재 커서가 읽을 위치의) 컬럼명에 해당하는 문자열 반환
int getInt(String columnLabel) 컬럼명에 해당하는 정수값 반환
String getString(columnIndex) 컬럼 인덱스에 해당하는 문자열 반환(1부터 시작)
int getInt(columnIndex) 컬럼 인덱스에 해당하는 정수값 반환(1부터 시작)
			statement = connection.createStatement();
			resultSet = statement.executeQuery(query);
			resultSet.first();
			while (resultSet.next()) {
				String id = resultSet.getString("user_id");
				String pw = resultSet.getString("user_pw");
				System.out.println(id + " : " + pw);
			}

 

 

 


 

 

[ 자원 해제 ]

 

Connection의 자원은 꼭 해제해줘야 합니다. 스트림과 마찬가지입니다. 커넥션풀을 사용하더라도 반납을 위해 스레드에서 자원 해제를 해줘야 합니다. 또한 데이터 작업을 위해 생성된 Statement와 ResultSet 객체 또한 자원을 해제해줘야 합니다.

 

ResultSet의 경우는 그냥 데이터를 받아서 자바 내부에 만든 객체 아닌가 생각할 수 있지만, 실제로는 모든 데이터를 받아와서 한번에 저장해두는게 아니라 일정 갯수만큼 버퍼로 가지고 와서 다 읽고 다시 가져오는 방식입니다. 실제로 버퍼로 데이터를 가지고 오는 것도 생성될 때가 아닌 next() 메소드를 수행할 때 시작됩니다. 따라서 ResultSet 객체 또한 DB와 계속 커넥션을 가지고 있는 자원이라고 생각하면 됩니다.

 

본래 커넥션이 해제될 때 나머지는 자동으로 해제되는게 정상이라고는 하는데 그 과정에서 예외가 발생할 경우 자동 해제가 되지 않아 문제가 발생하는 경우가 있다고 합니다. 따라서 명시적으로 자원을 해제해주는 것이 좋은 방법입니다. 해제 순서는 만든 순서의 역순입니다. ResultSet → Statement → Connection 순입니다.

 

finally {
	try {
		if (resultSet != null)
			resultSet.close();
		if (statement != null)
			statement.close();
		if (connection != null)
			connection.close();
	} catch (Exception ex) {
		ex.printStackTrace();
	}

 

 

위의 코드가 간단하긴 하지만 위와 같이 짜면 만약 try가 모두 끝나기 전에 예외가 발생하면 다른 객체의 자원해제가 되지 않을 수 있습니다. 따라서 번거롭더라도 아래와 같이 하나씩 따로 처리해주는게 좋습니다.

 

		finally {
			if (resultSet != null)
			try {				
				resultSet.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			if (statement != null)
			try {
				statement.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			
			if (connection != null)
			try {
				connction.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}

 

 


 

 

[ 전체 코드 예시 ]

 

package study1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {

	public static void main(String[] args) {

		Connection connection = null;
		Statement statement = null;
		ResultSet resultSet = null;
		
		String driver = "oracle.jdbc.driver.OracleDriver";
		String url = "jdbc:oracle:thin:@localhost:1521";
		String uid = "admin";
		String upw = "oracle";
		String query = "select * from ORG_USER";
		
		try {
			Class.forName(driver);
		
			connection = DriverManager.getConnection(url, uid, upw);
			statement = connection.createStatement();
			resultSet = statement.executeQuery(query);
			while (resultSet.next()) {
				String id = resultSet.getString("user_id");
				String pw = resultSet.getString("user_pw");
				System.out.println(id + " : " + pw);
			}

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

		} finally {
			if (resultSet != null)
			try {				
				resultSet.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			if (statement != null)
			try {
				statement.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
			
			if (connection != null)
			try {
				connection.close();
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}
}
728x90

댓글

💲 추천 글