Java프로그래밍 14강 - JDBC 프로그래밍
이번 강의에서는 관계형 데이터베이스의 기본 구조와 MariaDB 준비 과정을 살펴보고, JDBC API로 Java 프로그램과 데이터베이스 서버를 연결하는 방법을 학습한다. Connection, Statement, ResultSet의 역할과 메타데이터 및 PreparedStatement 활용법을 코드 흐름에 따라 정리한다.
제1장 데이터베이스와 MariaDB 기초
1. 데이터베이스와 DBMS
데이터베이스는 서로 관련된 데이터를 체계적으로 모아 둔 집합 장소이다. 데이터베이스 관리 시스템인 DBMS는 이러한 데이터를 효율적으로 저장, 검색, 변경, 삭제하도록 지원하는 응용 소프트웨어이다. 관계형 DBMS에서는 데이터를 테이블 형태로 저장한다.
하나의 DBMS는 여러 데이터베이스로 구성될 수 있고, 하나의 데이터베이스는 여러 테이블을 가진다. 테이블은 여러 레코드로 이루어지며, 각 레코드는 여러 필드로 구성된다. 각 필드에는 정해진 자료형에 맞는 데이터가 저장되고, 레코드를 고유하게 식별하기 위한 키 필드가 존재한다.
구조: DBMS → 데이터베이스 → 테이블 → 레코드(행) → 필드(열). 시험에서는 행과 열의 의미, 키 필드의 역할을 정확히 구분해야 한다.
2. 관계형 데이터베이스와 스키마
테이블은 관계형 데이터베이스의 기본 자료 구조이며 고유한 이름을 갖는다. 행은 레코드 또는 엔터티라고 하며 한 대상의 데이터를 나타낸다. 열은 속성 또는 필드라고 하며 모든 행에서 같은 종류의 값을 담는다.
스키마는 테이블의 이름과 구조를 정의한 정보이다. 각 필드의 이름, 데이터 타입, 길이 같은 속성을 명시한다. 예를 들어 게시글 테이블은 ID를 INT, 제목을 VARCHAR(100), 작성일자를 DATE로 정의할 수 있다.
| 구성 요소 | 의미 | 예 |
|---|---|---|
| 테이블 | 같은 구조의 데이터를 행과 열로 저장 | 게시글 테이블 |
| 레코드 | 한 개체의 데이터 묶음 | 게시글 한 건 |
| 필드 | 레코드를 구성하는 속성 | ID, 제목, 작성자 |
| 스키마 | 테이블과 필드의 구조 정보 | 필드명, 자료형, 길이 |
3. MariaDB와 샘플 데이터베이스 준비
강의에서는 관계형 DBMS로 MariaDB를 사용한다. 서버 설치 과정에서 root 계정의 암호를 정해 기억해야 하며 기본 포트는 3306이다. 설치 후에는 서비스 관리 도구에서 MariaDB 서버가 실행 중인지 확인하고, MySQL Client 또는 명령 프롬프트에서 서버에 접속한다.
mysql -u root -p
실습에는 MySQL 문서에서 제공하는 world 샘플 데이터베이스를 사용한다. 압축 파일의 world.sql을 내려받아 클라이언트에서 실행한 뒤 데이터베이스와 테이블이 설치되었는지 확인한다.
source c:/db/world.sql
show databases;
use world;
show tables;
제2장 JDBC와 MariaDB 연결 준비
1. JDBC API
JDBC는 Java DataBase Connectivity의 약자로, Java 프로그램이 관계형 DBMS와 연동하도록 제공되는 표준 API이다. 프로그램은 JDBC를 이용해 데이터베이스에 연결하고 SQL 질의, 갱신, 삽입, 삭제를 요청하며 그 결과를 받을 수 있다. JDBC API는 JDK의 java.sql 패키지에 포함된다.
JDBC는 공통 프로그래밍 인터페이스를 제공하지만 실제 DBMS와 통신하는 구체적인 코드는 각 DBMS 제작사가 제공하는 JDBC 드라이버가 담당한다. 따라서 사용할 DBMS에 맞는 드라이버를 프로젝트에 포함해야 한다.
2. MariaDB JDBC 드라이버
MariaDB JDBC 드라이버는 MariaDB용 JDBC API를 구현한 소프트웨어이며 .jar 파일 형식으로 제공된다. 강의에서는 Java 8 이상용 MariaDB Connector/J 파일을 내려받아 프로젝트의 Classpath에 추가한다.
Eclipse에서는 프로젝트 속성의 Java Build Path에서 Libraries를 선택하고 Classpath에 외부 JAR 파일을 추가한다. 실습 프로젝트는 일반 Java 프로젝트로 만들며 강의 지침에서는 module-info.java를 만들지 않는다.
연결 전 점검: MariaDB 서버 실행, 접속 계정과 암호, 3306 포트, world 데이터베이스 설치, 드라이버 JAR의 Classpath 포함 여부를 차례로 확인한다.
3. JDBC URL과 연결 코드
JDBC URL은 연결할 DBMS 종류, 서버 위치, 포트, 데이터베이스 이름을 표현한다. 강의의 MariaDB 연결 문자열은 로컬 서버의 3306 포트와 world 데이터베이스를 가리킨다.
String url = "jdbc:mariadb://localhost:3306/world";
String user = "root";
String pass = "mariadb";
Connection conn = DriverManager.getConnection(url, user, pass);
DriverManager.getConnection()은 URL, 사용자 아이디, 비밀번호로 서버 접속을 시도하고 성공하면 Connection 객체를 반환한다. 이 객체는 데이터베이스 연결 자체를 나타내며 SQL 실행 객체를 만드는 출발점이 된다.
제3장 JDBC 연동 프로그램의 처리 순서
1. 전체 순서
import java.sql.*;로 JDBC 패키지를 사용한다.- 필요하면
Class.forName("org.mariadb.jdbc.Driver")로 드라이버를 동적으로 로드한다. DriverManager.getConnection()으로 MariaDB 서버와 연결한다.Connection에서Statement또는PreparedStatement를 만든다.- SQL을 실행하고 조회 결과는
ResultSet으로 처리한다. - 사용한 결과 집합, 실행 객체, 연결 객체를 닫아 자원을 반환한다.
최신 JDBC 환경에서는 드라이버가 자동으로 인식되므로 Class.forName()을 생략할 수 있다. 그러나 JDBC의 구성 원리를 이해하기 위해 드라이버 로드 단계를 함께 알아 두는 것이 좋다.
2. 자원 관리와 try-with-resources
데이터베이스 연결과 SQL 실행 객체는 외부 자원을 사용하므로 작업이 끝나면 반드시 닫아야 한다. 전통적으로 finally 블록에서 rs.close(), stmt.close(), conn.close()를 호출하지만, try-with-resources를 사용하면 블록 종료 시 자동으로 닫힌다.
try (Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM city LIMIT 0, 50")) {
while (rs.next()) {
// 현재 행의 필드값 처리
}
}
객체 흐름: DriverManager → Connection → Statement → ResultSet. 앞 단계의 객체를 이용해 다음 단계의 객체를 만든다.
제4장 Statement로 SQL 실행하기
1. Statement 객체
Statement는 SQL 구문을 실행하고 결과를 반환하는 객체이다. Connection.createStatement()로 생성한다. 실행할 SQL의 종류에 따라 execute(), executeQuery(), executeUpdate()를 선택한다.
| 메소드 | 용도와 반환값 |
|---|---|
boolean execute(String sql) | SQL을 실행한다. 첫 결과가 ResultSet이면 true, 갱신 개수이거나 결과가 없으면 false이다. |
ResultSet getResultSet() | execute()로 생성된 조회 결과를 반환한다. |
int getUpdateCount() | SQL 실행으로 영향을 받은 행의 개수를 반환한다. |
ResultSet executeQuery(String sql) | SELECT를 실행하고 조회 결과를 반환한다. |
int executeUpdate(String sql) | INSERT, UPDATE, DELETE를 실행하고 영향받은 행 수를 반환한다. |
2. SELECT 실행
조회 결과가 필요할 때는 executeQuery()가 가장 직접적이다. 반환된 ResultSet에서 커서를 이동하며 각 행의 값을 읽는다. execute()를 사용할 때는 반환값이 true인지 확인한 후 getResultSet()으로 결과를 얻는다.
try (Connection conn = DriverManager.getConnection(url, user, pass);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT * FROM city LIMIT 0, 50")) {
while (rs.next()) {
System.out.print(rs.getInt("ID") + " ");
System.out.println(rs.getString("Name"));
}
}
3. INSERT, UPDATE, DELETE 실행
데이터를 삽입, 수정, 삭제하는 SQL은 일반적으로 executeUpdate()를 사용한다. 반환되는 정수는 작업의 영향을 받은 행 수이므로 처리 결과를 확인하는 데 쓸 수 있다.
int count = stmt.executeUpdate(
"INSERT INTO 테이블이름 VALUES (...) ");
System.out.println(count + "개의 행이 삽입되었습니다.");
count = stmt.executeUpdate(
"UPDATE 테이블이름 SET 필드=값 WHERE ...");
count = stmt.executeUpdate(
"DELETE FROM 테이블이름 WHERE ...");
execute()로 데이터 변경 구문을 실행하면 반환값은 보통 false이고, getUpdateCount()로 영향받은 행 수를 확인할 수 있다.
제5장 ResultSet과 커서
1. ResultSet의 의미
ResultSet은 SELECT 실행 결과를 테이블 형태로 나타내는 객체이다. 결과 테이블에는 현재 행을 가리키는 커서가 있으며, 객체가 처음 생성되었을 때 커서는 첫 행의 바로 앞에 위치한다. 따라서 값을 읽기 전에 next()를 호출해 첫 행으로 이동해야 한다.
커서는 한 행을 가리키며 각 열의 값은 열 순번이나 열 이름으로 읽는다. JDBC 열 인덱스는 0이 아니라 1부터 시작한다.
2. 주요 메소드
| 메소드 | 기능 |
|---|---|
boolean next() | 커서를 다음 행으로 이동하고 유효한 행이면 true를 반환한다. |
boolean previous() | 커서를 이전 행으로 이동한다. |
Statement getStatement() | 현재 결과 집합을 만든 Statement를 반환한다. |
String getString(int index) | 지정한 열의 값을 문자열로 반환한다. |
String getString(String columnName) | 열 이름으로 문자열 값을 읽는다. |
int getInt(int index) | 지정한 열의 값을 정수로 반환한다. |
int getInt(String columnName) | 열 이름으로 정수 값을 읽는다. |
while (rs.next()) {
int id = rs.getInt("ID");
String name = rs.getString("Name");
int population = rs.getInt("Population");
}
주의: ResultSet의 커서가 유효한 행을 가리킬 때만 열 값을 읽을 수 있다. next()가 false이면 더 이상 처리할 행이 없다.
제6장 DatabaseMetaData와 자료형 변환
1. DatabaseMetaData
DatabaseMetaData는 JDBC 드라이버와 연결된 데이터베이스의 정보를 제공하는 인터페이스이다. Connection.getMetaData()로 객체를 얻으며 드라이버 이름, 드라이버 버전, 연결 URL, 사용자 이름 등을 확인할 수 있다.
| 메소드 | 반환 정보 |
|---|---|
getDriverName() | JDBC 드라이버 이름 |
getDriverVersion() | JDBC 드라이버 버전 |
getURL() | 연결에 사용된 DBMS URL |
getUserName() | 연결에 사용된 데이터베이스 사용자 |
DatabaseMetaData meta = conn.getMetaData();
System.out.println(meta.getDriverName());
System.out.println(meta.getDriverVersion());
System.out.println(meta.getURL());
System.out.println(meta.getUserName());
2. DBMS와 Java의 자료형 대응
DBMS 필드 자료형과 Java 자료형 사이에는 대응 관계가 있다. 값을 읽을 때는 ResultSet의 알맞은 getter를, 매개변수 값을 지정할 때는 PreparedStatement의 알맞은 setter를 사용한다.
| DBMS 자료형 | Java 자료형 | ResultSet | PreparedStatement |
|---|---|---|---|
CHAR | String | getString() | setString() |
VARCHAR | String | getString() | setString() |
INTEGER | int | getInt() | setInt() |
DATE | java.sql.Date | getDate() | setDate() |
제7장 PreparedStatement
1. 미리 컴파일된 SQL
PreparedStatement는 미리 컴파일된 SQL 구문을 표현한다. Connection.prepareStatement(String sql)에 SQL을 전달해 생성하며, Statement와 달리 객체 생성 시 SQL 문장이 정해진다. 같은 구조의 SQL을 값만 바꾸어 여러 번 실행할 때 효율적이다.
SQL 안에 물음표 ?로 매개변수를 두고 실행 전에 값을 지정할 수 있다. 첫 번째 물음표의 인덱스는 1이다. 문자열은 setString(), 정수는 setInt()처럼 자료형에 맞는 setter를 사용한다.
String query =
"SELECT * FROM 테이블이름 WHERE 필드1=? AND 필드2>?";
PreparedStatement ps = conn.prepareStatement(query);
ps.setString(1, "필드값");
ps.setInt(2, 숫자);
ResultSet rs = ps.executeQuery();
2. 주요 메소드
| 메소드 | 기능 |
|---|---|
boolean execute() | 객체가 가진 SQL을 실행한다. |
ResultSet executeQuery() | 조회 SQL을 실행하고 결과 집합을 반환한다. |
int executeUpdate() | 삽입·수정·삭제 SQL을 실행하고 영향받은 행 수를 반환한다. |
void setInt(int parameterIndex, int x) | 지정한 매개변수 위치에 정숫값을 넣는다. |
void setString(int parameterIndex, String x) | 지정한 매개변수 위치에 문자열 값을 넣는다. |
3. 도시 인구 조회 예
String query = "SELECT * FROM city WHERE population > ?";
try (Connection conn = DriverManager.getConnection(url, user, pass);
PreparedStatement ps = conn.prepareStatement(query)) {
ps.setInt(1, 9000000);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("Name") + " "
+ rs.getInt("Population"));
}
}
}
SQL 구조는 유지하면서 인구 기준값만 바꾸어 반복 실행할 수 있다. PreparedStatement에서는 SQL이 이미 객체 안에 있으므로 executeQuery()에 다시 SQL 문자열을 인자로 전달하지 않는다.
제8장 ResultSetMetaData
1. 결과 테이블의 구조 정보
ResultSetMetaData는 ResultSet 결과 테이블의 이름, 열 이름, 열 개수, 열 자료형 같은 구조 정보를 얻는 객체이다. ResultSet.getMetaData()로 생성한다. 조회 결과의 구조가 실행 시점에 정해질 때 열을 동적으로 처리하는 데 유용하다.
| 메소드 | 기능 |
|---|---|
String getColumnName(int index) | 지정한 위치의 열 이름을 반환한다. |
int getColumnCount() | 결과 집합의 열 개수를 반환한다. |
int getColumnType(int index) | 지정한 열의 SQL 자료형 코드를 반환한다. |
String getTableName(int index) | 지정한 열이 속한 테이블 이름을 반환한다. |
2. 자료형에 따라 값 읽기
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
for (int i = 1; i <= colCount; i++) {
System.out.print(rsmd.getColumnName(i) + ", ");
}
while (rs.next()) {
for (int i = 1; i <= colCount; i++) {
switch (rsmd.getColumnType(i)) {
case Types.INTEGER:
System.out.print(rs.getInt(i) + ", ");
break;
case Types.CHAR:
case Types.VARCHAR:
System.out.print(rs.getString(i) + ", ");
break;
}
}
System.out.println();
}
열 인덱스는 1부터 시작하므로 모든 열을 처리하려면 조건을 i <= colCount로 작성한다. getColumnType()이 반환하는 값은 Types.INTEGER, Types.CHAR 같은 JDBC SQL 자료형 상수와 비교할 수 있다.
메타데이터 구분: DatabaseMetaData는 연결된 DBMS와 드라이버 정보이고, ResultSetMetaData는 특정 조회 결과의 열 구조 정보이다.
핵심 개념 정리
- 관계형 DBMS는 데이터를 테이블의 행과 열로 저장하며, 스키마는 테이블의 구조와 필드 속성을 정의한다.
- JDBC는 Java와 관계형 DBMS를 연결하는 표준 API이고, 실제 통신에는 DBMS별 JDBC 드라이버가 필요하다.
DriverManager.getConnection()은Connection을,Connection.createStatement()는Statement를 만든다.executeQuery()는 조회 결과인ResultSet을,executeUpdate()는 변경된 행 수를 반환한다.ResultSet커서는 처음에 첫 행 앞에 있으므로next()로 이동한 뒤 값을 읽는다.DatabaseMetaData는 DBMS와 드라이버 정보,ResultSetMetaData는 결과 테이블의 열 정보를 제공한다.PreparedStatement는 미리 컴파일된 SQL과?매개변수를 이용해 같은 SQL을 값만 바꾸어 실행한다.- 데이터베이스 자원은 try-with-resources로 관리하면 블록 종료 시 자동으로 반환된다.
최종 정리: JDBC 프로그램은 연결, SQL 실행 객체 생성, SQL 실행, 결과 처리, 자원 반환의 순서로 작성한다. 단순 SQL에는 Statement, 값이 바뀌는 반복 SQL에는 PreparedStatement를 사용하고, 조회 결과는 커서와 자료형에 맞는 getter로 읽는다. 연결 정보와 결과 구조가 필요할 때는 각각 DatabaseMetaData와 ResultSetMetaData를 구분해 활용한다.
예상문제 20선
1. 관계형 데이터베이스에서 한 개체의 데이터 묶음을 뜻하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④
레코드는 테이블의 한 행으로, 한 개체를 설명하는 여러 필드값의 묶음이다.
2. 테이블의 이름, 필드 이름, 데이터 타입과 길이 같은 구조 정보를 뜻하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②
스키마는 데이터베이스와 테이블을 구성하는 구조와 속성을 정의한 정보이다.
3. JDBC API가 포함된 Java 패키지는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
JDBC의 핵심 인터페이스와 클래스는 JDK의 java.sql 패키지에 포함된다.
4. MariaDB JDBC 드라이버의 역할로 가장 알맞은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
DBMS 제작사가 제공하는 JDBC 드라이버가 표준 API 호출을 실제 DBMS 통신으로 연결한다.
5. DriverManager.getConnection(url, user, pass)가 성공했을 때 반환하는 객체는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②getConnection()은 데이터베이스 서버와의 연결을 나타내는 Connection 객체를 반환한다.
6. JDBC 객체 생성 흐름으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④DriverManager가 연결을 만들고, 연결에서 실행 객체를 만들며, 조회 실행 결과로 결과 집합을 얻는다.
7. try-with-resources를 JDBC 코드에 사용하는 주된 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
try-with-resources는 정상 종료나 예외 발생 여부와 관계없이 등록된 자원을 자동으로 닫는다.
8. SELECT 문을 실행하고 ResultSet을 직접 반환받는 Statement 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①executeQuery()는 조회 SQL을 실행하고 결과 테이블을 나타내는 ResultSet을 반환한다.
9. executeUpdate()의 반환값이 의미하는 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④executeUpdate()는 삽입, 수정, 삭제로 영향을 받은 행의 개수를 정수로 반환한다.
10. ResultSet이 처음 생성되었을 때 커서의 위치는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
초기 커서는 첫 행 직전에 있으므로 첫 데이터를 읽기 전에 next()를 호출해야 한다.
11. ResultSet에서 while (rs.next())를 사용하는 목적은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②next()는 커서를 이동하고 읽을 행이 있으면 true를 반환하므로 전체 행 반복에 사용한다.
12. JDBC에서 열의 순번으로 값을 읽을 때 첫 번째 열의 인덱스는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③
JDBC의 열 인덱스와 PreparedStatement 매개변수 인덱스는 모두 1부터 시작한다.
13. JDBC 드라이버 이름, 버전, 연결 URL을 알아내는 객체는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①DatabaseMetaData는 현재 연결의 DBMS와 JDBC 드라이버 관련 정보를 제공한다.
14. DBMS의 INTEGER 필드에 대응하는 Java 자료형과 JDBC 메소드 조합으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④INTEGER는 Java의 int에 대응하며 읽기에는 getInt(), 지정에는 setInt()를 사용한다.
15. PreparedStatement에 관한 설명으로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②PreparedStatement는 SQL의 ? 위치에 setter로 값을 지정해 같은 구조의 문장을 반복 실행할 수 있다.
16. PreparedStatement SQL의 두 번째 물음표에 정수 100을 지정하는 코드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③setInt(parameterIndex, value)에서 첫 인자는 1부터 시작하는 매개변수 위치이고 둘째 인자는 정숫값이다.
17. PreparedStatement에서 조회 SQL을 실행할 때 일반적으로 SQL 문자열을 다시 인자로 주지 않는 이유는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ②prepareStatement(query)로 객체를 만들 때 SQL 구조가 이미 저장되므로 executeQuery()만 호출한다.
18. ResultSetMetaData 객체를 얻는 방법은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ①
특정 조회 결과의 구조 정보는 ResultSet.getMetaData()를 호출해 얻는다.
19. 조회 결과의 열 개수를 반환하는 ResultSetMetaData 메소드는?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ④getColumnCount()는 현재 결과 테이블에 포함된 열의 총개수를 반환한다.
20. DatabaseMetaData와 ResultSetMetaData의 비교로 옳은 것은?
정답입니다.
오답입니다. 답안을 다시 선택해 보세요.
정답 및 해설 보기
정답: ③DatabaseMetaData는 연결된 시스템 정보이고, ResultSetMetaData는 특정 결과 집합의 열 정보이다.
댓글
댓글 쓰기