utf-8 설정
기존에 mysql을 사용하고 있기 때문에 port번호가 겹치지 않도록 3306
-> 3336
으로 변경했다.
(mysql과 mariadb는 동일한 포트번호를 사용한다.)
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
JUnit을 활용하여 테스트 코드를 작성할 수 있다.
@Test
: 테스트를 위한 메소드에 대한 정보 표기 public void
이어야 하며, 파라미터가 존재하지 않아야 한다.
Assertions.assertEquals(변수1, 변수2)
: 변수의 내용이 같은지 확인하는 메서드
testConnection()
테스트 코드
Class.forName("org.mariadb.jdbc.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:mariadb://localhost:3336/webdb",
"webuser",
"webuser");
Assertions.assertNotNull(connection);
connection.close();
Class.forName("JDBC 드라이버클래스")
: JDBC 드라이버 클래스를 메모리상으로 로딩. 해당 드라이버가 존재하지 않는 경우 예외 발생
Connection connection
: java.sql.Connection
은 데이터베이스와 네트워크를 연결하는 인터페이스이다.
DriverManager.getConnection("url", "user", "password")
: DB 내의 여러 정보를 이용하여 특정 데이터베이스에 연결
jdbc:mariadb://localhost:3336/webdb
jdbc 프로토콜을 사용하여 mariadb에 연결.localhost:3306
은 네트워크 연결 정보,webdb
는 연결하려는 DB 정보를 의미한다.
Assertions.assertNotNull()
: DB와 정상적으로 연결이 된다면 Connection 객체는 null이 아닐 것이다.
connection.close()
: DB와의 연결을 종료한다. JDBC 프로그램은 리소스 낭비를 방지하기 위해 DB 연결을 잠깐씩 맺고 종료한다. 반드시 작업이 완료되면 DB와의 연결을 종료해야 한다.
- 몇 개의 row가 추가/변경/삭제 되었는지
- 레코드로 반환
java.sql.Connection
- Connection 인터페이스⭐는 데이터베이스와 네트워크 상의 연결을 목적으로 한다.
- 실제 구현 클래스는 JDBC 드라이버 파일 내부의 클래스
- Connection은 반드시 close()해야 한다.⭐
Connection.close()
는 데이터베이스쪽에 연결을 끊어도 좋다는 신호를 주고 네트워크 연결 종료try~catch~finally
수동 종료 또는try~with~resources
를 통해 자동 종료Statement
또는PreParedStatement
와 같이 SQL을 실행할 수 있는 객체를 생성한다.
Connection connection = ...
PreparedStatement preparedStatement
= connection.prepareStatement("select * from tbl_todo”);
java.sql.Statement/PreparedStatement
- JDBC에서 SQL을 데이터베이스로 보내기 위해서
Statement/PreparedStatement
타입을 이용- 그 외 프로시저 호출을 위한
CallableStatement
도 있다.
PreparedStatement
: SQL문을 미리 전달하고 나중에 데이터를 보내는 방식Statement
: SQL 문 내부에 모든 데이터를 같이 전송하는 방식
- 실제 개발에서는
SQL injection
을 예방하기위해PreparedStatement
만을 이용하는 것이 관례Statement
도Connection
과 마찬가지로close()
를 통해서 종료해야 DB 내부의 메모리와 같이 사용했떤 자원들 즉각 정리됨.
setXXX()
: setInt(), setString(), setDate()와 같이 다양한 타입에 맞게 데이터 세팅executeUpdate()
: DML(insert/update/delete)을 실행하고 결과를 int타입으로 반환 (영향 받은 row의 개수)executeQuery()
: 쿼리(select) 실행.ResultSet
이라는 리턴 타입 이용
java.sql.ResultSet
PreparedStatement
를 이용해서insert/update/delete
를 처리하는 DML의 경우int
로 반환된다. 반면에
executeQuery
로select
를 실행한다면 DB에서 반환하는 레코드(행)을 읽어들이기 위해ResultSet
이라는 인터페이스가 이용된다. 이 인터페이스는 자바 코드에서 데이터를 읽기 때문에 getter 메소드를 사용해 필요한 타입으로 데이터를 읽는다.ResultSet
의 메소드 중next()
를 사용해서 다음 행(row)의 데이터를 읽을 수 있다.ResultSet
은 순차적으로 데이터를 읽도록 구성되어 있다.ResultSet
도 네트워크를 통해 데이터를 읽으므로 사용이 끝난 후에는 반드시close()
를 해주어야 한다.
Connection Pool과 DataSource
- JDBC는 필요한 순간에 잠시 DB와 네트워크로 연결하고 데이터를 보내고 받는 방식이다.
DB와의 연결(Connection)을 맺는 작업은 시간과 자원 소모가 많다. 여러 번 SQL을 실행하면 성능 저하가 발생하는 것이다.
JDBC는 Connection을 미리 생성해서 보관하며, 필요시에 꺼내쓰는 방식인 Connection Pool(커넥션 풀)⭐을 이용해 문제를 해결한다.
javax.sql.DataSource
인터페이스는 커넥션 풀을 자바에서 API형태로 지원하며, 커넥션 풀을 이용하는 라이브러리는 모두 DataSource 인터페이스를 구현하므로 이를 활용해서 JDBC 코드를 작성하게 된다.- Connection Pool은 기작성된 라이브러리를 이용하는 경우가 많은데, DBCP, C3PO, HikariCP 라이브러리를 이용한다.
DAO(Data Access Object)
- DAO는 데이터를 전문적으로 처리하는 객체이다.
DB의 접근과 처리를 전담하는 객체를 의미한다.- DAO는 주로 VO를 단위로 처리한다.
- DAO를 호출하는 객체는 DAO가 내부에 어던식으로 데이터를 처리하는지 알 수 없도록 구성한다.
VO(Value Object) 혹은 엔티티(Entity)
- OOP는 데이터를 객체 단위로 처리한다.
테이블의 한 행(row)는 자바 프로그램에서 하나의 객체가 된다.- DB에서 하나의 데이터는 엔티티(entity)라고 하며 자바에서는 이를 처리하기 위해 테이블과 유사한 구조의 클래스를 만들어서 객체로 처리한다.
이것이 '값을 보관하는 용도'의 VO(Value Object)이다.- DTO는 각 계층을 오고 가는 데 사용되며, getter/setter를 모두 사용하지만 VO는 데이터베이서의 엔터티를 자바 객체로 표현되며 데이터 자체를 의미하여 getter만을 이용하는 경우가 대부분이다
인파 | IntelliJ - Lombok 설치 방법 & 오류 해결
- 롬복(Lombok)⭐은 자바 클래스에서 반복적으로 작성되는 getter, setter, toString, 생성자 코드 등의 소스들을, 어노테이션(Annotation)을 사용하여 생략할 수 있도록 컴파일 시점에 자동으로 생성해주는 라이브러리이다.
코끼리 | [JAVA] LOMBOK이란? LOMBOK 적용하는 방법
- 편리성을 제공하는 라이브러리일수록 주의할 사항으로 API 설명과 내부 동작을 어느정도 숙지하고 사용해야 한다는 점 입니다. 예를 들어 Lombok의
@Data
어노테이션이나@ToString
어노테이션으로 자동 생성되는toString()
메서드는 순환 참조 또는 무한 재귀 호출 문제로 인해 StackOverflowError가 발생할수도 있습니다. 물론 이 문제를 인지한 Lombok에서 해결할 수 있는 속성들을 제공하지만 롬복이 편리하다는 이유만으로 마구 사용한다면 여러가지 문제가 발생할 수 있다는 것입니다.
자바 웹 개발 워크북 118p
Lombok 라이브러리를 이용하면 간편해지는 작업들
- getter/setter 관련 : @Getter, @Setter, @Data 등을 이용해서 자동 생성
- toString() : @ToString을 이용한 toString()메소드 자동 생성
- equals()/hashCode() : @EqualsAndHashCode를 이용한 자동생성
- 생성자 자동 생성 : @ALlArgsConstructor, @NoArgsConstructor 등을 이용한 생성자 자동 생성
- 빌더 생성 : @Builder를 이용하여 빌더 패턴 코드를 생성. (setter를 편리하게 함)
build.gradle에 롬복 라이브러리 의존성 추가 (추가 후에는 그래들 반드시 갱신)
repositories {
mavenCentral()
}
dependencies {
compileOnly 'org.projectlombok:lombok:1.18.28'
annotationProcessor 'org.projectlombok:lombok:1.18.28'
testCompileOnly 'org.projectlombok:lombok:1.18.28'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.28'
}
코드 연구소 | DB 커넥션 풀(Connection pool)이란? HikariCP란?
- 커넥션 풀은 데이터베이스와 연결된 커넥션을 미리 만들어 놓고 이를 pool로 관리하는 것이다. 즉, 필요할 때마다 커넥션 풀의 커넥션을 이용하고 반환하는 기법이다. 이처럼 미리 만들어 놓은 커넥션을 이용하면 Connection에 필요한 비용을 줄일 수 있다. 따라서 DB에 빠르게 접속할 수 있다.
- 또한 커넥션 풀을 사용하면 커넥션 수를 제한할 수 있어서 과도한 접속으로 인한 서버 자원 고갈을 방지할 수 있으며 DB 접속 모듈을 공통화해 DB 서버의 환경이 바뀔 경우 유지보수를 쉽게 할 수 있다.
:(사진 출처 - https://javarevisited.blogspot.com/2018/07/how-to-setup-jndi-database-connection-pool-tomcat-spring-example-tutorial.html#axzz83pG7yfSj)- HikariCP는 가벼운 용량과 빠른 속도를 가지는 JDBC의 커넥션 풀 프레임워크이다. SpringBoot를 사용해 본 사람이라면 한 번쯤은 이름을 들어본 적이 있을 것이다. SpringBoot는 커넥션 풀 관리를 위해 HikariCP를 사용한다.
- DB 연결을 많이 할수록 HikariCP를 이용하는 것과 사용하지 않는 것에는 상당한 성능 차이가 발생한다. 특히 데이터베이스가 원격지에 떨어져 있는 경우에는 네트워크 연결에 더 많은 시간을 소비해야 하기 때문에 (히카리CP를 사용하고 안하고의) 차이가 더 커진다.
- (자바 웹 개발 워크북 122p 발췌)
dependencies {
compileOnly('jakarta.servlet:jakarta.servlet-api:5.0.0')
... 생략...
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation 'com.zaxxer:HikariCP:5.0.0'
import com.zaxxer.hikari.HikariConfig;
와import com.zaxxer.hikari.HikariDataSource;
를 사용해 커넥션 풀을 구성한다.HikariConfig
객체를 생성한다.setXXX()
를 통해 드라이버클래스명, JdbcUrl, Username, Password 등을 구성하며, addDataSourceProperty("키"-"밸류")
로 데이터소스속성을 추가한다.HikariDataSource
객체의 생성자에 담아 데이터소스 객체를 생성한다.Connection connection = ds.getConnection();
으로 커넥션 객체를 얻어와 Connection
인터페이스에 담는다.connection.close();
//🔥 사용한 자원은 반드시 닫아준다.
- DAO는 데이터베이스나 외부 파일 시스템과 같은 영속성 메커니즘에 접근하여 데이터의 CRUD(Create, Read, Update, Delete) 처리를 담당하는 객체 혹은 그러한 패턴을 의미한다.
package org.zerock.jdbcex.dao;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.Connection;
//⭐ enum 클래스
public enum ConnectionUtil {
INSTANCE; //⭐ 상수 하나로 싱글톤 패턴을 구현한다.
//⭐ 싱글톤 패턴이므로 ConnectionUtil.INSTANCE.getConnection()에 의해서 하나의 ds가 반환됨.
private HikariDataSource ds; // 히카리데이터소스 참조변수 ds
ConnectionUtil() {
//⭐ HikariConfig 객체에 DB와 연결에 대한 구성정보를 담는다.
//(드라이버, JDBCURL, 유저이름, 패스웓, 데이터소스속성)
HikariConfig config = new HikariConfig();
config.setDriverClassName("org.mariadb.jdbc.Driver");
config.setJdbcUrl("jdbc:mariadb://localhost:3336/webdb");
config.setUsername("webuser");
config.setPassword("webuser");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
//⭐ DB와 연결정보가 담긴 HikariConfig 객체를 ConnectionUtil 클래스의 히카리데이터소스 참조변수에 할당
this.ds = new HikariDataSource(config);
}
public Connection getConnection() throws Exception {
return ds.getConnection();
}
}
try(){}catch(예외){예외처리}
로 사용하며 try() 내에 선언된 변수들이 자동으로 close()된다. (단 try() 내에 선언된 변수들은 모두 Closeable이라는 인터페이스를 구현한 타입들이어야 한다.)
@BeforeEach
⭐ : Junit 애너테이션 모든 테스트 전에 해당 메서드 호출@CleanUp
: try-with-resource
보다 가독성 있는, 자원을 회수하는 애너테이션@CleanUp
이 추가된 변수는 해당 메소드가 끝날 때 close()
가 호출되는 것 보장void insert(TodoVO vo) throws Exception
:public void testInsert() throws Exception{
TodoVO todoVO = TodoVO.builder()
.title("Sample Title...")
.dueDate(LocalDate.now())
//finish 속성을 별도로 입력하지 않으면 default에 의해 0이 됨.
.build();
todoDAO.insert(todoVO);
}
Lombok의 @Builder 사용 :
해당 클래스 객체.builder()
로 시작하여 .멤버변수명()
으로 세팅한 다음 .build()
로 마무리하여 데이터를 세팅한다.
⭐빌더 패턴은 생성자와 달리 필요한 만큼만 데이터를 세팅할 수 있다.
Lombok @Builder의 동작 원리 : 클래스 레벨, 생성자 레벨, Singular
- 빌더 패턴에서는 한 번 설정한 속성을 여러번 메서드를 호출하여 다시 설정할 수 있어야 하기 때문에
final
키워드는 적합하지 않다.
public List<TodoVO> selectAll()
: select문의 결과로 받아오는 다수의 행들을 List<TodoVO>
로 변환하여 반환한다.TodoVO
List<TodoVO>
)TodoVO selectOne(Long tno)
: tno 제목 번호에 따른 단 하나의 레코드(row)를 자바 객체 TodoVO로 변환하여 얻어오기
String sql = "select * from tb1_todo where tno = ?";
: 동적쿼리
preparedStatement.setLong(1, tno);
: 몇 번 게시글을 조회할지 그 인자(tno
)를 넘겨 받기 때문에 동적쿼리의 와일드카드?
를 인자로 넘겨받은 tno
로 바꿔주어야 한다.
resultSet.next();
: while(resultSet.next())
문을 사용할 필요가 없다. 왜냐하면 쿼리의 실행 결과로 단 하나의 row만 반환되기 때문이다.
삭제 void deleteOne(Long tno)
수정 void updateOne(TodoVO vo)
String sql = "update tb1_todo set title =?, dueDate = ?, finished = ? where tno = ?";
: update 테이블 set 열1=? , 열2=?, 열3=?, ... where 조건
preparedStatement.setString(1, vo.getTitle());
: 와일드 카드 4개에 부합하도록 총 4개의 데이터를 입력해주어야 한다. 그 중 첫 번째?
는 인자로 전달된 vo의 제목이다.