본문 바로가기
Java

JDBC 실습 - CRUD, SOLID, 성능최적화, SQL예외처리, HikariCP

by 개발자공부 2024. 6. 18.
JDBC를 사용하여 학생 관리 시스템을 구축하기.
-학생의 정보를 데이터베이스에 저장하고 관리하는 간단한 시스템을 구축한다.
-학생 정보를 추가, 조회, 수정, 삭제할 수 있는 기능을 구현한다.

1. 기능 요구사항
    -학생 정보 추가
    -학생 정보 조회
    -학생 정보 수정
    -학생 정보 삭제
    
2. 비기능 요구사항
    -사용자 친화적인 콘솔 인터페이스 제공
    -적절한 예외 처리 및 로그 기록
    -데이터베이스 연결 풀 사용(HikariCP)

코드 리팩토링

SOLID 원칙

1. 단일 책임 원칙(Single Responsibility Principle, SRP) : 클래스는 하나의 책임만 가져야 한다.

2. 개방-폐쇄 원칙(Open/Closed Principle, OCP) : 소프트웨어 개체는 확장에는 열려 있어야 하지만 수정에는 닫혀있어야 한다.

3. 리스코프 치환 원칙(Liskov Substitution Principle, LSP) : 프로그램 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.

4. 인터페이스 분리 원칙(Interface Segregation Principle, ISP) : 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

5. 의존성 역전 원칙(Dependency Inversion Principle, DIP) : 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.

 

JDBC 성능 최적화

PreparedStatement 사용 장점

SQL쿼리를 미리 컴파일하고, 동일한 쿼리를 반복해서 실행할 때 효율적으로 사용할 수 있는 인터페이스이다. 이는 성능과 보안 측면에서 많은 장점을 제공한다.

성능 향상

성능 향상 보안 향상
쿼리 컴파일 : 
SQL쿼리를 미리 컴파일하여, 쿼리를 여러 번 실행할 때 컴파일 시간을 절약할 수 있다.
SQL 인젝션 방지 : 
쿼리와 데이터가 분리되어 있어 SQL 인젝션 공격을 방지할 수 있다.
+)Statement를 사용했을 때 컬럼명 뒤에 'or 1 = 1' 을 입력하면 값이 반드시 참이 되어 다른 이용자 정보까지 모두 출력된다.
쿼리 계획 재사용 : 
동일한 쿼리를 반복적으로 실행할 때 쿼리 계획을 재사용하여 실행 시간을 단축할 수 있다.

 

데이터베이스 연결의 생애주기

1. 애플리케이션 로직에서 데이터베이스 연결 요청
2. 데이터베이스 드라이버 초기화 및 연결 설정
3. TCP/IP 연결 설정
4. 사용자 인증 및 세션 생성
5. 커넥션 생성 완료
6. 데이터베이스 연결 사용 및 종료

 

연결 풀(Connection Pool)

● 정의 : 데이터베이스 연결 풀은 일정 수의 데이터베이스 연결을 미리 생성해두고, 애플리케이션에서 필요할 때마다 이 연결을 재사용하는 기술입니다.

목적 : 데이터베이스 연결을 효율적으로 관리하여 성능을 향상시키고, 데이터베이스 서버의 부하를 줄인다.

작동 원리 : 애플리케이션이 데이터베이스 연결을 요청하면 연결 풀에서 사용 가능한 연결을 반환한다. 사용이 끝난 연결은 폐기되지 않고 다시 연결 풀에 반환되어 재사용된다. 

 

데이터 소스(Data Source)

정의 : 데이터 소스는 데이터베이스 연결 정보를 캡슐화한 객체이다. 애플리케이션이 데이터베이스와 상호작용할 때 사용된다. 일반적으로 데이터 소스는 연결 풀을 포함하여 데이터베이스 연결을 관리한다.

목적 : 데이터 소스는 데이터베이스 연결 설정을 단순화하고, 연결 풀을 통해 효율적인 연결 관리를 제공한다.

사용 방법 : 데이터 소스를 설정하고, 애플리케이션에서 데이터 소스를 통해 데이터베이스 연결을 요청한다.

 

연결 풀과 데이터 소스 관계

통합 관리 : 데이터 소스는 연결 풀을 사용하여 데이터베이스 연결을 관리한다. 데이터 소스를 통해 데이터베이스 연결을 요청하면 내부적으로 연결 풀에서 관리하는 연결이 반환된다.

연결 추상화 : 데이터 소스는 연결 풀을 포함한 다양한 연결 관리 방식을 추상화하여 제공한다. 이를 통해 애플리케이션 개발자는 데이터 소스를 통해 쉽게 데이터베이스 연결을 사용할 수 있다.


더보기
package greenAcademy.studentManagement.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class StudentDTO {

	private int id;
	private String name;
	private int age;
	private String email;

}
더보기
package greenAcademy.studentManagement;

import java.sql.Connection;
import java.sql.SQLException;

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

// 싱글톤 패턴
// 객체 단 하나만 생성함을 보장해야 한다면 싱글톤 패턴으로 설계할 수 있다.
public class DBConnectionManager {

	// 자기 자신의 참조 주소값을 담을 변수 생성.
	// 단, private (외부 접근 차단)
	private static DBConnectionManager instance;
	private HikariDataSource dataSource;

	// 단, 외부에서 생성자를 호출 못하게 막아야 한다.
	private DBConnectionManager() {
		HikariConfig config = new HikariConfig();
		config.setJdbcUrl(null);
		config.setUsername("root");
		config.setPassword("asd123");
		config.setMaximumPoolSize(10);
		dataSource = new HikariDataSource(config);
	}

	// 외부에서 클래스이름.getxxxx 메서드를 만들어주면 된다.
	// 한 번에 스레드 하나만 접근하도록 동기화 적용
	public synchronized static DBConnectionManager getInstance() {
		if (instance == null) {
			instance = new DBConnectionManager();
		}
		return instance;
	}

	// Connection 객체를 반환(구현체 - HikariCP 이다)
	public Connection getConnection() throws SQLException{
		return dataSource.getConnection();
	}

} // end of class
더보기
package greenAcademy.studentManagement;

import java.sql.SQLException;
import java.util.List;

import greenAcademy.studentManagement.model.StudentDTO;

public interface StudentDAOImpl {

	// 학생 정보 추가
	public void addStudent(StudentDTO dto) throws SQLException;
	// 학생 전체 조회
	public List<StudentDTO> getAllStudent() throws SQLException;
	// 학생 아이디로 조회
	public StudentDTO getStudentById(int id) throws SQLException;
	// 학생 정보 수정
	public void updateStudent(String name, StudentDTO dto) throws SQLException;
	// 학생 정보 삭제
	public void deleteStudent(int id) throws SQLException;
	
	
}
더보기
package greenAcademy.studentManagement;

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

import greenAcademy.studentManagement.model.StudentDTO;

public class StudentDAO implements StudentDAOImpl {

	@Override
	public void addStudent(StudentDTO dto) throws SQLException {
		String query = "  INSERT INTO students(name,age,email) VALUES(?, ?, ?)  ";

		try (Connection conn = DBConnectionManager.getInstance().getConnection()) {
			PreparedStatement pstmt = conn.prepareStatement(query);
			pstmt.setString(1, dto.getName());
			pstmt.setInt(2, dto.getAge());
			pstmt.setString(3, dto.getName());
			pstmt.executeUpdate();
		}

	}

	@Override
	public List<StudentDTO> getAllStudent() throws SQLException {

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

		String query = "  SELECT * FROM students WHERE id = ?  ";

		try (Connection conn = DBConnectionManager.getInstance().getConnection()

		) {
			PreparedStatement pstmt = conn.prepareStatement(query);
			ResultSet rs = pstmt.executeQuery();

			while (rs.next()) {
				StudentDTO dto = new StudentDTO().builder().id(rs.getInt("id")).name(rs.getString("name"))
						.age(rs.getInt("age")).email(rs.getString("email")).build();

				list.add(dto);
			}
		} catch (Exception e) {
			// TODO: handle exception
		}

		return null;
	}

	@Override
	public StudentDTO getStudentById(int id) throws SQLException {

		String query = " SELECT * FROM students WHERE id = ? ";

		try (Connection conn = DBConnectionManager.getInstance().getConnection()) {
			PreparedStatement pstmt = conn.prepareStatement(query);
			pstmt.setInt(1, id);

			try (ResultSet rs = pstmt.executeQuery()) {
				if (rs.next()) {
					return new StudentDTO(rs.getInt("id"), rs.getString("name"), rs.getInt("age"),
							rs.getString("email"));
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
		}

		return null;
	}

	@Override
	public void updateStudent(String name, StudentDTO dto) throws SQLException {

		String query = " UPDATE students SET name = ?, age = ?, email = ? WHERE name = ? ";

		try (Connection conn = DBConnectionManager.getInstance().getConnection()) {
			PreparedStatement pstmt = conn.prepareStatement(query);
			pstmt.setString(1, dto.getName());
			pstmt.setInt(2, dto.getAge());
			pstmt.setString(3, dto.getEmail());
			pstmt.setString(4, name);
			pstmt.executeUpdate();
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

	@Override
	public void deleteStudent(int id) throws SQLException {
		String query = " DELETE FROM students WHERE id = ? ";

		try (Connection conn = DBConnectionManager.getInstance().getConnection()) {
			PreparedStatement pstmt = conn.prepareStatement(query);
			pstmt.setInt(1, id);
			pstmt.executeUpdate();
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

}
더보기
package ver2;

import java.sql.SQLException;
import java.util.List;

import ver2.model.StudentDTO;

public class StudentManagementSystem {

	private static final StudentDAO studentDAO = new StudentDAO();

	public static void main(String[] args) {
		// 사용자에게 보여주는 부분 꾸며줘도 됨
		try {
			List<StudentDTO> list = studentDAO.getAllStudents();
			System.out.println(list.size());
			System.out.println(list.toString());
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

SQLException 다루기

SQLException은 JDBC에서 발생할 수 있는 일반적인 예외이다. 이 예외는 데이터베이스와의 통신 중에 발생하는 오류를 나타낸다. SQLException은 다양한 속성과 메서드를 제공하여 예외에 대한 상세한 정보를 제공한다.

 

주요 속성 및 메서드

getErrorCode() : 데이터베이스 벤더가 제공하는 특정 오류 코드를 반환한다.

getSQLState() : SQLState 코드를 반환한다. 이 코드는 표준 SQL 상태 코드를 나타낸다.

getMessage() : 예외 메세지를 반환한다.

getNextException() : 체인된 예외를 반환한다.

 

'Java' 카테고리의 다른 글

Dynamic Web Project 1  (0) 2024.06.29
DAO,DTO,VO,Entity  (0) 2024.06.21
JDBC 트랜잭션 관리와 배치 처리 - 6  (1) 2024.06.12
Parsing(파싱)  (0) 2024.06.10
Socket  (0) 2024.06.10