[DB] JDBC 프로그래밍 (0715)

왕감자·2024년 7월 15일

KB IT's Your Life

목록 보기
83/177

JDBC

Java Database Connectivity

  • 데이터베이스와 연결해서 입출력 지원
  • DBMS의 종류와 상관없이 동일하게 사용할 수 있는 클래스와 인터페이스로 구성


JDBC 인터페이스 / 클래스

1) DriverManager

  • JDBC 드라이버 로딩 ⇨ 실제 구현체 찾아줌

2) Connection

  • 접속/로그인 담당 ⇨ close되면 데이터와 접속 끊김

3) Statement / PreparedStatement / CallableStatement

  • String으로 작성된 SQL문을 실행해주는 객체

4) ResultSet

  • INSERT, UPDATE, DELETE 리턴값 ➞ int
  • SELECT ➞ TABLE (순회하면서 필요한 데이터 가져옴)
    ⇨ SELECT문 실행 결과를 처리하는 객체

JDBC 개발 절차


1) DB 연결

① 드라이버 로딩

➔ 없으면 ClassNotFoundException 발생

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

② Connection 객체

  • DB에 연결 세션 만듦
Connection conn = DriverManager.getConnection("연결 문자열(접속url)", "사용자", "비밀번호")
  • 연결 문자열 (접속 url)
    • "jdbc:mysql://[host]:[포트]/[db이름]"
      ➔ String url = "jdbc:mysql://127.0.0.1:3306/jdbc_ex";

ConnectionTest.java

// DB 커넥션은 모듈화해서 쓸거다~!
public class ConnectionTest {
    @Test
    @DisplayName("jdbc_ex 데이터베이스 접속 (Connection Test)")
    public void testConnection() throws SQLException, ClassNotFoundException {
        Class.forName("com.mysql.cj.jdbc.Driver"); // 드라이버 연결

        // 설정정보 - 하드코딩(비권장) -> 설정 파일로 분리
        String url = "jdbc:mysql://127.0.0.1:3306/jdbc_ex"; // 연결문자열(접속url)
        String id = "jdbc_ex";
        String password = "jdbc_ex";

        Connection conn = DriverManager.getConnection(url, id, password);
        System.out.println("DB 연결 성공");

        conn.close(); // DB 연결 종료
    }
}
  • 설정 정보는 설정 파일로 분리하는 것이 권장
    • resouce::/ application.properties (설정 파일)
      # 키=값 (공백X)
      driver=com.mysql.cj.jdbc.Driver
      url=jdbc:mysql://127.0.0.1:3306/jdbc_ex
      id=jdbc_ex
      password=jdbc_ex

> DB 모듈

JDBCUtil.java

// DB 모듈
public class JDBCUtil {
    static Connection conn = null; // static으로 Connection 객체 준비
    static { // conn을 초기화할 코드가 길기 때문에  static 초기화 블럭으로 초기화 진행
        try {
            Properties properties = new Properties();
            properties.load(JDBCUtil.class.getResourceAsStream("/application.properties"));
            String driver = properties.getProperty("driver");
            String url = properties.getProperty("url");
            String id = properties.getProperty("id");
            String password = properties.getProperty("password");

            Class.forName(driver);
            conn = DriverManager.getConnection(url, id, password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    // DB 연결
    public static Connection getConnection() {
        return conn;
    }
    
    // DB 연결 끊기
    public static void close() {
        try {
            if (conn != null) {
                conn.close();
                conn = null;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

> JDBCUtil - getConnection 메서드 테스트

  • test 또한 동일한 패키지로 설정 → Intellij가 해결해줌~!
    • 클래스 내부 마우스 오른쪽 - Go to - Test ⇨ 자동으로 test 생성

JDBCUtilTest.java

package org.scoula.jdbc_ex.common; // JDBCUtil과 동일 패키지
class JDBCUtilTest {
    @Test
    @DisplayName("jdbc_ex에 접속")
    void getConnection() throws SQLException {
        try (Connection conn = JDBCUtil.getConnection()){
            System.out.println("DB 연결 성공쓰~");
        } // catch 없음 -> 예외 처리 안하겠다는 뜻 , 자동 닫기를 하기 위해 try문을 쓴 것
    }
}

2) Statement 생성

▶ Statement

-SQL문 실행 클래스
-Connection 객체를 통해 생성: Statement stmt = conn.createStatement();


  • SQL 실행 메서드

    • ResultSet executeQuery(SQL문) : SELECT
    • int executeUpdate(SQL문) : INSERT, UPDATE, DELETE

  • ResultSet

    • 컬럼 값 추출
      - getXxxx("컬럼명")
      - getString(), getInt(), getLong(), getDouble(), getDate()

▶ PreparedStatement

-Statement는 단순한 문장은 쉬운데 변수 처리를 해야하는 복잡한 문장에는 부적합 ⇨ PreparedStatement

  • SQL문에 값을 넣을 때 파라미터화 해서 처리
    String sql = "INSERT INTO USERS (ID, PASSWORD, NAME, ROLE)" + "VALUES(?, ?, ?, ?)";

  • Connection 객체를 통해 생성 : PreparedStatment pstmt = conn.prepareStatement(sql);

  • 파라미터 설정
    • pstmt.setXxxx(파라미터번호, 값)
    • setString(), setInt(), setLong(), setDouble()

  • SQL문 실행
    : int count = pstmt.executeUpdate()'

Statement로 Insert문 실행

String sql = "INSERT INTO USERS (ID, PASSWORD, NAME, ROLE)" + "VALUES('member2', 'member123', '일반회원', 'USER');
int count = stmt.executeUpdate(sql);

// 값을 변수로 대체할 경우
String userid = "member2";
String password = "member123";
String name = "일반회원";
String role = "USER";
String sql = "INSERT INTO USERS (ID, PASSWORD, NAME, ROLE)" + "VALUES('" + userid + "','" + password + "','" + name + "','" + role + "')";
// -> PreparementStatement로 처리

CRUD 테스트

CrudTest.java

// 테스트 케이스는 실행되는 순서가 정해져있지 X
// 순서 지정을 위해 TestMethodOrder 사용
// OrderAnnotion : 넘버링 한 순서대로 실행
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class CrudTest {
    Connection conn = JDBCUtil.getConnection();

    @AfterAll
    static void tearDown() {
        JDBCUtil.close();
    }

    // 테스트 케이스
    @Test
    @DisplayName("새로운 User 등록")
    @Order(1)
    public void insertUser() throws SQLException {
        String sql = "INSERT INTO USERS (ID, PASSWORD, NAME, ROLE) VALUES (?, ?, ?, ?)";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) { // DB컴파일
            pstmt.setString(1, "scoula"); // 1부터 시작
            pstmt.setString(2, "scoula123");
            pstmt.setString(3, "스콜라");
            pstmt.setString(4, "USER");

            int count = pstmt.executeUpdate();
            Assertions.assertEquals(1, count); // 1이면 통과, 아니면 예외 발생
        }
    }

    @Test
    @DisplayName("User 목록 추출")
    @Order(2)
    public void selectUser() throws SQLException {
        String sql = "SELECT * FROM USERS";
        try (Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql);
             ) {
            while (rs.next()) {
                System.out.println(rs.getString("name"));
            }

        }
    }

    @Test
    @DisplayName("특정 User 검색")
    @Order(3)
    public void selectUserById() throws SQLException {
        String userid = "scoula";
        String sql = "SELECT * FROM USERS WHERE ID = ?";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, userid);
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    System.out.println(rs.getString("name"));
                } else {
                    throw new SQLException("scoula not found");
                }
            }
        }
    }

    @Test
    @DisplayName("특정 User 수정")
    @Order(4)
    public void updateUser() throws SQLException {
        String userid = "scoula";
        String sql = "UPDATE USERS SET NAME = ? WHERE ID = ?";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, "스콜라 수정");
            pstmt.setString(2, userid);
            int count = pstmt.executeUpdate();
            Assertions.assertEquals(1, count);
        }
    }

    @Test
    @DisplayName("특정 User 삭제")
    @Order(5)
    public void deleteUser() throws SQLException {
        String userid = "scoula";
        String sql = "DELETE FROM USERS WHERE ID = ?";
        try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, userid);
            int count = pstmt.executeUpdate();
            Assertions.assertEquals(1, count);
        }
    }
}



단위 테스트

  • 단위 테스트 = 메서드 단위 테스트
  • 모든 코드는 테스트가 되어야 함
  • 테스트를 통과한 코드만 사용해서 프로그래밍 해야 함

⇨ 검증 코드는 따로 관리 되어야 함, 테스트 하는 메커니즘도 필요

  • main만 배포
  • test에 사용하는 라이브러리 : JUnit (Gradle default)

JUnit

  • 단위 테스트 라이브러리
  • test 폴더 자동 생성 - 실제 코드와 테스트 코드 분리해서 관리
  • 메서드마다 새로운 인스턴스 만듦

Annotation

: 테스트 용이라는 어노테이션 붙음

  • @DisplayName("테스트이름") : 테스트 구분 제목
  • @Test : 테스트 메서드

Assertions (단정문)

: 실패하면 예외를 발생시킬지 판정 내림

  • assertEquals(기대값, 실제값)
    : 실제값과 기대값이 다르면 테스트 실패 ⇨ 예외 발생

JUnit으로 테스트 클래스 만들기

src/test/java
JUnitTest.java

public class JUnitTest {

    @DisplayName("1 + 2 는 3이다") // 메서드별로 이름을 붙여줌
    @Test
    public void junitTest() {
        int a = 1;
        int b = 2;
        int sum = 3;

        Assertions.assertEquals(a + b, sum); // 기대값, 실제값
    }
}

  • int sum = 4로 주었을 때 (기대값 ≠ 실제값)

JUnit 주요 어노테이션

  • JUnit은 메서드마다 새로운 인스턴스를 만듦
  • 공통적인 작업(ex.DB연결) 有 - 시작할 때 한번~ (BeforeAll)
  • @BeforeAll
    • 전체 테스트 실행 전 1회 호출
    • static 메서드로 정의
  • @BeforeEach
    • 각 테스트 케이스마다 실행 전 호출
  • @AfterEach
    • 각 테스트 케이스마다 실행 후 호출
  • @AfterAll
    • 전체 테스트 실행 후 1회 호출
    • static 메서드로 정의

JUnitCycleTest.java

public class JUnitCycleTest {
    @BeforeAll // 전체 테스트 시작 전 1회 실행, static
    static void beforeAll() {
        System.out.println("@BeforeAll");
    }
    @BeforeEach // 테스트케이스 시작 전 마다 실행
    public void beforeEach() {
        System.out.println("@BeforeEach");
    }
    @Test // 1번 테스트 케이스
    public void test1() {
        System.out.println("test 1");
    }
    @Test // 2번 테스트 케이스
    public void test2() {
        System.out.println("test 2");
    }
    @AfterEach // 테스트 케이스 종료 전 마다 실행
    public void afterEach() {
        System.out.println("@AfterEach");
    }
    @AfterAll // 전체 테스트를 마치고 종료하기 전 1회, static
    static void afterAll() {
        System.out.println("@AfterAll");
    }
}


[실습]


jdbc_ex 데이터베이스 생성

CREATE DATABASE jdbc_ex;

사용자 준비

CREATE USER 'jdbc_ex'@'%' IDENTIFIED BY 'jdbc_ex';
GRAND ALL PRIVILEGES ON jdbc_ex.* TO 'jdbc_ex'@'%';
FLUSH PRIVILEGES;
  • 'jdbc_ex'@'%'
    • 'jdbc_ex' : user id
    • '%' : 접속 ip, (%: 어디서든지 가능, 제한X)
  • INDENTIFIED BY 'jdbc_ex'
    • 'jdbc_ex' : 비밀번호

라이브러리 설치

build.gradle

dependencies {
    implementation 'com.mysql:mysql-connector-j:8.3.0'
    compileOnly 'org.projectlombok:lombok:1.18.30'
    annotationProcessor 'org.projectlombok:lombok:1.18.30'
    
    testCompileOnly 'org.projectlombok:lombok:1.18.30'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'

    testImplementation platform('org.junit:junit-bom:5.10.0')
    testImplementation 'org.junit.jupiter:junit-jupiter'
}

test~ : 단위테스트(JUnit)를 위한 설정

흔하게 쓰는 라이브러리이기 때문에 따로 저장~!


Intellij Datasource 기능 설정

: 매번 워크벤치에서 SQL X, Intellij에서 SQL 처리 O

⇨ 프로젝트 단위로 관리되는 정보
⇨ 프로젝트를 새로 만들 때마다 새로 추가 해줘야 함

SQL 쿼리 실행

sql파일 생성 - DB 연결

데이터 준비

jdbc_ex.sql

USE jdbc_ex;

DROP TABLE IF EXISTS USERS;

CREATE TABLE USERS (
    ID VARCHAR(12) NOT NULL PRIMARY KEY,
    PASSWORD VARCHAR(12) NOT NULL,
    NAME VARCHAR(30) NOT NULL,
    ROLE VARCHAR(6) NOT NULL
);

INSERT INTO USERS (ID, PASSWORD, NAME, ROLE)
VALUES('guest', 'guest123', '방문자', 'USER'),
      ('admin', 'admin123', '관리자', 'ADMIN'),
      ('member', 'member123', '일반회원', 'USER');

SELECT * FROM USERS;

0개의 댓글