1. Lombok 라이브러리
- Lombok을 사용하면 자바에서 클래스 작성 시 getter / setter 생성 또는 생성자 함수 정의하는 작업을 간단한 annotation 추가만으로 끝낼 수 있음
- getter / setter: @Getter, @Setter, @Data 등을 이용해 자동 생성
- toString(): @ToString을 이용하여 toString() 메서드 자동 생성
- equals() / hashCode(): @EqualsAndHashCode를 이용한 자동 생성
- 생성자 자동 생성: @AllArgsConstructoe, @NoArgsConstructor 등을 이용한 생성자 자동 생성
- 빌더 생성: @Builder를 이용한 빌더 패턴 코드 생성
1) Lombok 라이브러리 추가
- https://projectlombok.org/에서 build.gradle에 필요한 설정 추가 가능
- 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());
}
}
- 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);
}
}
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));
}
}
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);
}
}
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);
}
}
'back-end > Java' 카테고리의 다른 글
[자바 웹 개발 워크북] 3.1 - 세션과 필터 (0) | 2023.01.09 |
---|---|
[자바 웹 개발 워크북] 2.3 - 웹 MVC와 JDBC의 결합 (1) | 2023.01.06 |
[자바 웹 개발 워크북] 2.1 - JDBC 프로그래밍 준비 (0) | 2023.01.04 |
[자바 웹 개발 워크북] 1.5 - 모델(Model) (0) | 2023.01.03 |
[자바 웹 개발 워크북] 1.4 - HttpServlet (0) | 2023.01.02 |