본문 바로가기
Dev/Database

JDBC 프로그래밍

by vellahw 2022. 9. 6.

 

자바에서 데이터베이스를 사용할 때는 JDBC API를 이용해서 프로그래밍을 한다

JDBC는 Java DataBase Connectivity의 약자로,

자바에서 데이터베이스와 관련된 작업을 처리할 수 있도록 도와주는 API이다.

 

 

1. JDBC API를 사용하는 어플리케이션의 구조

JSP를 비롯한 자바 기반의 어플리케이션에서 데이터베이스를 사용할 때에는 데이터베이스 종류에 상관 없이 JDBC API를 이용해서 데이터베이스에 접근 하게 된다.

각각의 DBMS는 자신에게 알맞는 JDBC 드라이버를 제공하고 있으며 보통 jar 파일 형태로 제공한다.

 *오라클 sql developer 의 경우 아래와 같은 경로에서 찾을 수 있다.

ojdbc8.jar

 이 파일을 이클립스 웹 프로젝트 디렉터리의 src\main\webapp\WEB-INF\lib 디렉터리에 복사하여 JDBC를 사용한다.

 

 

2. JDBC 프로그래밍의 코딩 스타일

  1. JDBC 드라이버 로딩
  2. 데이터베이스 커넥션 구하기
  3. 쿼리 실행을 위한 Statement 객체 생성
  4. 쿼리 실행
  5. 쿼리 실행 결과 사용
  6. Statement 종료
  7. 데이터베이스 커넥션 종료

 

▼ 위 순서에 맞춰 테이블로부터 정보를 읽어와 출력해주는 예제

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<%@ page import="java.sql.DriverManager" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.Statement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.SQLException" %>
<html>
<head><title>회원 목록</title></head>
<body>

MEMBER 테이블의 내용
<table width="100%" border="1">
<tr>
	<td>이름</td>
	<td>아이디</td>
	<td>이메일</td>
</tr>
<%
	//1. JDBC 드라이버 로딩
	Class.forName("oracle.jdbc.driver.OracleDriver"); //오라클 드라이버

	Connection conn = null;
	Statement stmt = null;
	ResultSet rs = null;
	
	try {
		String jdbcDriver = "jdbc:oracle:thin:@localhost:1521:XE"; //JDBC URL
		String dbUser = "codingco";	//sql developer에서 생성한 계정
		String dbPass = "oracle";	///sql developer에서 생성한 계정의 비밀번호

		String query = "select * from MEMBER order by MEMBERID";  //order by: 오름차순 정렬

	//2. 데이터베이스 커넥션 생성
	conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
	
	//3. Statement 생성
	stmt = conn.createStatement();
	
	//4. 쿼리 실행
	rs = stmt.executeQuery(query);
	
	//5. 쿼리 실행 결과 출력
	while(rs.next()) {
%>
<tr>
	<td><%= rs.getString("NAME") %></td>
	<td><%= rs.getString("MEMBERID") %></td>
	<td><%= rs.getString("EMAIL") %></td>
</tr>
<%
		}
	} catch(SQLException ex) {
		out.println(ex.getMessage());
		ex.printStackTrace();
	} finally {
		//6. 사용한 Statement 종료
		if (rs != null) try { rs.close(); } catch(SQLException ex) {}
		if (stmt != null) try { stmt.close(); } catch(SQLException ex) {}

		//7. 커넥션 종료
		if (conn != null) try { conn.close(); } catch(SQLException ex) {}
	}
%>
</table>
</body>
</html>

▲viewMemberList.jsp

jsp 파일 실행 결과와 실제 sql developer에 생성된 table 데이터

 

<코드 분석>

▲ 데이터 처리에 필요한 클래스를 import 함

 

▲ JDBC 드라이버를 로딩 해야 데이터베이스에 연결해서 원하는 작업을 수행할 수 있다. (코드는 오라클 기준)

 

▲ JDBC URL는 DBMS와의 연결을 위한 식별 값으로 JDBC 드라이버에 따라 형식이 다름 (코드는 오라클 기준)

 

▲ DriverManager를 이용해서 Connection을 생성한다.

데이터베이스 프로그래밍을 하기 위해선 데이터베이스와 연결된 커넥션을 구해야하는데

import한 java.sql.Connection 클래스가 데이터베이스 커넥션을 나타내고,

import한 java.sql.DriverManager 클래스가 제공하는 getConnection() 메소드를 사용해서 커넥션을 구할 수 있다.

  • DriverManager.getConnection(String jdbcURL)
  • DriverManager.getConnection(String jdbcURL, String user, String password)

 

DriverManager.getConnection() 메소드는 Connection 객체를 생성하지 못하면 SQLException 예외를 발생 시키기 때문에 try-catch 블록을 사용해 예외 처리를 해주어야 한다.

Connection conn = null;
try {
    String jdbcDriver = “JDBC URL";
    String dbUser = “계정 이름";
    String dbPass = “계정 비밀번호";
    
    conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
    ...
} catch(SQLException ex) {
    // 에러 발생
} finally {
    if (conn != null) try { conn.close(); } catch(SQLException ex) {}
}

Connection 객체를 다 사용한 뒤에는 close() 메소드를 호출하여 반환 해주어야 한다.

 

Connection 객체를 생성한 후에는 Connection으로부터 Statement를 생성한 뒤 쿼리를 실행 할 수 있다.

 

<Statement가 제공하는 메소드>

  • ResultSet executeQuery(String query): SELECT 쿼리를 실행
  • int executeUpdate(String query): INSERT, UPDATE, DELETE 쿼리를 실행

 

ResultSet executeQuery() 메소드는 SELECT 쿼리의 결과값을 import한 java.sql.ResultSet 객체에 저장해서 리턴함

executeUpdate() 메소드는 INSERT, UPDATE, DELETE 쿼리를 실행해서 그 결과로 변경된 레코드의 개수를 리턴함

 

* Statement를 사용해서 값을 변경하는 예제

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<html>
<head><title>이름 변경 폼</title></head>
<body>

<form action="update.jsp" method="post">
<table border="1">
<tr>
	<td>아이디</td>
	<td><input type="text" name="memberID" size="10"></td>
	<td>이름</td>
	<td><input type="text" name="name" size="10"></td>
</tr>
<tr>
	<td colspan="4"><input type="submit" value="변경"></td>
</tr>
</table>
</form>
</body>
</html>

▲ updateForm.jsp

실행 결과

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<%@ page import="java.sql.DriverManager" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.Statement" %>
<%@ page import="java.sql.SQLException" %>

<%
	request.setCharacterEncoding("euc-kr");

	String memberID = request.getParameter("memberID");
	String name = request.getParameter("name");
	
	int updateCount = 0;
	
	Class.forName("oracle.jdbc.driver.OracleDriver");

	Connection conn = null;
	Statement stmt = null;
	
	try{
		String jdbcDriver = "jdbc:oracle:thin:@localhost:1521:XE";
		String dbUser = "codingco";
		String dbPass = "oracle";
        
		//MEMBER 테이블의 name 칼럼을 변경하는 쿼리
		String query = "update MEMBER set NAME = '"+name+"' " + "where MEMBERID = '"+memberID+"'";
        
		conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
		stmt = conn.createStatement();  //Statement 생성
		updateCount = stmt.executeUpdate(query); //메소드 실행 결과로 변경된 레코드의 개수 저장
	} finally {
		if(stmt != null) try { stmt.close(); } catch(SQLException ex) {}
		if(conn != null) try { conn.close(); } catch(SQLException ex) {}
	}
%>
<html>
<head><title>이름 변경</title></head>
<body>
<% if(updateCount > 0) { %>  //uddateCount가 0보다 크면 변경된 값이 존재한다고 간주함
<%= memberID %>의 이름을 <%= name %>(으)로 변경
<% } else { %>
<%= memberID %> 아이디가 존재하지 않음
<% } %>
</body>
</html>

▲ update.jsp

updateFor.jsp

▲ 이름을 변경할 아이디와 변경할 이름을 입력

[변경] 버튼을 누른 후 update.jsp 실행 결과
viewMemberList.jsp 재실행 결과

기존엔 아이디 SSL 이름이 '홍길동형'이었음

 

* ResultSet에서 값 읽어오기

ResultSet 클래스는 next() 메서드를 제공하는데, 이 메소드를 사용해서 SELECT 결과의 존재 여부를 확인할 수 있다.

ResultSet은 SELECT 쿼리의 결과를 위 그림과 같이 행으로 저장하며 '커서'를 통해서 각 행의 데이터에  접근한다.

최초의 커서는 1행에 존재하며 커서의 다음 행이 존재할 경우 true를 리턴하여 커서를 그 행으로 이동시킨다.

커서가 마지막 행에 도달하면 next() 메소드는 false를 리턴한다.

ResultSet 클래스의 주요 데이터 읽기 메소드로는 getString(), getTimestamp(), getDate(), getTime() 등이 있다.

 

1개의 행을 처리할 땐 보통 다음과 같이 if-else 구문을 사용하고

rs = stmt.executeQuery("select * from member");
if (rs.next()) {  // 다음 행(첫 번째 행)이 존재하면 rs.next()는 true를 리턴
    // rs.next()에 의해 다음 행(첫 번째 행)으로 이동
    String name = rs.getString("NAME");
} else {
    // 첫 번째 행이 존재하지 않는다. 즉, 결과가 없다.
}

1개 이상의 행을 처리할 때에는 while 구문이나  do-while 구문을 사용한다.

rs = stmt.executeQuery(...);
if (rs.next()) {
    do {
        String name = rs.getString("NAME");
        ...
    } while( rs.next() );
}

 

▼ 파라미터로 아이디를 전달 받으면 MEMBER 테이블로부터 해당 회원 정보를 읽어와 출력 해주는 JSP 페이지

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<%@ page import="java.sql.DriverManager" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.Statement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.SQLException" %>    

<%
	String memberID = request.getParameter("memberID");
%>
<html>
<head><title>회원 정보</title></head>
<body>
<%
	Class.forName("oracle.jdbc.driver.OracleDriver");

	Connection conn = null;
	Statement stmt = null; 
	ResultSet rs = null;
	
	try{
		String jdbcDriver = "jdbc:oracle:thin:@localhost:1521:XE";
		String dbUser = "codingco";
		String dbPass = "oracle";

		String query = "select * from MEMBER where MEMBERID = '"+memberID+"'";
		
		conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
		stmt = conn.createStatement();
		
		rs = stmt.executeQuery(query);
		if(rs.next()) {
%>
<table border="1">
<tr>
	<td>아이디</td><td><%= memberID %></td>
</tr>
<tr>
	<td>암호</td><td><%= rs.getString("PASSWORD") %></td>
</tr>
<tr>
	<td>이름</td><td><%= rs.getString("NAME") %></td>
</tr>
<tr>
	<td>이메일</td><td><%= rs.getString("EMAIL") %></td>
</tr>
</table>
<%
	} else {
%>
<%= memberID %>에 해당하는 정보가 존재하지 않습니다.
<%
		}
	} catch(SQLException ex) {
%>
에러 발생: <%= ex.getMessage() %>
<%
	} finally {
		if (rs != null) try { rs.close(); } catch(SQLException ex) {}
		if (stmt != null) try { stmt.close(); } catch(SQLException ex) {}
		if (conn != null) try { conn.close(); } catch(SQLException ex) {}
	}
%>
</body>
</html>

▲viewMemberList.jsp

URL 뒤에 위 사진 속 처럼 입력한 후 viewMemberList.jsp를 실행한 결과

MEMBER 테이블에 MEMBERID 칼럼값이 입력한 아이디와 일치하는 레코드가 존재한다면 사진과 같은 결과가 출력된다.

입력한 아이디와 일치하는 레코드가 존재하지 않을 때의 실행 결과

 

 

*ResultSet에서 Long 타입 값 읽어오기

LONG 타입은 대량의 텍스트 데이터를 저장할 때 사용되며 getCharacterStream() 메소드를 사용해야 한다.

ResultSet.getCharacterStream() 메소드의 리턴 타입은 java.io.Reader이다.

 

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<%@ page import="java.sql.DriverManager" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.Statement" %>
<%@ page import="java.sql.ResultSet" %>
<%@ page import="java.sql.SQLException" %>
<%@ page import="java.io.Reader" %>
<%@ page import="java.io.IOException" %>

<%
	String memberID = request.getParameter("memberID");
%>
<html>
<head><title>회원 정보</title></head>
<body>
<%
	Class.forName("oracle.jdbc.driver.OracleDriver");
	Connection conn = null;
	Statement stmt = null; 
	ResultSet rs = null;
	try {
		String jdbcDriver = "jdbc:oracle:thin:@localhost:1521:XE"; 
		String dbUser = "codingco";
		String dbPass = "oracle";
		String query = "select * from MEMBER_HISTORY" + " where MEMBERID = '"+memberID+"'";
        
		conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
		stmt = conn.createStatement();
		rs = stmt.executeQuery(query);
        
		if(rs.next()) {
%>
<table border="1">
<tr>
	<td>아이디</td><td><%= memberID %></td>
</tr>
<tr>
	<td>히스토리</td>
	<td>
<%
	String history = null;
	Reader reader = null;
	try {
		reader = rs.getCharacterStream("HISTORY"); //HISTORY 칼럼을 읽어옴
		if(reader != null) {
			StringBuffer buff = new StringBuffer();
			char[] ch = new char[512];
			int len = -1;
			while((len = reader.read(ch)) != -1) {	//reader로부터 데이터를 읽어와 저장
				buff.append(ch, 0, len);
			}
			history = buff.toString();	//버퍼에 저장된 내용을 String으로 변환
		}
	} catch(IOException ex) {
		out.println("예외 발생:" + ex.getMessage());
	} finally {
		if(reader != null) try { reader.close(); } catch(IOException ex) {}
	}
%>
	<%= history %>
	</td>
</tr>
</table>
<%
	} else {
%> 
<%= memberID %> 회원의 히스토리가 없습니다.
<%
		}
	} catch(SQLException ex) {
%>
에러 발생: <%= ex.getMessage() %>
<%
	} finally {
		if (rs != null) try { rs.close(); } catch(SQLException ex) {}
		if (stmt != null) try { stmt.close(); } catch(SQLException ex) {}
		if (conn != null) try { conn.close(); } catch(SQLException ex) {}
	}
%>
</body>
</html>

 

예제를 위해 생성한 MEMBER_HISTORY 테이블
테이블에 입력된 데이터

URL에 위처럼 직접 memberID 파라미터의 값을 붙여서 실행해주면 MEMBER_HISTORY 테이블에 입력한 데이터가 출력 된다.

 

 

3. PreparedStatement를 사용한 쿼리 실행

PreparedStatement는 SQL 쿼리의 틀을 미리 정해놓고 나중에 값을 지정하는 방식이다.

1) PreparedStatement의 일반적 사용

pstmt = conn.prepareStatement(
   "insert into MEMBER (MEMBERID, NAME, EMAIL) values (?, ?, ?)");
pstmt.setString(1, "codingco"); // 첫번째 물음표의 값 지정
pstmt.setString(2, "코딩코");   // 두번째 물음표의 값 지정
pstmt.executeUpdate();

이때 첫 번째 물음표의 인덱스는 1이며 이후 물음표의 인덱스는 나오는 순서대로 1씩 증가한다.

(MEMBERID = codingco, NAME = 코딩코)

 

PreparedStatement는 다음과 같은 이유로 사용한다.

  • 반복해서 실행되는 동일 쿼리의 속도를 향상 시킴
  • 값 변환을 처리함 (작은 따옴표 등 값에 포함된 특수 문자의 처리)
  • 코드의 간결함 (문자열 연결에 따른 코드의 복잡함을 감소 시킨다)

 

PreparedStatement를 사용하면 위처럼 복잡하고 번거롭게 작은 따옴표 처리를 하지 않아도 된다는 장점이 크다. 

 

2) 쿼리 실행 관련 메서드

  • ResultSet executeQuery(): SELECT 쿼리를 실행할 때 사용되며 ResultSet을 결과값으로 리턴한다.
  • int executeUpdate(): INSERT, UPDATE, DELETE 쿼리를 실행할 때 사용되며, 실행 결과 변경된 레코드의 개수를 리턴한다.
  •  PreparedStatement를 생성할 때 실행할 쿼리를 지정하기 때문에 두 메소드는 쿼리를 인자로 입력 받지 않는다.

 

3) PreparedStatement 클래스가 제공하는 set 메서드

대표적으로 setString(int index, String x), setInt(int index, x), setTimestamp(int index, Timestamp x) 등이 있다.

 

4) PreparedStatement 클래스를 사용하여 MEMBER 테이블에 값 삽입하기

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<html>
<head><title>MEMBER 테이블 레코드 삽입</title></head>
<body>
<form action="insert.jsp" method="post">
<table border="1">
<tr>
	<td>아이디</td>
	<td><input type="text" name="memberID" size="10"></td>
	<td>암호</td>
	<td><input type="text" name="password" size="10"></td>
</tr>
<tr>
	<td>이름</td>
	<td><input type="text" name="name" size="10"></td>
	<td>이메일</td>
	<td><input type="text" name="email" size="10"></td>
</tr>
<tr>
<td colspan="4"><input type="submit" value="삽입"></td>
</tr>
</table>
</form>
</body>
</html>

▲데이터 폼 작성 (insertForm.jsp)

실행 결과

 

▼ 삽입 버튼을 누르면 데이터가 전송 되는 insert.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR" %>
<%@ page import="java.sql.DriverManager" %>
<%@ page import="java.sql.Connection" %>
<%@ page import="java.sql.PreparedStatement" %>
<%@ page import="java.sql.SQLException" %>

<%
	request.setCharacterEncoding("euc-kr");

	String memberID = request.getParameter("memberID");
	String password = request.getParameter("password");
	String name = request.getParameter("name");
	String email = request.getParameter("email");

	Class.forName("oracle.jdbc.driver.OracleDriver");
	Connection conn = null;
	PreparedStatement pstmt = null;
	try{
		String jdbcDriver = "jdbc:oracle:thin:@localhost:1521:XE";
		String dbUser = "codingco";
		String dbPass = "oracle";

		conn = DriverManager.getConnection(jdbcDriver, dbUser, dbPass);
        
        	//실행할 쿼리 생성 및 각 물음표에 값 지정
		pstmt = conn.prepareStatement("insert into MEMBER values (?, ?, ?, ?)");
		pstmt.setString(1, memberID);
		pstmt.setString(2, password);
		pstmt.setString(3, name);
		pstmt.setString(4, email);
	
		pstmt.executeUpdate();
	} finally {
		if(pstmt != null) try { pstmt.close(); } catch(SQLException ex) {}
		if(conn != null) try { conn.close(); } catch(SQLException ex) {}
	}
%>
<html>
<head><title>삽입</title></head>
<body>
MEMBER 테이블에 새로운 레코드를 삽입 했습니다.
</body>
</html>

폼에 데이터 입력
삽입 버튼을 누른 결과
viewMemberList.jsp 실행 결과: '코딩타비' 가 추가된 것을 볼 수 있음

 

 

 

댓글