1. Lombok 라이브러리

  • Lombok을 사용하면 자바에서 클래스 작성 시 getter / setter 생성 또는 생성자 함수 정의하는 작업을 간단한 annotation 추가만으로 끝낼 수 있음
    • getter / setter: @Getter, @Setter, @Data 등을 이용해 자동 생성
    • toString(): @ToString을 이용하여 toString() 메서드 자동 생성
    • equals() / hashCode(): @EqualsAndHashCode를 이용한 자동 생성
    • 생성자 자동 생성: @AllArgsConstructoe, @NoArgsConstructor 등을 이용한 생성자 자동 생성
    • 빌더 생성: @Builder를 이용한 빌더 패턴 코드 생성

 

 1) Lombok 라이브러리 추가

  • jdbcex 프로젝트 내의 build.gradle 파일에서 dependencies를 아래처럼 변경
dependencies {
    compileOnly('javax.servlet:javax.servlet-api:4.0.1')

    testImplementation("org.junit.jupiter:junit-jupiter-api:${junitVersion}")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitVersion}")

    implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.8'

    compileOnly 'org.projectlombok:lombok:1.18.24'
    annotationProcessor 'org.projectlombok:lombok:1.18.24'

    testCompileOnly 'org.projectlombok:lombok:1.18.24'
    testAnnotationProcessor 'org.projectlombok:lombok:1.18.24'
}

 


실습

 1) TodoVO 클래스 작성

 

[자바 웹 개발 워크북] 2.1 - JDBC 프로그래밍 준비

1. MariaDB 설치·생성 1) MariaDB 설치 2) 데이터베이스 생성과 사용자 계정 추가 데이터베이스에서 필요한 작업을 명령하는 SQL 에디터인 HeidiSQL 프로그램이 같이 설치됨 '신규 ' 버튼을 눌러서 root 계

data-science-study.tistory.com

  - 앞서 데이터베이스에서 만든 tbl_todo 테이블의 데이터를 자바 객체로 처리하기 위해 테이블과 유사한 구조의 TodoVO 클래스와 객체 이용

  - Lombok을 이용하면 반복적으로 생성하는 코드를 줄여 DTO나 VO 작성 시 편리

package org.zerock.jdbcex.domain;

import lombok.Builder;
import lombok.Getter;
import lombok.ToString;

import java.time.LocalDate;

// VO는 주로 읽기 전용으로 사용하는 경우가 많으므로 @Getter 추가(getTno(), getTitle() 등을 호출 가능)
@Getter
// 객체 생성 시 빌더 패턴을 이용하기 위해 @Builder 추가(TodoVO.builder().build() 형태로 객체 생성 가능)
@Builder
@ToString
public class TodoVO {

  // tbl_todo 테이블의 칼럼들을 기준으로 작성
  private Long tno;
  private String title;
  private LocalDate dueDate;
  private boolean finished;
}

 

2. HikariCP 설정

  • 프로젝트에서 Connection의 생성은 Connection Pool인 HikaariCP 이용
  • build.gradle 파일의 dependencies에 관련 라이브러리 추가
dependencies {

    ...
    
    implementation group: 'com.zaxxer', name: 'HikariCP', version: '5.0.0'
}

 


실습

 2) Connection Pool 이용하기

  - HikariCP 이용을 위해 HikariConfig라는 타입의 객체를 생성해야 함

  - 이전에 만들었던 ConnectTests 파일을 HikariCP를 이용하는 테스트 메서드로 변경하여 작성

package org.zerock.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.sql.Connection;
import java.sql.DriverManager;

public class ConnectTests {

  @Test
  public void testHikariCP() throws Exception {

    HikariConfig config = new HikariConfig();
    config.setDriverClassName("org.mariadb.jdbc.Driver");
    config.setJdbcUrl("jdbc:mariadb://localhost:3306/webdb");
    config.setUsername("webuser");
    config.setPassword("비밀번호");
    config.addDataSourceProperty("cachePrepStmts", "true");
    config.addDataSourceProperty("prepStmtCacheSize", "250");
    config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

    HikariDataSource ds = new HikariDataSource(config);
    Connection connection = ds.getConnection();

    System.out.println(connection);
    connection.close();
  }
}

  - 기존과 동일하게 연결되지만 HikariCP를 통해 연결되었음을 첫 번째 줄에서 알 수 있음

  - 데이터베이스 연결을 많이 할수록 HikariCP 사용 여부에 따른 성능의 차이가 발생

  - 데이터베이스가 원격지에 떨어진 경우 네트워크 연결에 더 많은 시간을 소비해야하므로 HikariCP 사용 여부의 차이가 커짐


 

 1) TodoDAO와 @Cleanup

  • HikariCP를 이용할 수 있게 되면 다음에는 실제 SQL 처리를 전담하는 TodoDAO 구성
  • TodoDAO는 이전에 작성한 TodoService와 연동되어 데이터베이스와 TodoService 간의 연결 담당
  • TodoDAO에서 필요한 작업 수행 시 HikariDataSource를 이용하게 되므로 이에 대한 처리를 쉽게 사용할 수 있도록 ConnectionUtil 클래스를 enum으로 구성하여 사용
// ConnectionUtil
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import java.sql.Connection;

public enum ConnectionUtil {

  INSTANCE;
  private HikariDataSource ds;

  ConnectionUtil() {
    HikariConfig config = new HikariConfig();
    config.setDriverClassName("org.mariadb.jdbc.Driver");
    config.setJdbcUrl("jdbc:mariadb://localhost:3306/webdb");
    config.setUsername("webuser");
    config.setPassword("yong1998");
    config.addDataSourceProperty("cachePrepStmts", "true");
    config.addDataSourceProperty("prepStmtCacheSize", "250");
    config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

    ds = new HikariDataSource(config);
  }

  public Connection getConnection() throws Exception {
    return ds.getConnection();
  }

}
  • ConnectionUtil은 하나의 객체를 만들어서 사용하는 방식
  • HikariConfig를 이용해서 하나의 HikariDataSource를 구성
  • HikariDataSource는 getConnection()을 통해서 사용하며 외부에서는 ConnecetionUtil.INSTANCE.getConnection()을 통해 Connection을 얻을 수 있도록 함

 

  • TodoDAO에 ConnectionUtil을 사용하는 코드 추가
// TodoDAO
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class TodoDAO {

  // try-with-resources 기능을 이용하여 try() 내에서 선언된 변수들이 자동으로 close()될 수 있도록 함
  public String getTime() {
     String now = null;
     try(Connection connection = ConnectionUtil.INSTANCE.getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement("select now()");
         ResultSet resultSet = preparedStatement.executeQuery();
         ) {
       resultSet.next();

       now = resultSet.getString(1);
     }catch(Exception e){
       e.printStackTrace();
     }
     return now;
  }

}

 

  • DAO의 동작에 문제가 없는지 테스트 코드 작성
// TodoDAOTests
package org.zerock.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dao.TodoDAO;

public class TodoDAOTests {
  private TodoDAO todoDAO;

  @BeforeEach
  public void ready() {
    todoDAO = new TodoDAO();
  }

  @Test
  public void testTime() throws Exception {
    System.out.println(todoDAO.getTime());
  }
}

TodoDAO의 getTime이 작동하며 현재 시간이 출력됨을 확인

 

  • lombok의 @Cleanup: try-with-resource 대신 lombok의 @Cleanup을 이용해 더 깔끔한 코드 생성 가능
    • Cleanup이 추가된 변수는 해당 메서드가 끝날 때 close()가 호출되는 것을 보장
    • getTime()에 @Cleanup 적용한 코드
// TodoDAO
// try-catch 대신 @Cleanup을 사용하여 더 간결한 코드 작성
public String getTime2() throws Exception {
    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement("select now()");
    @Cleanup ResultSet resultSet = preparedStatement.executeQuery();

    resultSet.next();

    String now = resultSet.getString(1);

    return now;
}
    • @Cleanup을 이용하면 Lombok 라이브러리에 상당히 종속적인 코드를 작성하게 된다는 부담이 있지만 최소한의 코드로 close()가 보장된다는 장점

 


실습

 3) TodoDAO의 등록 기능 구현

  - TodoVO 객체를 데이터베이스에 추가하는 기능

  - TodoDAO에 insert() 메서드 구성해서 작성

// TodoDAO
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Cleanup;
import org.zerock.jdbcex.domain.TodoVO;

import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class TodoDAO {

  ...
  
  // insert() 메서드 작성
  public void insert(TodoVO vo) throws Exception {

    // sql 구문을 직접 작성하여 전달
    // ?는 나중에 전달할 데이터를 지정, setXXX를 이용하여 실제 값들을 지정(인덱스 번호가 0이 아닌 1부터 시작)
    String sql = "insert into tbl_todo (title, dueDate, finished) values (?, ?, ?)";

    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);

    preparedStatement.setString(1, vo.getTitle());
    // LocalDate타입을 지원하지 않으므로 java.sql.Date타입을 이용해 변환하여 추가
    preparedStatement.setDate(2, Date.valueOf(vo.getDueDate()));
    preparedStatement.setBoolean(3, vo.isFinished());

    // insert()는 파아미터로 입력된 TodoVO객체의 정보를 이용해 DML 구문으르 실행하기에 executeUpdate()를 실행하도록 구성
    preparedStatement.executeUpdate();
  }

}

 

  - TodoDAOTests에 insert() 메서드 테스트용 코드 작성

// TodoDAOTests
package org.zerock.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;

import java.time.LocalDate;

public class TodoDAOTests {
  private TodoDAO todoDAO;

  @BeforeEach
  public void ready() {
    todoDAO = new TodoDAO();
  }

  @Test
  public void testInsert() throws Exception {
    TodoVO todoVO = TodoVO.builder()
        .title("Sample Title...")
        .dueDate(LocalDate.of(2022,12,31))
        .build();
    todoDAO.insert(todoVO);
  }
}

테스트 결과, 새로운 데이터가 tbl_todo 테이블에 잘 insert 된 것을 확인

 

 4) TodoDAO의 목록 기능 구현

  - TodoDAO를 이용해서 tbl_todo 내의 모든 데이터 가져오는 기능 구현

  - 테이블의 각 행이 TodoVO의 객체가 되고, 모든 TodoVO를 담을 수 있도록 List<TodoVO>타입을 return

// TodoDAO
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Cleanup;
import org.zerock.jdbcex.domain.TodoVO;

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

public class TodoDAO {

  ...

  // SelectAll() 메서드 작성
  public List<TodoVO> selectAll() throws Exception {

    String sql = "select * from tbl_todo";
    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);

    @Cleanup ResultSet resultSet = preparedStatement.executeQuery();

    List<TodoVO> list = new ArrayList<>();

    while (resultSet.next()) {
      TodoVO vo = TodoVO.builder()
          .tno(resultSet.getLong("tno"))
          .title(resultSet.getString("title"))
          .dueDate(resultSet.getDate("dueDate").toLocalDate())
          .finished(resultSet.getBoolean("finished"))
          .build();
      list.add(vo);
    }
    return list;
  }

}

 

  - TodoDAOTests에 selectAll() 테스트용 코드 작성

// TodoDAOTests
package org.zerock.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;

import java.time.LocalDate;
import java.util.List;

public class TodoDAOTests {
  private TodoDAO todoDAO;

  @BeforeEach
  public void ready() {
    todoDAO = new TodoDAO();
  }

  @Test
  public void testList() throws Exception {
    List<TodoVO> list = todoDAO.selectAll();
    list.forEach(vo -> System.out.println(vo));
  }
}

tbl_todo 테이블의 모든 열이 출력되는지 확인

 

 5) TodoDAO의 조회 기능 구현

  - selectAll()은 tbl_todo의 모든 데이터를 TodoVO 객체로 만들어주는 기능

  - 경우에 따라 특정 번호(tno)의 데이터만 가져오는 기능 필요

  - selectOne() 메서드 작성(특정 번호(tno)를 파라미터로 받고, todoVO가 return)

// TodoDAO
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Cleanup;
import org.zerock.jdbcex.domain.TodoVO;

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

public class TodoDAO {

  ...
  
  // SelectOne() 메서드 작성
  public TodoVO selectOne(Long tno) throws Exception {
    String sql = "select * from tbl_todo where tno = ";
    
    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);
    
    preparedStatement.setLong(1, tno);
    
    @Cleanup ResultSet resultSet = preparedStatement.executeQuery();
    
    resultSet.next();
    TodoVO vo = TodoVO.builder()
        .tno(resultSet.getLong("tno"))
        .title(resultSet.getString("title"))
        .dueDate(resultSet.getDate("dueDate").toLocalDate())
        .finished(resultSet.getBoolean("finished"))
        .build();
    return vo;
  }

}

 

  - TodoDAOTests에 selectOne() 테스트용 코드 작성

// TodoDAOTests
package org.zerock.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;

import java.time.LocalDate;
import java.util.List;

public class TodoDAOTests {
  private TodoDAO todoDAO;

  @BeforeEach
  public void ready() {
    todoDAO = new TodoDAO();
  }

  @Test
  public void testSelectOne() throws Exception {
    // 이때 테스트용 tno는 반드시 tbl_todo 테이블에 존재하는 번호여야함
    Long tno = 1L;
    TodoVO vo = todoDAO.selectOne(tno);
    System.out.println(vo);
  }
}

tno가 1인 행을 조회했을 때 성공적으로 조회되는 것을 확인
tno가 7인 것을 조회했을 때, tbl_todo에 tno가 7인 것이 없으므로 오류 발생

 

 6) TodoDAO의 삭제 / 수정 기능 구현

  - 삭제 기능은 조회 기능과 비슷하지만 쿼리(select)가 아님

  - 수정 기능은 특정한 번호를 가진 데이터의 제목(title)과 만료일(dueDate), 완료 여부(finish)를 update하도록 구성

// TodoDAO
package org.zerock.jdbcex.dao;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Cleanup;
import org.zerock.jdbcex.domain.TodoVO;

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

public class TodoDAO {

  ...
  
  // deleteOne 메서드 작성
  public void deleteOne(Long tno) throws Exception {
    String sql = "delete from tbl_todo where tno = ?";

    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);

    // 삭제할 때도 특정 번호(tno)가 필요
    preparedStatement.setLong(1, tno);

    preparedStatement.executeUpdate();
  }

  // updateOne() 메서드 작성
  public void updateOne(TodoVO todoVO) throws Exception {
    String sql = "update tbl_todo set title = ?, dueDate = ?, finished = ? where tno = ?";

    @Cleanup Connection connection = ConnectionUtil.INSTANCE.getConnection();
    @Cleanup PreparedStatement preparedStatement = connection.prepareStatement(sql);

    preparedStatement.setString(1, todoVO.getTitle());
    preparedStatement.setDate(2, Date.valueOf(todoVO.getDueDate()));
    preparedStatement.setBoolean(3, todoVO.isFinished());
    preparedStatement.setLong(4, todoVO.getTno());

    preparedStatement.executeUpdate();
  }

}

 

  - TodoDAOTests에 selectOne() 테스트용 코드 작성

// TodoDAOTests
package org.zerock.dao;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zerock.jdbcex.dao.TodoDAO;
import org.zerock.jdbcex.domain.TodoVO;

import java.time.LocalDate;
import java.util.List;

public class TodoDAOTests {
  private TodoDAO todoDAO;

  @BeforeEach
  public void ready() {
    todoDAO = new TodoDAO();
  }

  // tno가 1인 첫 번째 데이터의 제목, 날짜, 완료 여부를 다음과 같이 update하도록 test
  @Test
  public void testUpdateOne() throws Exception {
    TodoVO todoVO = TodoVO.builder()
        .tno(1L)
        .title("Sample Title")
        .dueDate(LocalDate.of(2021,12,31))
        .finished(true)
        .build();
    todoDAO.updateOne(todoVO);
  }
}

첫 번째 행이 업데이트 된 tbl_todo 테이블

 

+ Recent posts