JDBC
📌 Java 언어를 사용하여 DB와 상호 작용하기 위한 자바 표준 인터페이스로 데이터베이스 관리 시스템(DBMS)과 통신하여 데이터를 삽입(C), 검색(R) , 수정(U) 및 삭제(D)할 수 있게 해준다.
JDBC의 주요 특징
- 표준 API
- 대부분의 RDBMS (관계형 데이터베이스 관리 시스템)에 대한 드라이버가 제공되어 여러 종류의 DB 대해 일관된 방식으로 상호 작용할 수 있다.
- 데이터베이스 연결
- SQL 쿼리 실행
- Prepared Statement
- 결과 집합 처리(Result Set)
- 데이터베이스로부터 반환된 결과 집합을 처리할 수 있다.
- 트랜잭션 관리
- JDBC를 사용하여 데이터베이스 트랜잭션을 시작, 커밋(성공) 또는 롤백(실패)하는 등의 트랜잭션 관리 작업을 수행할 수 있습니다.
JDBC의 주요 구성 요소
- DriverManager:
- 데이터베이스 드라이버를 관리하고, 데이터베이스 연결(Connection)을 제공합니다.
- Connection:
- 데이터베이스와 애플리케이션 간의 연결을 나타냅니다.
- Statement:
- SQL 문을 실행하는 데 사용됩니다. (예: Statement, PreparedStatement, CallableStatement)
- ResultSet:
- SQL 쿼리 결과를 저장하고 처리하는 객체.
- SQLException:
- 데이터베이스 작업 중 발생하는 예외를 처리하는 클래스.
JDBC 작동 원리
- 애플리케이션이 JDBC API를 호출합니다.
- DriverManager가 요청을 적절한 데이터베이스 드라이버에 전달합니다.
- 데이터베이스 드라이버가 데이터베이스에 연결을 설정합니다.
- Connection 객체를 통해 SQL 문을 실행하고 결과를 반환받습니다.
JDBC를 사용하는 단계
a. 데이터베이스 드라이버 로드
- 데이터베이스와 상호작용하기 위해 드라이버를 메모리에 로드
Class.forName("com.mysql.cj.jdbc.Driver");
b. 데이터베이스 연결
- DriverManager를 사용하여 데이터베이스에 연결.
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydatabase", "username", "password"
);
c. SQL 문 실행
- Statement 또는 PreparedStatement를 사용하여 SQL 문 실행.
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT * FROM users");
d. 결과 처리
- ResultSet 객체를 사용하여 쿼리 결과를 읽고 처리.
while (resultSet.next()) {
System.out.println("ID: " + resultSet.getInt("id"));
System.out.println("Name: " + resultSet.getString("name"));
}
e. 자원 해제
- 사용한 자원(Connection, Statement, ResultSet)을 닫아야 함.
resultSet.close();
statement.close();
connection.close();
Statement VS Prepared Statement
📌 Java에서 데이터베이스에 SQL 쿼리를 실행하기 위한 인터페이스이다. 이들은 데이터베이스와의 통신을 통해 쿼리 결과를 반환하거나 데이터 조작을 수행하는 데 사용됩니다.
Statement
- DB와 연결되어 있는 Connection 객체를 통해 SQL문을 Database에 전달하여 실행하고, 결과를 반환받는 객체
public class StatementExample {
public static void main(String[] args) {
try {
// MySqlDriver 파일을 라이브러리에 추가한다.
// Driver연결
Class.forName("mysql.jdbc.driver.MySqlDriver");
// Database와 연결(계정 접속)
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/mydatabase", "username", "password");
// Statement 인스턴스 생성
Statement statement = connection.createStatement();
// String SQL Query
String query = "SELECT * FROM MEMBER WHERE NAME = 'wonuk'";
// Query 실행 -> 결과는 ResultSet으로 반환된다.
ResultSet rs = statement.execute(query);
// 결과 처리
while (rs.next()) {
// 결과 처리 로직
}
// 연결을 수동으로 끊어줘야한다.
rs.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- SQL 쿼리를 직접 문자열로 작성하여 데이터베이스에 보내는 방법이다.
- SQL 쿼리는 실행 전에 문자열 형태로 전달되고, 실행 시점에 데이터베이스에 직접 파싱되고 실행.
- 매번 실행할 때마다 SQL 문을 다시 파싱하므로 성능에 영향을 미칠 수 있고, 보안 취약점을 가질 수 있습니다.
Prepared Statement
- SQL문을 Complie 단계에서 ? 를 사용하여 preCompile 하여 미리 준비해놓고 Query문을 파라미터 바인딩 후 실행하고 결과를 반환받는다. 미리 준비해놓았다(Pre) 중요!
public class PreparedStatementExample {
public static void main(String[] args) {
try {
// [Mysql.jdbc.driver.MysqlDriver] 파일을 라이브러리에 추가한다.
Class.forName("mysql.jdbc.driver.MysqlDriver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/mydatabase", "username", "password");
String query = "SELECT * FROM employees WHERE department = ?";
PreparedStatement preparedStatement = connection.prepareStatement(query);
// 값을 설정
preparedStatement.setString(1, "HR");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
// 결과 처리 코드
}
resultSet.close();
preparedStatement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- SQL 쿼리를 미리 컴파일하여 데이터베이스에 전송할 때 값만 바뀌는 형태로 전달한다.
- 쿼리가 한 번 컴파일되면 여러 번 실행할 수 있으며, 성능이 향상되고 보안 측면에서 더 안전한다.
- 동적인 입력값을 placeholder?로 대체하고 파라미터 바인딩을 통해 쿼리를 삽인한다. 즉, 사용자 입력을 직접적으로 쿼리에 삽입하지 않는다.
- 이스케이핑 처리를 지원한다. 말그대로 탈출(Escape), 입력값이 자동으로 쿼리에 안전하게 이스케이핑된다. 이스케이핑은 입력 데이터에서 잠재적인 SQL 쿼리 문자열을 무력화한다.
SQL Injection
📌 악의적인 사용자가 애플리케이션에서 입력 데이터를 이용하여 SQL 쿼리를 조작하고 데이터베이스에 무단 접근하거나 데이터를 변조하는 공격이다.
SQL Injection 종류
- Error Based SQL Injection
- Database에 고의적으로 오류를 발생시켜 에러 응답을 통해 DB 구조를 파악하는 방법.
- Union Based SQL Injection
- 컬럼의 개수와 데이터 형식이 같아야 한다.
- DB의 UNION 연산자를 사용하여 쿼리 결과값의 조합을 통해 정보를 조회한다.
- Blind Based SQL Injection
- Stored Procedure SQL Injection
- Time Based SQL Injection
- SQL Injection 예시
- Java 코드 예시
// 로그인
public String login(String id, String password) {
return String query =
"SELECT * FROM MEMBER WHERE ID = " + id
+ "AND PASSWORD = " + password;
}
// 검증
public String login(String id, String password) {
// 매개변수 id, password 가 문제없는지 검증하는 로직 Escape 등
String query =
"SELECT * FROM MEMBER WHERE ID = " + id + "AND PASSWORD = " + password;
return query;
}
- 해결방법
- 클라이언트에게 에러 메세지 노출을 차단한다.
- 입력값을 검증(Validation)한다.
- Prepared Statements를 사용한다.
XSS(Cross Site Scription)
📌 악성 스크립트를 웹사이트에 주입하는 Code Injection 기법 중 하나. 웹 애플리케이션에서 발생하는 보안 취약점으로, 공격자가 악성 스크립트를 사용자 브라우저에서 실행하도록 유도하는 공격입니다.
- 이를 통해 공격자는 사용자의 세션 쿠키를 탈취하거나, 악성 코드를 실행해 개인 정보를 노출시키고, 웹사이트의 신뢰성을 훼손할 수 있습니다.
XSS의 동작 원리
- 공격자가 웹 애플리케이션의 취약점을 이용해 악성 스크립트를 삽입.
- 사용자가 해당 페이지를 열거나 입력값을 처리할 때 악성 스크립트가 실행됨.
- 브라우저는 이 스크립트를 신뢰된 사이트의 일부로 간주하여 실행.
XSS의 위험성
- 세션 탈취:
- 사용자의 세션 쿠키를 탈취하여 계정에 접근.
- 웹사이트 변조:
- 사용자 브라우저에서 악성 콘텐츠를 표시하거나, 위조된 페이지를 로드.
- 피싱 공격:
- 사용자에게 신뢰할 수 없는 링크를 클릭하도록 유도.
- 브라우저 악성 코드 실행:
- 브라우저를 통해 악성 소프트웨어를 배포.
XSS의 종류
- Stored XSS
- 공격자가 취약점이 있는 Web Application에 악성 스크립트를 영구적으로 저장하여 다른 사용자에게 전달하는 방식
<script>alert(document.cookie);</script>
- 해당 script를 게시글에 삽입하면 HTML로 구성되어 있기 때문에 해당 스크립트가 조회하는 사용자에게 실행된다.
- Reflected XSS
- 공격 스크립트가 서버에 저장되지 않고 즉시 반사되어 실행됨.
- URL에 포함된 파라미터나 입력값을 통해 전달.
- 외부 링크 페이지로 이동시킨다.
- DOM based XSS
- 서버와 상관없이 클라이언트 측 JavaScript에서 발생.
- DOM(Document Object Model)을 조작하여 악성 스크립트를 실행.
- 해결방법
- 입/출력 값을 검증(Validation)하고 필터링하여 해결한다.
- 외부 보안관련 라이브러리를 사용한다.
- 보안 솔루션을 사용한다.
Persistence Framework
📌 애플리케이션에서 데이터를 영구적으로 저장하고 관리하기 위해 데이터베이스와 같은 저장소와의 상호 작용을 단순화하는 소프트웨어 도구이다.
Persistence Framework 개요
- JDBC의 한계
- 간단한 SQL을 실행하는 경우에도 중복된 코드가 너무 많았다.
public class PreparedStatementExample {
public static void main(String[] args) {
try {
// ojdbc6.jar[oracle.jdbc.driver.OracleDriver] 파일을 라이브러리에 추가한다.
Class.forName("oracle.jdbc.driver.OracleDriver");
// 1. Connection
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost/mydatabase", "username", "password");
String query = "SELECT * FROM employees WHERE department = ?";
// 2. Statement
PreparedStatement preparedStatement = connection.prepareStatement(query);
// 값을 설정
preparedStatement.setString(1, "HR");
// 3. ResultSet
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
// 결과 처리 코드
}
resultSet.close();
preparedStatement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- onnection, Prepared Statement, ResultSet 등
- DB에 따라 일관성 없는 정보를 가진 채로 Checked Exception(SQL Exception) 처리를 한다.
- Checked Exception인 SQLException은 개발자가 명시적으로 처리해야 한다.
- 각 DBMS는 고유한 SQL 문법과 오류 코드 체계를 가지고 있다.
- JDBC에서 발생하는 SQLException은 이러한 DBMS에 따라 달라질 수 있으며, 예외 메시지나 코드도 DB마다 다를 수 있다. 즉, 모두 그에 맞게 처리해야 한다.
- Connection과 같은 공유 자원을 제대로 반환하지 않으면 한정된 시스템 자원(CPU, Memory)에 의해 서버가 다운되는 등의 문제가 발생한다.
- SQL Query를 개발자가 직접 작성한다.
- 중복 적인 쿼리 및 코드 작성이 필요하다.
- 대부분의 테이블에 CRUD하는 쿼리가 포함된다.
- Persistence Framework의 등장
- JDBC 처럼 복잡함이나 번거로움 없이 간단한 작업만으로 Database와 연동되는 시스템
- 모든 Persistence Framework는 내부적으로 JDBC API를 이용한다.
- preparedStatement를 기본적으로 사용한다.
- 크게 SQL Mapper, ORM 두가지로 나눌 수 있다.
- JDBC, SQL MAPPER, ORM의 공통점
- 영속성(Persistence) 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지 않는 데이터의 특성, 영구히 저장되는 특성
Persistence Framework의 역할
- 데이터 접근 로직 간소화:
- JDBC와 같은 저수준 API의 반복 작업을 제거.
- 객체와 관계형 데이터 매핑:
- 객체지향 언어(Java)의 데이터와 관계형 데이터베이스 간의 불일치를 해결.
- 트랜잭션 관리:
- 데이터 작업을 트랜잭션 단위로 처리하여 데이터 무결성 보장.
- 데이터베이스 독립성:
- 특정 데이터베이스에 종속되지 않고, 다양한 RDBMS와 연동 가능.
- 쿼리 생성 최적화:
- 동적 쿼리 생성을 자동화하거나 간소화.
Persistence Framework의 특징
- ORM (Object-Relational Mapping):
- 객체 모델과 관계형 데이터베이스를 매핑.
- 데이터베이스 작업을 객체지향 방식으로 처리.
- SQL 추상화:
- SQL 쿼리를 자동 생성하거나 간소화된 API로 대체.
- 트랜잭션 지원:
- ACID 특성을 보장하는 트랜잭션 처리.
- 데이터베이스 연결 관리:
- 커넥션 풀을 사용해 데이터베이스 연결 성능을 최적화.
주요 Persistence Framework
a. Hibernate
- 설명: JPA(Java Persistence API)의 구현체 중 하나로 가장 널리 사용됨.
- 특징:
- 완전한 ORM 지원.
- 동적 쿼리 및 캐시 지원.
- 다양한 데이터베이스와 호환 가능.
b. JPA (Java Persistence API)
- 설명: Java 표준 ORM API로, 데이터베이스와 객체 간 매핑을 정의.
- 특징:
- Hibernate, EclipseLink와 같은 구현체가 필요.
- 표준화된 인터페이스 제공.
c. MyBatis
- 설명: SQL 매핑 기반의 Persistence Framework.
- 특징:
- SQL을 직접 작성하여 세부적인 제어 가능.
- 자동 매핑 기능 지원 (SQL 결과와 객체 매핑).
d. Spring Data JPA
- 설명: JPA를 더 간단하게 사용할 수 있도록 지원하는 Spring 모듈.
- 특징:
- 기본 CRUD 작업을 간소화.
- Repository 패턴 지원.
e. EclipseLink
- 설명: JPA의 또 다른 구현체로, Oracle과 밀접한 통합.
- 특징:
- 성능 최적화와 고급 기능 지원.
ORM과 SQL 매핑 프레임워크 비교
ORM (Hibernate, JPA) | SQL 매핑 (MyBatis) | |
쿼리 작성 방식 | 자동 생성 (HQL, JPQL) | 직접 작성 (SQL 문장 사용). |
생산성 | 높은 추상화로 간단한 작업은 편리. | 복잡한 쿼리 작성에 유리. |
유연성 | 쿼리 최적화가 어려울 수 있음. | 세부적인 제어 가능. |
학습 곡선 | ORM의 개념 이해 필요. | SQL 지식만으로 쉽게 접근 가능. |
사용 사례 | CRUD 중심의 애플리케이션. | 복잡한 데이터 조작 및 최적화된 쿼리 필요. |
Persistence Framework의 장점
- 생산성 향상:
- 데이터 접근 로직을 간소화.
- 데이터베이스 독립성:
- 다양한 RDBMS와 호환 가능.
- 코드 간결화:
- CRUD 작업의 반복 코드를 제거.
- 트랜잭션 관리:
- ACID 특성을 쉽게 적용 가능.
- 객체-관계 매핑:
- 객체지향적인 데이터 접근 가능.
Persistence Framework의 단점
- 복잡성 증가:
- ORM 개념 학습과 설정이 필요.
- 쿼리 최적화 한계:
- ORM에서는 복잡한 SQL 튜닝이 어려울 수 있음.
- 퍼포먼스 문제:
- 자동 생성된 쿼리가 비효율적일 수 있음.
- 추상화의 비용:
- 프레임워크의 내부 동작을 이해해야 문제를 해결 가능.
Persistence Framework 선택 가이드
요구사항 | 권장 프레임워크 |
간단한 CRUD 애플리케이션 | Spring Data JPA, Hibernate |
복잡한 SQL 쿼리 작업 | MyBatis |
높은 데이터베이스 독립성 필요 | JPA |
빠르고 세부적인 SQL 제어 필요 | MyBatis |
대규모 트랜잭션 관리 및 확장성 요구 | Hibernate, JPA |
SQL Mapper
📌 SQL 쿼리와 객체(Object) 간의 매핑을 지원하는 도구입니다. 직접 작성한 SQL 문의 실행 결과와 객체(Object)의 필드를 Mapping하여 데이터를 객체화한다. 대표적인 SQL Mapper로 Spring JDBC Template, MyBatis가 있다.
- Spring JDBC Template
- Spring Framework에서 제공하는 JDBC 작업을 단순화하고 개선한 유틸리티 클래스
- JDBC Template의 장점
- 간편한 데이터베이스 연결
- 손수 적었던 Connection 관련 코드들을 yml 혹은 properties 파일에 설정만으로 해결한다.
- Prepared Statement를 사용한다.
- 예외 처리와 리소스 관리
- DB Connection을 자동으로 처리하여 리소스 누수를 방지한다.
- 결과 집합(ResultSet) 처리
- 데이터를 자바 객체로 변환할 수 있도록 돕는다.
- 배치 처리 작업을 지원한다.
- 매일 동일한 시간에 수행되는 쿼리, 주로 통계에 사용된다.
- 간편한 데이터베이스 연결
// 1. XML OR Gradle에 Spring JDBC 의존성 추가
// 2. application.properties OR application.yml에 데이터베이스 연결 설정
@RestController
public class MemberController {
private final MemberRepository memberRepository;
public MemberController(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@GetMapping("/members")
public List<Member> findById(Long id) {
return memberRepository.findById(id);
}
}
// Member Object
public class Member {
private Long id;
private String name;
private int age;
// Getter and Setter methods
}
// Repository Anotation의 역할에 대해 공부해주세요.
@Repository
public class MemberRepository {
private final JdbcTemplate jdbcTemplate;
public MemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// Member 객체로 리턴한다.
public List<Member> findById(Long id) {
String query = "SELECT * FROM MEMBER WHERE id = " + id;
return jdbcTemplate.query(query, (rs, rowNum) -> {
Member member = new Member ();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
member.setAge(rs.getInt("age"));
return member;
});
}
}
대표적인 SQL Mapper
- MyBatis:
- SQL 기반의 가장 널리 사용되는 SQL Mapper.
- 간단한 XML 매핑 설정으로 SQL과 객체를 연결.
- JDBC Template (Spring):
- Spring에서 제공하는 SQL 처리 도구.
- SQL Mapper와 비슷한 역할 수행.
- iBatis:
- MyBatis의 이전 버전으로, MyBatis로 발전됨.
SQL Mapper의 한계
- SQL을 직접 다룬다.
- 특정 DB에 종속적으로 사용하기 쉽다.
- DB마다 Query문, 함수가 조금씩 다르다.
- 테이블마다 비슷한 CRUD SQL, DAO(Data Access Object) 개발이 반복된다. → 코드 중복
- 테이블 필드가 변경될 시 이와 관련된 모든 DAO의 SQL문, 객체의 필드 등을 수정해야 한다.
- 코드상으로 SQL과 JDBC API를 분리했지만 논리적으로 강한 의존성을 가지고 있다.
- 객체와의 관계는 사라지고 DB에 대한 처리에 집중하게 된다. → SQL 의존적인 개발
- 관계형 DB와 객체지향의 패러다임 불일치
- 객체지향으로 설계된것을 관계형 DB에 저장하기란 어렵다.
- 테이블에 저장한 데이터를 다시 객체화 하는것도 어렵다.
// 1. 객체 안의 객체
public class Member {
// 필드들..
private Team team;
} -> ERD?
// 2. 상속 구조
public class Member extends Person {
// 필드들..
} -> ERD?
// 3. extends, implements
public class Member extends Person implements Workable {
// 필드들..
} -> ERD?
- 객체지향 : 캡슐화, 추상화, 상속, 다형성 → 객체 중심
- 관계형 데이터베이스(RDB) → 데이터 중심의 구조
- 각각 지향하는 목적이 다르기 때문에 사용 방법과 표현 방식에 차이가 있다.
요약
- JDBC → SQL Mapper(JDBC Template, Mybatis) → ORM(JPA Hibernate)
- Persistence Framework는 모두 JDBC API를 내부적으로 사용하고 있다.
- SQL Mapper, ORM 들은 내부적으로 Prepared Statement를 사용하고있다.
- SQL Mapper, ORM 들은 Database Connection등 리소스 관리를 자동으로 해주고있다.
- Persistence Framework는 저장소와의 상호 작용을 단순화 한다.
MyBatis
📌 SQL 쿼리들을 XML 파일에 작성하여 코드와 SQL을 분리하여 관리되도록 만들어준다.
- SQL과 Java Code의 분리가 핵심
- Query를 JAVA에서 XML로
- 복잡한 JDBC 코드가 사라진다.
- ResultSet과 같이 결과값을 맵핑하는 객체가 없다.
- 설정이 간단하다.
- 관심사를 분리한다. → SQL 을 따로 관리한다.
- XML 안에있는 SQL을 Java의 메소드에 매핑해준다.
- MyBatis 장점
- 자동으로 Connection 관리를 해주면서 JDBC 사용할 때의 중복 작업 대부분을 없애준다.
- DB 결과 집합을 자바 객체로 매핑할 수 있다.
- 복잡한 쿼리나 다이나믹하게(동적쿼리) 변경되는 쿼리 작성이 쉽다.
- 상황에 따라 분기처리(IF)를 통해 쿼리를 동적으로 만들어주는것.
- 관심사 분리 - DAO로부터 SQL문을 분리하여 코드의 간결성 및 유지보수성이 향상된다.
- 쿼리 결과를 캐싱하여 성능을 향상시킬 수 있다.
// User 클래스
public class User {
private Long id;
private String userName;
private String email;
// Getter and Setter methods
}
// Mapper Interface 생성 : SQL 쿼리와 매핑을 정의하는 인터페이스
public interface UserMapper {
User getUserById(Long id);
}
public class Main {
public static void main(String[] args) {
String resource = "mybatis-config.xml";
try (Reader reader = Resources.getResourceAsReader(resource)) {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.getUserById(1L);
System.out.println(user.getId() + ", " + user.getUsername() + ", " + user.getEmail);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Mapper XML
SQL 쿼리와 객체 매핑을 정의한다.
<mapper namespace="com.example.UserMapper">
<select id="getUserById" resultType="com.example.User">
SELECT id, userName, email
FROM users
WHERE id = #{id}
</select>
</mapper>
MyBatis 설정 파일 작성
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="userName" value="userName" />
<property name="password" value="password" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/UserMapper.xml" />
</mappers>
</configuration>