EunJng

[Java] 이것이 자바다 Ch.20 본문

STUDY

[Java] 이것이 자바다 Ch.20

Eunjng 2024. 2. 29. 14:55

'이것이 자바다' 교재 및 강의를 참고하여 정리한 내용입니다.

Ch.20 데이터베이스 입출력

JDBC 개요

  • 자바는 DB와 연결해서 데이터 입출력 작업을 할 수 있도록 JDBC(Java Database Connectivity) 라이브러리 제공
  • JDBC Driver : JDBC 인터페이스를 구현한 것. DBMS마다 별도로 다운로드받아 사용

  • DriverManager : JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체 생성
  • Connection : Statement, PreparedStatement, CallableStatement 구현 객체 생성. 트랜잭션 처리 및 DB 연결을 끊을 때 사용
  • Statement : SQL의 DDL, DML을 실행할 때 사용. 주로 정적 SQL문을 실행할 때 사용
  • PreparedStatement : SQL의 DDL, DML을 실행할 때 사용. 매개변수화된 SQL문을 사용할 수 있어 편리성과 보안성이 좋다.
  • CallableStatement : DB에 저장되어 있는 프로시저와 함수를 호출할 때 사용
  • ResultSet : DB에서 가져온 데이터를 읽을 때 사용

 

DB 연결

  • 클라이언트 프로그램에서 DB와 연결하려면 DBMS의 JDBC Driver 필요
  • DB연결에 필요한 정보
    • DBMS가 설치된 컴퓨터의 IP 주소
    • DBMS가 허용하는 포트 번호
    • 사용자 및 비밀번호
    • 사용하고자 하는 DB 이름

DB 연결

  • JDBC Diver를 메모리로 로딩
    • Class.forName("com.mysql.cj.jdbc.Driver");
    • JDBC Driver 클래스의 static 블록이 실행되며 DriverManager에 JDBC Driver 객체 등록
    • Build Path에서 JDBC Driver 클래스를 찾지 못하면 ClassNotFoundException 발생 - 예외 처리 필요
  • DB 연결
    • Connection conn = DriverManager.getConnection("연결 문자열", "사용자", "비밀번호");
      • 연결 문자열: jdbc:mysql://{IP 주소}:{포트 번호}/{DB명}
package ThisIsJava;

import java.io.FileInputStream;
import java.sql.*;

public class UserInsertExample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            // JDBC Driver 등록
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 연결
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/thisisjava",
                    "root",
                    ""
            );
            System.out.println("연결 성공");

//            // 추가
//            String sql = "" +
//                    "INSERT INTO users (userid, username, userpassword, userage, useremail) " +
//                    "VALUES (?, ?, ?, ?, ?)";

//            // PreparedStatement 얻기 및 값 지정 1
//            PreparedStatement pstmt = conn.prepareStatement(sql);
//            pstmt.setString(1, "winter");
//            pstmt.setString(2, "한겨울");
//            pstmt.setString(3, "12345");
//            pstmt.setString(4, 25);
//            pstmt.setString(5, "winter@mycompany.com");

//            // 2
//            PreparedStatement pstmt = conn.prepareStatement(
//                    sql, Statement.RETURN_GENERATED_KEYS);
//            pstmt.setString(1, "눈 오는 날");
//            pstmt.setString(2, "함박눈이 내려요.");
//            pstmt.setString(3, "winter");
//            pstmt.setString(4, "snow.jpg");
//            pstmt.setBlob(5, new FileInputStream("src/ch20/mysql/sec06/snow.jpg"));


            // // 게시물 정보 수정
            // 매개변수화된 SQL 문 작성
            String sql = new StringBuilder()
                    .append("UPDATE boards SET ")
                    .append("btitle=?, ")
                    .append("bcontent=?, ")
                    .append("bfilename=?, ")
                    .append("bfiledata=? ")
                    .append("WHERE bno=?")
                    .toString();

            // 삭제
//            String sql = "DELETE FROM boards WHERE bwriter=?";

            
            // PreparedStatement 얻기 및 값 지정
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "눈사람");
            pstmt.setString(2, "눈으로 만든 사람");
            pstmt.setString(3, "snowman.jpg");
            pstmt.setBlob(4, new FileInputStream("src/ch20/mysql/sec06/snowman.jpg"));
            pstmt.setInt(5, 3);

            // SQL 문 실행
            int rows = pstmt.executeUpdate();
            System.out.println("수정된 행 수: " + rows);

            // bno 값 얻기
            if (rows == 1) {
                ResultSet rs = pstmt.getGeneratedKeys();
                if (rs.next()) {
                    int bno = rs.getInt(1);
                    System.out.println("저장된 bno: " + bno);
                }
                rs.close();
            }

            // PreparedStatement 닫기
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    // 연결 끊기
                    conn.close();
                    System.out.println("연결 끊기");
                } catch (SQLException e) {}
            }
        }
    }
}

 

데이터 읽기

  • PreparedStatement 생성 시 INSERT, UPDATE, DELETE일 경우 executerUpdate() 메소드를 호출하지만, 데이터를 가져오는 SELECT 문일 경우 executeQuery() 메소드 호출
  • executerQuery() 메소드는 가져온 데이터를 ResultSet에 저장하고 리턴

ResultSet

  • SELECT 문에 기술된 컬럼으로 구성된 행의 집합
  • 커서가 있는 행의 데이터만 읽을 수 있고, next() 메소드를 통해 커서를 다음 행으로 이동
    • if(rs.next())  || while(rs.next())

데이터 행 읽기

  • 컬럼의 데이터 타입에 따라 getXxx() 메소드가 사용되고, 매개값으로 컬럼의 이름 또는 순번을 줄 수 있다.
    • rs.getString("userid");
    • rs.getInt(3);
package ThisIsJava;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.*;

public class SelectExample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            // JDBC Driver 등록
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 연결
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/thisisjava",
                    "root",
                    ""
            );

//            // user Select
//            String sql = "" +
//                    "SELECT userid, username, userpassword, userage, useremail " +
//                    "FROM users " +
//                    "WHERE userid=?";

            // board Select
            String sql = "" +
                    "SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " +
                    "FROM boards " +
                    "WHERE bwriter=?";
            
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "winter");
            
            // SQL 문 실행 후, ResultSet을 통해 데이터 읽기
            ResultSet rs = pstmt.executeQuery();
            
//            // user Select
//            if (rs.next()) {    // 1개의 데이터 행을 가져왔을 경우
//                User user = new User();
//                user.setUserId(rs.getString("userid"));
//                user.setUserName(rs.getString("username"));
//                user.setUserPassword(rs.getString("userpassword"));
//                user.setUserAge(rs.getInt(4));  // 컬럼 순번 이용
//                user.setUserEmail(rs.getString(5));
//                System.out.println(user);
//            } else {    // 데이터 행을 가져오지 않았을 경우
//                System.out.println("사용자 아이디가 존재하지 않음");
//            }
            
            // board Select
            while (rs.next()) {
                // 데이터 행을 읽고 Board 객체 생성
                Board board = new Board();
                board.setBno(rs.getInt("bno"));
                board.setBtitle(rs.getString("btitle"));
                board.setBcontent(rs.getString("bcontent"));
                board.setBwriter(rs.getString("bwriter"));
                board.setBdate(rs.getDate("bdate"));
                board.setBfilename(rs.getString("bfilename"));
                board.setBfiledata(rs.getBlob("bfiledata"));

                System.out.println(board);
                
                // 파일로 저장
                Blob blob = board.getBfiledata();
                if (blob != null) {
                    InputStream is = blob.getBinaryStream();
                    OutputStream os = new FileOutputStream("C:/Temp/" +
                            board.getBfilename());
                    is.transferTo(os);
                    os.flush();
                    os.close();
                    is.close();
                }
            }
            rs.close();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    // 연결 끊기
                    conn.close();
                } catch (SQLException e) {}
            }
        }
    }
}

 

트랜잭션 처리

  • transaction: 기능 처리의 최소 단위. 트랜잭션은 소작업들이 모두 성공하거나 모두 실패해야 한다
  • DB는 트랜잭션 처리를 위해 커밋, 롤백 제공
    • commit : 내부 작업을 모두 성공 처리
    • rollback : 실행 전으로 돌아간다 (모두 실패 처리)
  • JDBC에서는 INSERT, UPDATE, DELETE 문을 실행할 때마다 자동 커밋이 일어나기 때문에 트랜잭션을 코드로 제어하려면 자동 커밋 기능을 꺼야 한다.
    • conn.setAutoCommit(false);
    • conn.commit();  // 수동 커밋
    • conn.rollback();  // 수동 롤백
    • 트랜잭션 처리 후에는 Connection을 계속 사용해야 한다면 setAutoCommit(true)로 자동 커밋 기능을 켜야 한다.
  • 커넥션 풀(Connection Pool)
    • 일정량의 Connection을 미리 생성시켜놓고, 서버에서 클라이언트의 요청을 처리할 때 Connection을 제공해주고 다시 반환받는 역할 수행
    • Connection 재사용을 통해 DB 연결 시간을 줄이고, 전체 Connection 수를 관리할 수 있다.

 

게시판 구현

package com.example.thisisjava;

import java.sql.*;
import java.util.Scanner;

public class BoardExample2 {
    // Field
    private Scanner scanner = new Scanner(System.in);
    private Connection conn;

    // Constructor
    public BoardExample2() {
        try {
            // JDBC Driver 등록
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 연결하기
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/thisisjava",
                    "root",
                    ""
            );
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
    }

    // Method
    public void list() {
        // 타이틀 및 컬럼명 출력
        System.out.println();
        System.out.println("[게시물 목록]");
        System.out.println("-----------------------------------------------------");
        System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
        System.out.println("-----------------------------------------------------");

        // boards 테이블에서 게시물 정보를 가져와서 출력
        try {
            String sql = "" +
                    "SELECT bno, btitle, bcontent, bwriter, bdate " +
                    "FROM boards " +
                    "ORDER BY bno DESC";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                Board board = new Board();
                board.setBno(rs.getInt("bno"));
                board.setBtitle(rs.getString("btitle"));
                board.setBcontent(rs.getString("bcontent"));
                board.setBwriter(rs.getString("bwriter"));
                board.setBdate(rs.getDate("bdate"));
                System.out.printf("%-6s%-12s%-16s%-40s \n",
                        board.getBno(),
                        board.getBwriter(),
                        board.getBdate(),
                        board.getBtitle());
            }
            rs.close();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }

        // 메인 메뉴 출력
        mainMenu();
    }

    public void mainMenu() {
        System.out.println();
        System.out.println("-----------------------------------------------------");
        System.out.println("메인 메뉴: 1.Create | 2.Read | 3.Clear | 4.Exit");
        System.out.printf("메뉴 선택: ");
        String menuNo = scanner.nextLine();
        System.out.println();

        switch (menuNo) {
            case "1" -> create();
            case "2" -> read();
            case "3" -> clear();
            case "4" -> exit();
        }
    }

    public void create() {
        // 입력 받기
        Board board = new Board();
        System.out.println("[새 게시물 입력]");
        System.out.print("제목: ");
        board.setBtitle(scanner.nextLine());
        System.out.print("내용: ");
        board.setBtitle(scanner.nextLine());
        System.out.print("작성자: ");
        board.setBtitle(scanner.nextLine());

        // 보조 메뉴 출력
        System.out.println("-----------------------------------------------------");
        System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
        System.out.printf("메뉴 선택: ");
        String menuNo = scanner.nextLine();
        if (menuNo.equals("1")) {
            // boards 테이블에 게시물 정보 저장
            try {
                String sql = "" +
                        "INSERT INTO boards (btitle, bcontent, bwriter, bdate) " +
                        "VALUES (?, ?, ?, now())";
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, board.getBtitle());
                pstmt.setString(2, board.getBcontent());
                pstmt.setString(3, board.getBwriter());
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        // 게시물 목록 출력
        list();
    }

    public void read() {
        // 입력 받기
        System.out.println("[게시물 읽기]");
        System.out.print("bno: ");
        int bno = Integer.parseInt(scanner.nextLine());

        // boards 테이블에서 해당 게시물을 가져와 출력
        try {
            String sql = "" +
                    "SELECT bno, btitle, bcontent, bwriter, bdate " +
                    "FROM boards " +
                    "WHERE bno=?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, bno);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                Board board = new Board();
                board.setBno(rs.getInt("bno"));
                board.setBtitle(rs.getString("btitle"));
                board.setBcontent(rs.getString("bcontent"));
                board.setBwriter(rs.getString("bwriter"));
                board.setBdate(rs.getDate("bdate"));
                System.out.println("#############");
                System.out.println("번호: " + board.getBno());
                System.out.println("제목: " + board.getBtitle());
                System.out.println("내용: " + board.getBcontent());
                System.out.println("작성자: " + board.getBwriter());
                System.out.println("날짜: " + board.getBdate());

                System.out.println("-----------------------------------------------------");
                System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
                System.out.printf("메뉴 선택: ");
                String menuNo = scanner.nextLine();
                System.out.println();

                if (menuNo.equals("1")) {
                    update(board);
                } else if (menuNo.equals("2")) {
                    delete(board);
                }
            }
            rs.close();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
        list();
    }

    public void update(Board board) {
        System.out.println("[수정 내용 입력]");
        System.out.print("제목: ");
        board.setBtitle(scanner.nextLine());
        System.out.print("내용: ");
        board.setBtitle(scanner.nextLine());
        System.out.print("작성자: ");
        board.setBtitle(scanner.nextLine());

        // 보조 메뉴 출력
        System.out.println("-----------------------------------------------------");
        System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
        System.out.printf("메뉴 선택: ");
        String menuNo = scanner.nextLine();
        if (menuNo.equals("1")) {
            // boards 테이블에 게시물 정보 수정
            try {
                String sql = "" +
                        "UPDATE boards SET btitle=?, bcontent=?, bwriter=? " +
                        "WHERE bno=?";
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.setString(1, board.getBtitle());
                pstmt.setString(2, board.getBcontent());
                pstmt.setString(3, board.getBwriter());
                pstmt.setInt(4, board.getBno());
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void delete(Board board) {
        // Boards 테이블에 게시물 정보 삭제
        try {
            String sql = "" +
                    "DELETE FROM boards WHERE bno=?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, board.getBno());
            pstmt.executeUpdate();
            pstmt.close();
        } catch (Exception e) {
            e.printStackTrace();
            exit();
        }
        list();
    }

    public void clear() {
        System.out.println("[게시물 전체 삭제]");
        System.out.println("-----------------------------------------------------");
        System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
        System.out.printf("메뉴 선택: ");
        String menuNo = scanner.nextLine();
        if (menuNo.equals("1")) {
            // boards 테이블에 게시물 정보 전체 삭제
            try {
                String sql = "TRUNCATE TABLE boards";
                PreparedStatement pstmt = conn.prepareStatement(sql);
                pstmt.executeUpdate();
                pstmt.close();
            } catch (Exception e) {
                e.printStackTrace();
                exit();
            }
        }
        list();
    }

    public void exit() {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
            }
        }
        System.out.println("** 게시판 종료 **");
        System.exit(0);
    }

    public static void main(String[] args) {
        BoardExample2 boardExample = new BoardExample2();
        boardExample.list();
    }
}