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 |