package org.example;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import java.sql.SQLException;
import static org.assertj.core.api.Assertions.assertThat;
public class UserDaoTest {
@BeforeEach
void setUp() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("db_schema.sql"));
DatabasePopulatorUtils.execute(populator, ConnectionManager.getDataSource());
}
@Test
void createTest() throws SQLException {
UserDao userDao = new UserDao();
userDao.create(new User("wizard", "password", "name", "email"));
User user = userDao.findByUserId("wizard");
assertThat(user).isEqualTo(new User("wizard", "password", "name", "email"));
}
}
@BeforeEach
void setUp() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("db_schema.sql"));
DatabasePopulatorUtils.execute(populator, ConnectionManager.getDataSource());
@BeforeEach 어노테이션은 각 테스트 메소드가 실행되기 전에 실행되는 설정 메소드 이다.
이 메소드는 데이터베이스 스키마를 초기화 하기 위해 사용된다 ResourceDatabase Populator 는 데이터베이스 초기화를 위한 스크립트를 로드하는 데 사용되며 db_schema.sql 스크립트를 클래스 패스에서 로드하여 실행한다 이로써 각 테스트 메소드가 실행될 때마다 일관된 데이터베이스 상태가 보장된다.
@Test
void createTest() throws SQLException {
UserDao userDao = new UserDao();
userDao.create(new User("wizard", "password", "name", "email"));
User user = userDao.findByUserId("wizard");
assertThat(user).isEqualTo(new User("wizard", "password", "name", "email"));
createTest() 메소드는 UserDao 클래스의 create() 메소드를 테스트한다.
create() 메소드는 사용자를 데이터베이스에 추가하고 그 사용자를 findByUserId() 메소드를 사용하여 다시 가져 온 후 예상된 결과와 비교하는 작업을 수행한다.
테스트에서는 먼저 UserDao 클래스의 인스턴스를 만들고
create() 메소드를 사용하여 새 사용자를 생선한다 그런 다음 findByUserId() 메소드를 사용하여 같은 사용자를 데이터베이스에서 가져오고 assertThat() 을 사용하여 예상 결과가 일치하는지 검사한다.
이 테스트 코드의 목적은 UserDao 클래스의 create() 및 findByUserId() 메소드가 정상적으로 작동하는지 확인한다
각 테스트 실행 전에 데이터베이스를 초기화하여 일관된 테스트 환경을 만든다 만약 테스트에 문제가 있다면 assertThat() 에서 오류를 발생시키므로 문제를 식별하고 해결할 수 있게 된다.
package org.example;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class ConnectionManager {
private static final String DB_DRIVER = "org.h2.Driver";
private static final String DB_URL = "jdbc:h2:mem://localhost/~/jdbc-practice;MODE=MySQL;DB_CLOSE_DELAY=-1";
private static final String DB_USERNAME = "sa";
private static final String DB_PW = "";
private static final int MAX_POOL_SIZE = 40;
private static final DataSource ds;
static {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setDriverClassName(DB_DRIVER);
hikariDataSource.setJdbcUrl(DB_URL);
hikariDataSource.setUsername(DB_USERNAME);
hikariDataSource.setPassword(DB_PW);
hikariDataSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariDataSource.setMinimumIdle(MAX_POOL_SIZE);
ds = hikariDataSource;
}
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
public static DataSource getDataSource() {
return ds;
}
}
이 코드는 데이터베이스 연결 관리를 담당하는 ConnectionManager 클래스를 정의하는 Java 코드이다.
이 클래스는 HikariCP 라이브러리를 사용하여 데이터베이스 연결 풀을 설정하고 데이터베이스 연결을 관리한다
private static final String DB_DRIVER = "org.h2.Driver";
private static final String DB_URL = "jdbc:h2:mem://localhost/~/jdbc-practice;MODE=MySQL;DB_CLOSE_DELAY=-1";
private static final String DB_USERNAME = "sa";
private static final String DB_PW = "";
private static final int MAX_POOL_SIZE = 40;
이 부분은 데이터베이스 연결에 필요한 여러 상수를 정의한다 여기에는 데이터베이스 드라이버 클래스, 데이터베이스 URL, 사용자 이름 , 암호 및 최대 연결 풀 크기 (MAX_POOL_SIZE)가 포함된다.
private static final DataSource ds;
static {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setDriverClassName(DB_DRIVER);
hikariDataSource.setJdbcUrl(DB_URL);
hikariDataSource.setUsername(DB_USERNAME);
hikariDataSource.setPassword(DB_PW);
hikariDataSource.setMaximumPoolSize(MAX_POOL_SIZE);
hikariDataSource.setMinimumIdle(MAX_POOL_SIZE);
ds = hikariDataSource;
}
ConnectionManager 클래스의 static 초기화 블록에서 데이터베이스 연결 풀을 설정한다 .
HikariCP의 HikariDataSource 클래스를 사용하여 데이터베이스 연결 풀을 구성하고 위에 정의한 상수들을 사용하여 데이터베이스 연결에 필요한 정보를 설정한다 설정이 완료된 HIkariDataSource 인스턴스는 ds 변수에 저장된다.
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
getConnection 메서드는 데이터베이스 연결을 반환한다 이 메서드에서는 ds.getConnection() 을 호출하여 데이터베이스 연결을 얻고 예외가 발생한 경우 SQLEception 을 처리하고 예외를 던진다.
public static DataSource getDataSource() {
return ds;
}
}
getDataSource 메서드는 설정된 데이터베이스 연결 풀을 반환한다 이것은 데이터베이스 연결을 직접 다루지 않고
DataSource 인터페이스를 통해 데이터베이스 연결을 얻을 수 있게 해준다.
이 코드는 HikariCP를 사용하여 데이터베이스 연결 관리를 효율적으로 처리하는 방법을 보여준다
HikariCP는 경량이며 고성능인 데이터베이스 연결 풀 라이브러리로 데이터베이스 연결을 효과적으로 관리하고 사용할 수 있도록 도와준다.
package org.example;
import java.sql.SQLException;
public class UserDao {
public void create(User user) throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
String sql = "INSERT INTO USERS VALUES (?, ?, ?, ?)";
jdbcTemplate.executeUpdate(sql, pstmt -> {
pstmt.setString(1, user.getUserId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
});
}
public User findByUserId(String userId) throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
String sql = "SELECT userId, password, name, email FROM USERS WHERE userid = ?";
return (User) jdbcTemplate.executeQuery(sql,
pstmt -> pstmt.setString(1, userId),
resultSet -> new User(
resultSet.getString("userId"),
resultSet.getString("password"),
resultSet.getString("name"),
resultSet.getString("email")
));
}
}
이 코드는 데이터베이스와 상호 작용하는 UserDao 클래스의 메서드를 정의하는 Java 코드이다 이 클래스는 JDBC 를 사용하여 데이터베이스에 쿼리를 실행하고 사용자를 생성하고 검색하는 기능을 제공한다.
public void create(User user) throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
create 메서드는 User 객체를 받아 데이터베이스에 사용자를 추가하는 역활을 한다 JdbcTemplate 클래스는 데이터베이스 작업을 수행하는 데 사용 된다.
String sql = "INSERT INTO USERS VALUES (?, ?, ?, ?)";
이 부분에서는 SQL 쿼리를 정의한다 이쿼리는 USERS 테이블에 새 사용자 정보를 추가하는 역활을 한다. ? 는 바인딩 변수로 나중에 값을 설정할 것이다.
jdbcTemplate.executeUpdate(sql, pstmt -> {
pstmt.setString(1, user.getUserId());
pstmt.setString(2, user.getPassword());
pstmt.setString(3, user.getName());
pstmt.setString(4, user.getEmail());
jdbcTemplate.executeUpdate() 메서드를 호출하여 SQL 쿼리를 실행한다
이 메서드는 SQL 쿼리와 바인딩 변수에 대한 설정을 입력으로 받아 데이터베이스에 쿼리를 실행한다 여기서는 pstmt 람다 표현식을 사용하여 바인딩 변수에 값(사용자 정보)을 설정한다.
public User findByUserId(String userId) throws SQLException {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
findByUserId 메서드는 특정 사용자 ID를 기반으로 사용자 정보를 데이터베이스에서 검생하는 역활을 한다.
마찬가지로 JdbcTemplate 클래스를 사용하여 데이터베이스 작업을 수행한다.
String sql = "SELECT userId, password, name, email FROM USERS WHERE userid = ?";
이 부분에서는 SELECT 쿼리를 정의한다 이 쿼리는 USERS 테이블에서 사용자 ID에 해당하는 정보를 검색한다.
return (User) jdbcTemplate.executeQuery(sql,
pstmt -> pstmt.setString(1, userId),
resultSet -> new User(
resultSet.getString("userId"),
resultSet.getString("password"),
resultSet.getString("name"),
resultSet.getString("email")
));
jdbcTemplate.executeQuery() 메서드를 호출하여 SQL 쿼리를 실행한다 이메서드는 SQL 쿼리 바인딩 변수 설정 및 결과 집합 처리를 위한 람다 표현식 입력으로 받는다 검색 결과는 resutlSet 람다 표현식을 사용하여 User 객체로 변환되어 반환한다.
package org.example;
import java.util.Objects;
public class User {
private final String userId;
private final String password;
private final String name;
private final String email;
public User(String userId, String password, String name, String email) {
this.userId = userId;
this.password = password;
this.name = name;
this.email = email;
}
public String getUserId() {
return userId;
}
public String getPassword() {
return password;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(userId, user.userId) && Objects.equals(password, user.password) && Objects.equals(name, user.name) && Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(userId, password, name, email);
}
}
User 클래스는 사용자의 아이디,비밀번호,이름,이메일과 같은 정보를 갖고 있으며 객체 생성 후 변결할 수 없는 불변 클랙스로 설계되어 있다.
private final String userId;
private final String password;
private final String name;
private final String email;
userId, password, name, email는 모두 사용자 정보를 나타내는 문자열(String) 형식의 필드
private final 키워드를 사용하여 이러한 필드를 불변하게 만든다.
따라서 이러한 필드의 값은 객체가 생성된 후에 변경할 수 없다.
public User(String userId, String password, String name, String email) {
this.userId = userId;
this.password = password;
this.name = name;
this.email = email;
User 클래스의 생성자 이다 생성자는 클래스의 객체를 초기화 할 때 사용된다. 생성자는 사용자 정보를 입력으로 받아User 객체를 생성한다 이 생성자는 분변 객체를 생성하기 위해 필드에 값을 할당한 후 변경할 수 없도록 만든다.
public String getUserId() {
return userId;
}
public String getPassword() {
return password;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
User 클래스의 각 필드에 대한 getter 메서드를 정의한다 이러한 getter 메서드를 통해 객체 필드 값을 읽을 수 있다.
필드는 private 로 선언되어 있으므로 외부에서 직접 접근할 수 없고 getter 메서드를 통해 필드 값을 안전하게 가져올 수 있다.
package org.example;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcTemplate {
public void executeUpdate(String sql, PreparedStatementSetter pss) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(sql);
pss.setPreparedStatement(pstmt);
pstmt.executeUpdate();
} finally {
if (pstmt != null) {
pstmt.close();
}
if (con != null) {
con.close();
}
}
}
public Object executeQuery(String sql, PreparedStatementSetter pss, RowMapper rowMapper) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(sql);
pss.setPreparedStatement(pstmt);
rs = pstmt.executeQuery();
Object obj = null;
if (rs.next()) {
return rowMapper.mapRow(rs);
}
return obj;
} finally {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (con != null) {
con.close();
}
}
}
}
JdbcTemplate 를 정의하는 코드이다
JdbcTemplate클래스는 데이터베이스 쿼리를 실행하고 결과를 처리하는 메서드를 제공한다.
public void executeUpdate(String sql, PreparedStatementSetter pss) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
executeUpdate 메서드는 SQL 쿼리를 실행하여 데이터베이스를 업데이트한다. sql 매개변수는 실행할 SQL 쿼리를 나타내며 PreparedStatementSetter 객체인 pss 는 바인딩 변수를 설정하는데 사용된다.
Connection, PreparedStatement, pss 객체를 초기화.
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(sql);
pss.setPreparedStatement(pstmt);
pstmt.executeUpdate();
} finally {
if (pstmt != null) {
pstmt.close();
}
if (con != null) {
con.close();
}
try 블록에서 데이터베이스 연결을 가져오고 PreparedStatement 를 설정하며 pss를 사용하여 바인딩 변수를 설정한 후 executeUpdate() 메서드를 호출하여 SQL 쿼리를 실행한다 finally 블록에서 PreparedStatement 및 Connection을 닫아 리소스를 해제 한다.
public Object executeQuery(String sql, PreparedStatementSetter pss, RowMapper rowMapper) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
executeQuery 메서드는 SQL 쿼리를 실행하여 결과를 조회한다.
sql 매개변수는 실행할 SQL 쿼리를 나타내며,
PreparedStatmentSetter 객체인 rowMapper 는 결과 집합을 객체로 매핑하는데 사용된다.
Connection, PreparedStatement, ResultSet, pss, rowMapper 객체를 초기화.
try {
con = ConnectionManager.getConnection();
pstmt = con.prepareStatement(sql);
pss.setPreparedStatement(pstmt);
rs = pstmt.executeQuery();
Object obj = null;
if (rs.next()) {
return rowMapper.mapRow(rs);
}
return obj;
} finally {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (con != null) {
con.close();
try 블록에서 데이터베이스 연결을 가져오고 PreparedStatement를 설정하며, pss를 사용하여 바인딩 변수를 설정한 후 executeQuery() 메서드를 호출하여 SQL 쿼리를 실행한다.
그런 다음 결과 집합을 반복하고 rowMapper를 사용하여 결과를 객체로 매핑 마지막으로 finally 블록에서 ResultSet, PreparedStatement, 및 Connection을 닫아 리소스를 해제한다.
JdbcTemplate 클래스는 데이터베이스 작업을 수행하는 메서드를 제공하고, 데이터베이스 연결을 관리하며, 리소스를 안전하게 해제한다
이러한 클래스는 데이터베이스 작업을 추상화하고 중복 코드를 줄이는 데 도움이된다.
package org.example;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public interface PreparedStatementSetter {
void setPreparedStatement(PreparedStatement pstmt) throws SQLException;
}
데이터베이스와 관련된 작업을 수행하는데 사용되며 주로 SQL 쿼리의 바인딩 변수를 설정하는 메서드를 포함하고 있다
PreparedStatement 객체를 매개변수로 받고 SQLException를 던질 수 있다.
이러한 인터페이스를 사용하면 다양한 데이터베이스 작업을 수행하는 클래스에서 PreparedStatement를 설정하는 논리를 재사용할 수 있다 예를 들어 SQL 쿼리의 바인딩 변수를 설정하는 방법이 각각 다른 여러 메서드가 있을 때, 이러한 메서드는 PreparedStatementSetter 인터페이스를 구현하여 일관된 방식으로 PreparedStatement를 설정할 수 있다. 이 것은 코드의 재사용 성과 유지보수성을 향상시킨다.
package org.example;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface RowMapper {
Object mapRow(ResultSet resultSet) throws SQLException;
}
이 인터페이스는 하나의 메서드인 mapRow를 선언 ResultSet 객체를 매개변수로 받고, SQLException을 던질 수 있다.
RowMapper 인터페이스의 유일한 메서드인 mapRow는 ResultSet 객체를 매개변수로 받는다 이 메서드의 목적은 ResultSet 객체에서 데이터를 읽고, 그 데이터를 Java 객체로 매핑하는 것 mapRow 메서드의 반환 유형은 Object로 선언되었지만, 실제로 반환되는 객체는 메서드를 구현한 클래스에 따라 다를 수 있다.
RowMapper 인터페이스는 주로 데이터베이스 조회 결과를 Java 객체로 변환하는데 사용된다 . 예를 들어, 데이터베이스로부터 조회한 행의 데이터를 ResultSet에서 읽고, 이 데이터를 도메인 객체로 변환하는 데 RowMapper를 사용할 수 있다.
이러한 인터페이스를 구현하여 데이터베이스 결과 집합을 객체로 변환하는 방법을 표준화하고, 코드의 재사용성과 유지보수성을 향상시킨다.
'JAVA > JDBC 프로그래밍' 카테고리의 다른 글
DB 커넥션 풀 개념 (0) | 2023.11.03 |
---|---|
JDBC 개념 (0) | 2023.11.03 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!