본문으로 건너뛰기

JDBC과거

JDBC 한바퀴 돌자
일단 과거코드부터~ java 1.7까지 때의 코드들


기본준비

MySQL Connector가 필요하다. 메이븐 리포지토리에서 적절한 버전을 찾아서 pom에 추가하자

메이븐 리포지토리

pom.xml

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>

그레이들 이라면

implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.30'

도커 컴포즈 사용으로 로컬에서 DB를 손쉽게 띄워놓을 것이다.
docker-compose.yml

version: "3.8"
services:
powercis:
image: mysql:5.7
container_name: mysql
ports:
- "13306:3306"
environment:
MYSQL_ROOT_PASSWORD: "password"
MYSQL_DATABASE: exercise
TZ: 'Asia/Tokyo'
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
- ~/mysql/study/volume:/var/lib/mysql

13306포트로 접속하면 컨테이너 3306으로 접속하도록 했다.
mysql은 5.7로 버전 지정

참고로 jdbc URL은 이런 형식이다

  • Oracle : jdbc:oracle:thin:@database_server_name:port:database_name
  • PostgreSQL : jdbc:postgresql://database_server_name:port:database_name
  • MySQL : jdbc:mysql://database_server_name:port:database_name

드라이버 클래스명과 기본 포트는 다음과 같다.

  • Oracle : oracle.jdbc.driver.OracleDriver
    • default port : 1521
  • PostgreSQL : org.postgresql.Driver
  • default port : 5432
  • MySQL : com.mysql.cj.jdbc.Driver
  • default port : 3306
  • 예전 드라이버 클래스명 (com.mysql.jdbc.Driver) 은 deprecated되었음에 주의

DB접속

public class JdbcConnection {
public static void main(String[] args) throws ClassNotFoundException, SQLException {

Class.forName("com.mysql.cj.jdbc.Driver");

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:13306/exercise",
"root",
"password"
);

System.out.println("데이터베이스 컨넥션이 닫힌 상태 : " + con.isClosed());

con.close();

System.out.println("데이터베이스 컨넥션이 닫힌 상태 : " + con.isClosed());

}
}
데이터베이스 컨넥션이 닫힌 상태 : false
데이터베이스 컨넥션이 닫힌 상태 : true

DB처리가 끝났다면 반드시 컨넥션을 종료해주어야 한다.

데이터베이스에 접속을 하기 위해서는

    1. Class.forName메서드로 드라이버를 로드 한다.
    1. DriverManager.getConnection 메서드로 데이터베이스에 접속한다.
    • 첫번째 파라미터 : jdbc url
    • 두번째 파라미터 : 어카운트 명
    • 세번째 파라미터 : 패스워드

JDBC 4.0 을 서포트 하는 드라이버라면 드라이버 로드 부분을 생략해도 된다. (명시적으로 적어도 상관 없다)

Class.forName("com.mysql.cj.jdbc.Driver");
or
Class.forName("oracle.jdbc.driver.OracleDriver");
or
Class.forName("org.postgresql.Driver");

데이터 베이스에 접속하는것은 많은 코스트가 필요로 하기때문에, 매번 접속을 하지 않고 컨넥션 풀을 설정하여 접속하는데
컨넥션 풀을 관리하는 것을 데이터 소스 라고 부르며 DataSource인터페이스를 통해서 이용하는것이 가능하다
데이터베이스로부터 접속을 끊더라도 인스턴스가 파기되지않고 계속 유지 되므로 재 이용이 가능하다 (보통 싱글톤으로 작성)


DB 검색

DB에 접속해서 다음 쿼리문을 실행하자

CREATE TABLE IF NOT EXISTS member
(
id INT(11) UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID',
name VARCHAR(50) NOT NULL COMMENT '이름',
age TINYINT(3) NOT NULL COMMENT '나이'
) COMMENT '멤버' CHARSET = utf8mb4;

테이블 생성이되면 다음 sql 쿼리문을 실행하자

INSERT INTO exercise.member (id, name, age) VALUES (1, 'James', 25);
INSERT INTO exercise.member (id, name, age) VALUES (2, 'Cavin', 20);
INSERT INTO exercise.member (id, name, age) VALUES (3, 'Mike', 17);

이제 검색을 해보자

public class SearchDB {
public static void main(String[] args) throws ClassNotFoundException, SQLException {

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:13306/exercise",
"root",
"password"
);

Statement stmt = con.createStatement();
ResultSet rSet = stmt.executeQuery(
"SELECT id, name, age FROM member"
);

while (rSet.next()) {
System.out.print("ID : " + rSet.getInt("id")); // columnLable
System.out.print("\tNAME : " + rSet.getString(2)); // columnIndex
System.out.println("\tAGE : " + rSet.getString("AGE")); // columnLable
}

con.close();
}
}
ID : 1	NAME : James	AGE : 25
ID : 2 NAME : Cavin AGE : 20
ID : 3 NAME : Mike AGE : 17

데이터베이스로 검색을 할 때에는 먼저 Connection.createStatement 메소드로 Statement인스턴스를 생성해야한다.
그 후 파라미터로 검색을 하는 SQL문을 지정하는 Statement.executeQuery 메소드를 호출한다.
반환값은 ResultSet으로 넘어온다.
ResultSet에서 사용할 수 있는 메서드들을 사용해서 적절하게 결과값을 가져오면 된다.
인덱스 번호나 / 인덱스 명으로 결과값을 가져올 수 있다.
항상 컨넥셕은 close해준다


등록 / 갱신 / 삭제

public class OperateDB {
public static void main(String[] args) throws Exception {

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:13306/exercise",
"root",
"password"
);

Statement stmt = con.createStatement();

// 초기 상태를 표시
System.out.println("초기 상태");
OperateDB.displayDB(stmt);

int result = stmt.executeUpdate(
"INSERT INTO member(id, name, age)"
+ " values (4, 'Kim', 20)");
System.out.println(result + "건 등록");

// 데이터 표시
OperateDB.displayDB(stmt);

result = stmt.executeUpdate(
"UPDATE member SET age = 35"
+ " WHERE id > 2");
System.out.println(result + "건 갱신");

// 데이터 표시
OperateDB.displayDB(stmt);

result = stmt.executeUpdate(
"DELETE FROM member"
+ " WHERE id = 3");
System.out.println(result + "건 삭제");

// 데이터 표시
OperateDB.displayDB(stmt);

stmt.close();
con.close();
}

private static void displayDB(Statement stmt) throws Exception {

System.out.println("======================");
ResultSet rSet = stmt.executeQuery(
"SELECT id, name, age FROM member"
);

while (rSet.next()) {
System.out.print("ID : " + rSet.getInt("id"));
System.out.print("\tNAME : " + rSet.getString(2));
System.out.println("\tAGE : " + rSet.getString("AGE"));
}

rSet.close();
System.out.println("======================");
}
}
초기 상태
======================
ID : 1 NAME : James AGE : 25
ID : 2 NAME : Cavin AGE : 20
ID : 3 NAME : Lucy AGE : 35
======================
1건 등록
======================
ID : 1 NAME : James AGE : 25
ID : 2 NAME : Cavin AGE : 20
ID : 3 NAME : Lucy AGE : 35
ID : 4 NAME : Kim AGE : 20
======================
2건 갱신
======================
ID : 1 NAME : James AGE : 25
ID : 2 NAME : Cavin AGE : 20
ID : 3 NAME : Lucy AGE : 35
ID : 4 NAME : Kim AGE : 35
======================
1건 삭제
======================
ID : 1 NAME : James AGE : 25
ID : 2 NAME : Cavin AGE : 20
ID : 4 NAME : Kim AGE : 35
======================

등록 / 갱신 / 삭제 시에는 Statement.executeUpdate 메서드를 사용한다
등록 시에는 등록한 레코드 수, 갱신이나 삭제 시에는 갱신/삭제의 레코드 수를 반환한다.
Statement.executeUpdate 메서드는 CREATE TABLE 문 같은 DDL문도 실행 가능하다.
이 경우에는 항상 0을 리턴한다


ResultSet을 사용하여 DB를 갱신

public class UpdateRow {
public static void main(String[] args) throws Exception {

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:13306/exercise",
"root",
"password"
);

Statement stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, // 검색 때의 것들을 보관
ResultSet.CONCUR_UPDATABLE // 갱신 가능한 상태로 Statement 를 작성
);

System.out.println("========== 갱신전 ==========");
UpdateRow.displayDB(stmt);

ResultSet rSet = stmt.executeQuery(
"SELECT id, name, age FROM member"
);

while (rSet.next()) {
// 나이를 3 증가시킨다
int age = rSet.getInt("age");
rSet.updateInt("age", age + 3);
rSet.updateRow();
}

rSet.close();

System.out.println("========== 갱신후 ==========");
UpdateRow.displayDB(stmt);

stmt.close();
con.close();
}

private static void displayDB(Statement stmt) throws Exception {

System.out.println("======================");
ResultSet rSet = stmt.executeQuery(
"SELECT id, name, age FROM member"
);

while (rSet.next()) {
System.out.print("ID : " + rSet.getInt("id"));
System.out.print("\tNAME : " + rSet.getString(2));
System.out.println("\tAGE : " + rSet.getString("AGE"));
}

rSet.close();
System.out.println("======================");
}
}
========== 갱신전 ==========
======================
ID : 1 NAME : Lucas AGE : 28
ID : 2 NAME : Cavin AGE : 23
ID : 4 NAME : Kim AGE : 38
======================
========== 갱신후 ==========
======================
ID : 1 NAME : Lucas AGE : 31
ID : 2 NAME : Cavin AGE : 26
ID : 4 NAME : Kim AGE : 41
======================

Statement.executeQuery 메소드를 호출하면 검색결과가 ResultSet인스턴스에 받아진다.
그 내용을 ResultSet.updateInt 메서드나 ResultSet.updateString 메서드를 사용하여 갱신후,
ResultSet.updateRow 메소드를 호출하므로써 DB갱신을 한다
ResultSet클래스를 사용하여 데이터베이스를 갱신할 때에는 ResultSet인스턴스를 작성가능한 Statement 상태로 만들어야 한다.
ResultSet.TYPE_FOR_WARD_ONLY / ResultSet.TYPE_SCROLL_INSENSITIVE / ResultSet.TYPE_SCROLL_SENSITIVE 필드중 어느것을 선택해야 하며, 두번째 파라미터로는 ResultSet.CONCUR_UPDATABLE 필드를 설정해야한다. 이렇게 하면 갱신 가능한 ResultSet을 반환하는 Statement 인스턴스를 작성하는것이 가능하다


트랜잭션 처리

public class StartTransaction {
public static void main(String[] args) throws Exception {

Connection con = DriverManager.getConnection(
"jdbc:mysql://localhost:13306/exercise",
"root",
"password"
);

System.out.println("===== 초기 데이터 표시 =====");
StartTransaction.displayDB(con);

con.setAutoCommit(false);

// 데이터를 업데이트 한다
StartTransaction.updateDB(con);

System.out.println("===== 갱신 직후 데이터 표시 ======");
StartTransaction.displayDB(con);

// 롤백 한다
con.rollback();

System.out.println("===== 롤백 후 데이터 표시 ======");
StartTransaction.displayDB(con);

// 데이터를 다시 업데이트 한다
StartTransaction.updateDB(con);

con.commit();

System.out.println("===== 커밋 후 데이터 표시 ======");
StartTransaction.displayDB(con);


// 다시 롤백 한다
con.rollback();

System.out.println("===== 다시 롤백 후 데이터 표시 ======");
StartTransaction.displayDB(con);

con.commit();
con.close();
}

private static void updateDB(Connection con) throws Exception {

PreparedStatement pStmt = con.prepareStatement(
"UPDATE member SET name = ?"
+ " WHERE id = ?"
);

pStmt.setString(1, "Lucas");
pStmt.setInt(2, 1);
int result = pStmt.executeUpdate();

pStmt.close();

System.out.println(result + "건 갱신");
}

private static void displayDB(Connection con) throws Exception {

PreparedStatement pStmt = con.prepareStatement(
"SELECT id, name FROM member"
+ " WHERE id = ?"
);

pStmt.setInt(1, 1);
ResultSet rSet = pStmt.executeQuery();

while (rSet.next()) {
System.out.print("ID : " + rSet.getInt("id"));
System.out.println("\tNAME : " + rSet.getString("name"));
}

rSet.close();
pStmt.close();
}
}
===== 초기 데이터 표시 =====
ID : 1 NAME : James
1건 갱신
===== 갱신 직후 데이터 표시 ======
ID : 1 NAME : Lucas
===== 롤백 후 데이터 표시 ======
ID : 1 NAME : James
1건 갱신
===== 커밋 후 데이터 표시 ======
ID : 1 NAME : Lucas
===== 다시 롤백 후 데이터 표시 ======
ID : 1 NAME : Lucas

Connection 클래스는 기본값으로 AutoCommit 이다 DB처리를 할때 자동으로 커밋된다.
Connection.setAutoCommit 메서드로 자동커밋을 off하게되면 Connection.commit 메서드를 호출하기 전까지는
처리 내용이 확정되지 않게된다


복수의 SQL을 한번에 실행(배치)


그외 Blob 데이터나, 스토어드 프로시저 등도 JDBC 를 통해서 가능하다.
그 부분들은 필요할 때 찾아보면 좋을것 같다.