자바(Java) GUI 프로그램 도서 조회 시스템 (3) DB Class 만들고 단위 테스트 수행

송범·2024년 11월 5일

Oracle DB를 연결해서 DB에 있는 데이터를 조회해보자.

  • DB연결 공통코드 작성
package com.util;

import java.net.URL;
import java.sql.*;
//공통코드 작성해 보기
//반복되는 코드 줄이기
//메소드는 슬림(양이 적게)하게 하나의 책임만 진다 - 재사용성이 좋다.
//DB서버는 하나이고 하나의 서버를 여러 개발자들이 바라본다
//하나의 객체를 가지고 공유하자(싱글톤패턴 - Sprint지원 - 프레임워크)
//클래스선언에 static붙임 - 얕은복사.
//모두 인터페이스이다. (Connection,PreparedStatement,ResultSet) - 결정할 수 없다.
//왜요? 디바이스마다 각각 다르게 동작해야한다. - 결정할 수 없다. - 구현체 클래스가 한다.
//아래 인터페이스는 모두 메소드로 객체 생성함.
public class DBConnectionMgr {

    static DBConnectionMgr dbMgr =null;
    //서버(정보제공측) - 클라이언트(제공된 정보를 활용) -> 2-iter
    //서버 - 미들웨어서버 - 클라이언트 -> 3-iter or multi-iter 라고도 불린다.
    //java.sql.* 혹은 javax.sql.* 참조함.
    Connection con; //
    PreparedStatement pstmt; //동적쿼리
    ResultSet rs; //Cursor조작하는 API를 제공한다.


    public final static String _Driver ="oracle.jdbc.driver.OracleDriver";
    public final static String _URL ="jdbc:oracle:thin:@localhost:1522:orcl";
    public final static String _User ="Scott";
    public final static String _Pass ="tiger";

    //메소드를 활용하여 객체를 생성하기 - 세련된 코드 - 싱글톤 패턴
    public static DBConnectionMgr getInstance() {
        if(dbMgr==null) {
            dbMgr=new DBConnectionMgr();
        }
        return dbMgr;
    }

    public Connection getConnection() {
        try {
            Class.forName(_Driver);
            con = DriverManager.getConnection(_URL, _User, _Pass);

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


    //사용한 자원 반납하기 - 이종간에 연계하는 코드 작성.
    //사용자 자원을 닫을 때는 생성된 역순으로 닫는다.
    //생략하면  JVM의 가비지컬렉터가 대신 해준다. -명시적으로 구현하는 것을 권장함
    //JDBC API - 순수한코드 -> MyBatis 사용 -> Hibernate
    //insert,update,delete -> Connection, PreparedStatement
    //select -> Connection, PreparedStatement, ResultSet
    public void freeConnection(Connection con,PreparedStatement pstmt,ResultSet rs) {
        try{
            if(rs != null) {
                rs.close();
            }
            if(pstmt != null) {
                pstmt.close();
            }
            if(con != null) {
                con.close();
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    

VO(Value Object) 클래스 설계

  • VO(Value Object)는 특정 테이블의 한레코드를 표현하는 클래스로, 데이터의 속성(필드)와 이를 캡슐화 하기 위해 getter and setter 메소드로 구성이 된다. 주로 데이터 전송과 조회에 대해 사용한다.

VO의 특징

  • 순수 데이터 객체로 , 비즈니스 로직을 담당하지는 않는다.
  • 주로 데이터베이스에서의 엔티티(Entity)와 동일한 구조를 가진다.
  • 클래스의 각 필드는 데이터베이스의 컬럼(특성,attribute)과 매핑되도록 설계하도록 한다.

package jdbc.book;

//Getter and Setter를 이용해서 VO 설계해보기
//그러나 상황에 따라 사용하지 않고 공부하기.



public class BookVO1 {
    private int b_no;
    private String b_name;
    private String b_author;
    private String b_publish;
    private String b_info;
    //여기서 파일 업로드 처리는 하지 않습니다. 그래서 이미지 이름만 저장
    private String b_img;

    public BookVO1() {}
    public BookVO1(int b_no, String b_name, String b_author, String b_publish, String b_info, String b_img) {
        this.b_no = b_no;
        this.b_name = b_name;
        this.b_author = b_author;
        this.b_publish = b_publish;
        this.b_info = b_info;
        this.b_img = b_img;

    }
    public String getB_author() {
        return b_author;
    }

    public void setB_author(String b_author) {
        this.b_author = b_author;
    }

    public int getB_no() {
        return b_no;
    }

    public void setB_no(int b_no) {
        this.b_no = b_no;
    }

    public String getB_name() {
        return b_name;
    }

    public void setB_name(String b_name) {
        this.b_name = b_name;
    }

    public String getB_publish() {
        return b_publish;
    }

    public void setB_publish(String b_publish) {
        this.b_publish = b_publish;
    }

    public String getB_info() {
        return b_info;
    }

    public void setB_info(String b_info) {
        this.b_info = b_info;
    }

    public String getB_img() {
        return b_img;
    }

    public void setB_img(String b_img) {
        this.b_img = b_img;
    }


}
  • lombok 사용해서 VO 설계
package jdbc.book;


import lombok.AllArgsConstructor;
import lombok.Generated;
import lombok.NoArgsConstructor;
import lombok.Setter;
//그러나 상황에 따라 사용하지 않고 공부하기.

@Setter
@Generated
@AllArgsConstructor
@NoArgsConstructor


public class
BookVO {
    private int b_no;
    private String b_name;
    private String b_author;
    private String b_publish;
    private String b_info;
    //여기서 파일 업로드 처리는 하지 않습니다. 그래서 이미지 이름만 저장
    private String b_img;
    
}

SQL 쿼리가 실행되는 과정

select b_no from book152
  1. 파싱(Parsing) - SQL문에 문법적으로 문제가 있는지 확인한다.

  2. 실행 계획 생성(Execution Plan Generation) - DBMS가 실행계획을 세운다.

  3. 옵티마이저(Optimizer) - DBMS는 실행 계획을 세우기 위해 DBMS는 옵티마이저를 사용하며 여러가지 실행 계획을 시뮬레이션하여 가장 비용이 적은(빠르고 효율적인) 계획을 선택한다.

  4. 실행 (Execution) - 최적의 계획이 결정되면 DBMS는 쿼리를 실행합니다.

  5. 결과 반환 (Fetching Results) - 실행 결과는 사용자에게 반환됩니다.

open -> cursor -> fetch -> close

DAO(Data Access Object) 클래스 설계

  • DAO(Data Access Object)는 데이터베이스와의 통신을 담당하는 클래스이다. VO(Valute Objec)를 이용해서 CRUD(Create,Read,Update,Delete) 작업을 수행합니다. DAO 클래스는 데이터베이스의 테이블을에 대한 작업을 수행하는 메소드들로 구성되어 있으며, 데이터베이스와 직접적인 상호작용은 이 객체가 담당합니다.
package jdbc.book;
//MVC패턴 - 데이터와 관련된 것을 Model 계층이다.

import com.util.DBConnectionMgr;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class BookDao {
    //Spring프레임워크가 기본적으로 객체 라이프사이클 관리하는 메커니즘
    DBConnectionMgr dbMgr = DBConnectionMgr.getInstance(); //싱글톤 패턴
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    BookApp bookApp = null;

    public BookDao(BookApp bookApp){
        this.bookApp = bookApp;
    }
    public BookDao(){}

    /*********************************************************
     * 도서 목록 조회 및 상세조회 구현
     * SELECT b_no,b_name,b_author,b_publish,b_info,b_img
     *  FROM book152
     *  where b_no = ? 1건 조회하기
     * SELECT b_no,b_name,b_author,b_publish,b_info,b_img
     *  FROM book152 전체 조회
     * @param pbvo
     * @return List<BookVO> : BookVO가 한 건만 담을 수 있다.
     * 한 건이면 bList.size() = 1 이면 한 건이다.
     * bList.size() > 1 이면 여러 건이다.
     ***********************************************************/

    public List<BookVO> getBookList(BookVO pbvo){
        System.out.println("getBookList호출 성공"+ pbvo.getB_no());
        List<BookVO> bList = new ArrayList<>();
        System.out.println(bList.size());
        StringBuilder sql =new StringBuilder(); //String은 원본이 바뀌지 않아서 메모리에 대한 누수가 있다.
        sql.append("select b_no,b_name,b_author,b_publish,b_info,b_img");
        sql.append(" from book152");
        if(pbvo.getB_no() > 0){
            sql.append(" where b_no = ?");
        }

        try{
            conn = dbMgr.getConnection();
            pstmt = conn.prepareStatement(sql.toString());
            if(pbvo.getB_no() > 0) {
                pstmt.setInt(1, pbvo.getB_no());
            }
            rs = pstmt.executeQuery();
            BookVO bvo = null;
            while(rs.next()){
                bvo = new BookVO();
                bvo.setB_no(rs.getInt("b_no"));//도서일련번호
                bvo.setB_name(rs.getString("b_name"));//도서명
                bvo.setB_author(rs.getString("b_author"));//저자명
                bvo.setB_publish(rs.getString("b_publish"));//출판사
                bvo.setB_info(rs.getString("b_info"));//도서 소개
                bvo.setB_img(rs.getString("b_img"));//도서이미지
                bList.add(bvo);
            }

        } catch (SQLException e) {
            System.out.println(sql.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
        finally{//예외가 발생하더라도 무조건 실행된다.
            //사용한 자원 반납하기 - 생성된 역순으로 해준다.
            //생략하면 처리는 되지만 명시적으로 처리하는 것이다. - 자바 튜닝
            dbMgr.freeConnection(conn, pstmt, rs);
        }

        System.out.println(bList.size());
        return bList;
    }


//    public static void main(String[] args) {
//        BookDao bookDao = new BookDao(new BookApp());
//        System.out.println(bookDao.getBookList(new BookVO()));
//    }
//



    /*******************************************************
     * 도서 삭제 구현하기
     * DELETE FROM book152 WHERE b_no = ?
     * @param //bvo or int b_no
     * @return int - 1이면 삭제 성공 / 0이면 삭제 실패
     *********************************************************/


    public int bookDelete(int b_no){
        int result = -1; //1이면 삭제 성공. 0이면 삭제 실패, 그래서 초기값을 -1로 한다.
        StringBuilder sql =new StringBuilder();
        sql.append("delete from book152 where b_no= ?");
        try{
            conn = dbMgr.getConnection();
            pstmt = conn.prepareStatement(sql.toString());
            pstmt.setInt(1, b_no); //b_no = ? 에 들어가는 번호
            result = pstmt.executeUpdate();

        } catch (SQLException e) {
            //쿼리문에 부적합한 식별자(컬럼명 오타), 테이블이나 뷰(오브젝트-select문 금융) 이름이 없습니다.
            System.out.println(sql.toString());
        } catch (Exception e) {
            e.printStackTrace(); //에러메시지도 stack에 히스토리를 가짐. 출력함
        }

        return result; //성공이면 1 실패이면 0
    }

    /*******************************************************************
     * 도서 정보 수정하기
     * UPDATE book152
     *      SET  b_name = ?
     *          ,b_author = ?
     *          ,b_publish = ?
     *      WHERE b_no = ?
     * @param bvo
     * @return
     */
    public int bookUpdate(BookVO bvo){
        int result = -1; //1이면 수정 성공. 0이면 수정 실패, 그래서 초기값을 -1로 한다.
        conn = dbMgr.getConnection();
        StringBuilder sql =new StringBuilder();
        sql.append(" UPDATE book152 set b_name=?,b_author=?,b_publish=?,b_info=?,b_img=?");
        sql.append(" where b_no = ?");
        try{
            pstmt = conn.prepareStatement(sql.toString());
            pstmt.setString(1, bvo.getB_name());
            pstmt.setString(2, bvo.getB_author());
            pstmt.setString(3, bvo.getB_publish());
            pstmt.setString(4, bvo.getB_info());
            pstmt.setString(5, bvo.getB_img());
            pstmt.setInt(6, bvo.getB_no());
            result = pstmt.executeUpdate();

        }catch (SQLException e){
            System.out.println(sql.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        finally {
            dbMgr.freeConnection(conn, pstmt);
        }

        return result;
    }

    //BookVO를 사용하지 않으면 파라미터 변수(컬럼명)를 다 주어야한다.
//    public int bookUpdate(int b_no,String b_name,String b_author,String b_publish){
//        return 1;
//    }

    /****************************************************************
     * 도서 정보 넣기
     * INSERT INTO book152(b_no,b_name,b_author,b_publish) VALUES(?,'?')
     * @param bvo
     * @return
     *****************************************************************/
    public int bookInsert(BookVO bvo){
        int result = -1; //1이면 삽입 성공. 0이면 삽입 실패, 그래서 초기값을 -1로 한다.
        StringBuilder sql =new StringBuilder();
        sql.append("insert into book152 values(seq_book152_no.nextval,?,?,?,?,?)");
        int i = 1;
        try{
            conn = dbMgr.getConnection();
            pstmt = conn.prepareStatement(sql.toString());
            //? 자리를 채우는 값을 설정할 때는 1번 부터 입니다.
            //컬럼이 추가되거나 컬럼의 순서가 바뀌면 숫자를 일일이 바꿔야하니까
            //번호 대신에 변수로 처리합니다. - 후위 연산자. -먼저 대입 나중에 증가
            //변수 i의 초기값이 0이면 ++i가 맞고 초기값을 1로 하면 i++이 맞다.
            pstmt.setString(i++,bvo.getB_name());
            pstmt.setString(i++,bvo.getB_author());
            pstmt.setString(i++,bvo.getB_publish());
            pstmt.setString(i++,bvo.getB_info());
            pstmt.setString(i++,bvo.getB_img());
            result = pstmt.executeUpdate();

        }catch (SQLException e){
            System.out.println(sql.toString());
        }
        catch (Exception e){
            e.printStackTrace();
        }finally {
            dbMgr.freeConnection(conn, pstmt);
        }

        return result;
    }


}

단위 테스트 해보기

package jdbc.book;

import javax.swing.*;
import java.util.List;
//main 메소드에 코드 많이 작성하는 거 금지

public class BookDaoTest {
    BookDao bookDao = new BookDao();
    JFrame frame = new JFrame();

    public int bookDelete(int b_no) {
        int result = -1;
        result = bookDao.bookDelete(b_no);
        if (result == 1) {
            System.out.println("1개 row가 삭제되었습니다"+result);
        }
        else {
            System.out.println("삭제가 실패되었습니다."+result);
        }
        return result;
    }

    public void getBookListTest(){

        BookDao bookDao = new BookDao();
        BookVO bookVO = new BookVO();
        bookVO.setB_no(0);
        List<BookVO> bookList = bookDao.getBookList(bookVO);
        //b_no가 0이면 where절이 추가되지 않아서 전체 조회가 된다.
        //b_no가 1이면 where절이 반영되니까 한 건이 조회가 된다.
        //b_no가 2이면 where절이 반영되니까 한 건이 조회가 된다.
        //b_no가 3이면 where절이 반영되니까 한 건이 조회가 된다.
        for (BookVO vo : bookList) {
            System.out.println(vo.getB_no());
            System.out.println(vo.getB_name());
            System.out.println(vo.getB_author());
            System.out.println(vo.getB_publish());
            System.out.println(vo.getB_info());
        }



    }

//    public void bookInsertTest(String b_name, String b_author, String b_publish, String b_info,String b_jpg) {
//        int result = -1;
//        BookVO bookVO = new BookVO();
//        BookDao bookDao = new BookDao();
//
//        bookVO.setB_name(b_name);
//        bookVO.setB_author(b_author);
//        bookVO.setB_publish(b_publish);
//        bookVO.setB_info(b_info);
//        bookVO.setB_img(b_jpg);
//
//        result = bookDao.bookInsert(bookVO);
//
//        if (result > 0) {
//            System.out.println("도서 삽입 성공");
//        } else {
//            System.out.println("도서 삽입 실패");
//        }
//    } //BookVO의 생성자를 활용하여 데이터를 넣는다.




    public static void main(String[] args) {
        BookDaoTest test = new BookDaoTest();
        int result = -1;
        result = test.bookDelete(8);
        if (result == 1) {
            JOptionPane.showMessageDialog(test.frame,"삭제 성공하였습니다.");
            test.getBookListTest();
        }
        else{
            JOptionPane.showMessageDialog(test.frame,"삭제 실패하였습니다.");
        }
        result = -1;
        BookVO bookVO = new BookVO(0,"책제목5","장보고","내용2","정보2","6.jpg");
        result = test.bookDao.bookInsert(bookVO); //생성자를 활용하여서 데이터를 넣는다.
        if (result == 1) {
            JOptionPane.showMessageDialog(test.frame,"삽입 성공하였습니다.");
        }
        else{
            JOptionPane.showMessageDialog(test.frame,"삽입 실패하였습니다.");
        }
        bookVO = new BookVO(4,"책제목 5","세종대왕","내용55","정보88","7.jpg");
        result = test.bookDao.bookUpdate(bookVO);

        if (result == 1) {
            JOptionPane.showMessageDialog(test.frame,"업데이트 성공하였습니다.");
        }
        else{
            JOptionPane.showMessageDialog(test.frame,"업데이트 실패하였습니다.");
        }
    }
}
  • UPDATE문 실행 업데이트 성공

  • 결과

profile
BackEnd&Data Scientist가 되고 싶은 개발 기록 노트

0개의 댓글