새소식

TIL

[TIL] 230702 <JPA> JDBC

  • -

 

 

의존성

  1. compile 시점 의존성: 프로젝트를 컴파일할 때 사용 →해당 라이브러리의 API를 사용할 수 있음
  2. runtime 시점 의존성: 애플리케이션을 실행할 때 사용 →실행 시에도 라이브러리가 필요
  3. 은닉성: implementation으로 추가된 의존성은 다른 프로젝트 모듈에서 직접 접근불가 →모듈 간의 캡슐화를 도와줌

 

 

의존성 옵션 (build.gradle > dependencies)

  • implemenataion 옵션
    • 직접적인 의존성을 추가할 때 사용
    • 특정 라이브러리나 모듈이 프로젝트 컴파일 시 필요하지만, 해당 라이브러리가 프로젝트 외부로 공개될 필요가 없다는 것을 의미
  • runtimeOnly 옵션
    • compile 시점에는 필요없고 runtime 시점에만 필요한 라이브러리를 추가할 때 사용
    • ex) Logging 관련 라이브러리, DB 관련 라이브러리 등
  • testImplementation 옵션
    • 테스트 코드를 수행할 때 적용할 라이브러리를 추가할 때 사용
    • 테스트 용도로만 라이브러리나 빌트인 DB 사용하고 싶다면 해당 옵션을 사용

 

 


 

 

  • 역할
    • 애플리케이션과 데이터베이스 간의 통신을 중개하는 역할
    • 드라이버는 애플리케이션의 요청을 데이터베이스가 이해할 수 있는 언어로 변환
  • 종류
    • 다양한 데이터베이스 시스템마다 호환되는 드라이버가 있음
    • 예를 들어, Oracle, MySQL, PostgreSQL 등 각 데이터베이스 제품에 맞는 특정 드라이버가 필요

 

 

동작 방식

  1. 연결 초기화
    • 요청 수신: 애플리케이션은 데이터베이스 작업을 시작하기 위해 드라이버에 연결을 요청
    • 연결 설정: 드라이버는 데이터베이스 서버에 로그인하고 필요한 설정을 수행하여 연결을 완료
                      ㄴ 네트워크 정보, 인증 자격 증명 등을 사용하여 이루어짐
  2. SQL 전송 및 실행
    • SQL 명령 변환: 애플리케이션에서 발송된 SQL 명령을 받은 드라이버는 해당 명령을 데이터베이스가 이해할 수 있는 형태로 변환
    • 명령 처리: 변환된 명령은 데이터베이스 서버로 전송되어 실행. 데이터베이스는 쿼리를 처리하고, 요구된 데이터를 검색하거나 데이터에 변화를 줌.
  3. 결과 처리
    • 결과 수신: 데이터베이스에서 작업의 결과를 보내면, 드라이버는 이 결과를 받아 애플리케이션에서 해석할 수 있는 형태로 변환
    • 결과 전달: 최종적으로, 드라이버는 이 결과를 애플리케이션에 전달. 애플리케이션은 이 정보를 사용자에게 표시하거나 다음 작업을 진행.
  4. 연결 종료
    • 연결 해제: 작업이 완료되면, 드라이버는 데이터베이스 서버와의 연결을 종료. 자원을 정리하고 다음 세션을 위해 시스템을 초기화.

 

 


 

 

> Spring Boot 의 JDBC 라이브러리

  • Spring Boot는 데이터베이스 연결을 쉽게 구성할 수 있도록 다양한 JDBC 드라이버를 지원하여 복잡한 설정 없이 데이터베이스와의 연결을 쉽게 구성가능

  • spring-boot-starter-jdbc : Spring Boot에서 JDBC를 통해 데이터베이스와 상호작용하기 위해 사용되는 스타터 패키지
    • 데이터베이스 작업을 수행하는 데 필요한 주요 의존성과 자동 구성 기능을 제공
    • 데이터베이스와의 연결을 쉽고 빠르게 구성할 수 있도록 도와주는 브릿지 역할
    • 주요 포함 내용
      1. JDBC API 지원: JDBC API를 통해 SQL 데이터베이스에 접근하고 작업 수행
      2. DataSource 구성: 데이터 소스 연결을 위한 기본적인 설정을 자동으로 구성. 데이터베이스 연결을 관리하는 데 필수적인 요소.
      3. JdbcTemplate: Spring의 핵심 클래스 중 하나로, JDBC 작업의 많은 번거로움을 줄여줌. SQL 쿼리 실행, 결과 세트 처리, 예외 처리 등을 단순화함
    • 주요 장점 내용
      1. 간소화된 데이터베이스 연결: DataSource 설정과 JdbcTemplate 사용을 통해 복잡한 JDBC 코드를 간소화 가능
      2. 자동 구성
      3. 효율적인 예외 처리: Spring의 DataAccessException을 통해 JDBC에서 발생하는 예외를 Spring의 일관된 예외 체계로 변환

 

 

> JDBC

  • JDBC(Java Database Connectivity)
    • 자바 애플리케이션에서 데이터베이스에 접근할 수 있도록 하는 API
    • Java 앱과 DB 를 연결시켜주기 위해 만들어진 기술
    • JPA도 이 기술을 사용하여 구현되어 있음
    • JDBC 드라이버는 이 API를 구현하여, 자바 애플리케이션과 특정 데이터베이스 간의 연결을 가능하게 함
  • JDBC 드라이버
    • JDBC Driver는 여러타입의 DB와 연결할 수 있는 기능을 제공
    • 타입: 네 가지 유형(Type 1, 2, 3, 4)이 있으며, 각각의 특징과 사용 환경에 따라 선택가능.
               Type 4 드라이버(순수 자바 드라이버)가 가장 일반적으로 사용됨

 

JDBC Driver Manager는 애플리케이션이 실행되고 있는 런타임 시점에

  • Connection(연결) 을 생성하여 쿼리를 요청할 수 있는 상태를 만들어주고
  • Statement(상태) 를 생성하여 쿼리를 요청하게 해주고
  • ResultSet(결과셋) 을 생성해 쿼리 결과를 받아올 수 있게 해줌.

// JdbcApplication.java

package com.thesun4sky.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JdbcApplication {

	public static void main(String[] args) throws SQLException {
		// 어플리케이션 실행 컨텍스트 생성
		SpringApplication.run(JdbcApplication.class, args);

		// 데이터베이스 연결정보
		String url = "jdbc:h2:mem:test"; 	// spring.datasource.url
		String username = "sa";				// spring.datasource.username

		// connection 얻어오기
		try (Connection connection = DriverManager.getConnection(url, username, null)) {
			try {
				// 테이블 생성 (statement 생성)
				String creatSql = "CREATE TABLE USERS (id SERIAL, username varchar(255))";
				try (PreparedStatement statement = connection.prepareStatement(creatSql)) {
					statement.execute();
				}

				// 데이터 추가 (statement 생성)
				String insertSql = "INSERT INTO USERS (username) VALUES ('teasun kim')";
				try (PreparedStatement statement = connection.prepareStatement(insertSql)) {
					statement.execute();
				}

				// 데이터 조회 (statement 생성 후 rs = resultSet 수신 & next() 조회)
				String selectSql = "SELECT * FROM USERS";
				try (PreparedStatement statement = connection.prepareStatement(selectSql)) {
					var rs = statement.executeQuery();
					while (rs.next()) {
						System.out.printf("%d, %s", rs.getInt("id"), rs.getString("username"));
					}
				}
			} catch (SQLException e) {
				if (e.getMessage().equals("ERROR: relation \"account\" already exists")) {
					System.out.println("USERS 테이블이 이미 존재합니다.");
				} else {
					throw new RuntimeException();
				}
			}
		}
	}
}

 

PreparedStatement

  • Statement의 동작방식

- Statement는 executeQuery()나 executeUpdate()를 실행하는 시점에 파라미터로 SQL문을 전달하는데, 이 때 전달되는 SQL 문은 완성된 형태로 한눈에 무슨 SQL 문인지 파악하기 쉬움
- 하지만 SQL문을 수행하는 과정에서 구문 분석을 수행하기 때문에 Prepared Statement에 비해 효율성이 떨어짐!

 

  • PreparedStatement는 Statement를 상속하고 있는 Interface

- PreparedStatement는 내부적으로 Statement의 4단계(구문분석, 치환, 실행, 인출) 과정 중 첫 번째 parse 과정의 결과를 캐싱하고, 나머지 3가지 단계만 거쳐서 SQL문이 실행될 수 있게 함
- 즉, 구문 분석(parse)의 결과를 캐싱해서 과정을 생략할 수 있으므로 성능이 향상됨
- 변수 문자열 쿼리를 집어넣어 해킹하는 SQL Injection도 방어가능

 

 

 

> JDBC Template (QueryMapper)

JDBC Template 는 JDBC Driver를 추상화하여 동작함 (dbcp = db connection pool)

  • JDBC로 직접 SQL을 작성했을때의 문제
    • SQL 쿼리 요청시 중복 코드 발생
    • DB별 예외에 대한 구분 없이 Checked Exception (SQL Exception) 처리
    • Connection, Statement 등 자원 관리를 따로 해줘야함 → 자원 해제 안해주면 메모리 꽉차서 서버가 죽음
  • 이 문제 해결을 위해 처음으로 Persistence Framework 등장!
    • SQL Mapper : JDBC Template, MyBatis
    • ORM : JPA, Hibernate

 

SQL Mapper (QueryMapper)

  • SQL ↔ Object
  • SQL문과 객체(Object)의 필드를 매핑하여 데이터를 객체화
// DataRepository.java
package com.thesun4sky.jdbc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class DataRepository {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	// 테이블 생성
	public void createTable() {
		jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS users (id SERIAL, name VARCHAR(255))");
	}

	// 사용자 추가 (Create)
	public void insertUser(String name) {
		jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", name);
	}

	// 사용자 ID로 이름 조회 (Read)
	public String findUserNameById(Long id) {
		return jdbcTemplate.queryForObject(
			"SELECT name FROM users WHERE id = ?",
			new Object[]{id},
			String.class
		);
	}

	// 사용자 이름 변경 (Update)
	public void updateUser(Long id, String newName) {
		jdbcTemplate.update("UPDATE users SET name = ? WHERE id = ?", newName, id);
	}

	// 사용자 삭제 (Delete)
	public void deleteUser(Long id) {
		jdbcTemplate.update("DELETE FROM users WHERE id = ?", id);
	}
}
// JdbcApplication.java
package com.thesun4sky.jdbc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JdbcApplication {

	public static void main(String[] args) {
		// 어플리케이션 실행 컨텍스트 생성
		var context = SpringApplication.run(JdbcApplication.class, args);
		// 데이터 조회 클래스 빈 조회
		var repository = context.getBean(DataRepository.class);

		// 테이블 생성
		repository.createTable();
		// 유저정보 추가
		repository.insertUser("Teasun Kim");
		// 유저정보 조회
		System.out.println("User Name: " + repository.findUserNameById(1L));
	}
}

 

JDBC Template (RowMapper)

  • SQL Mapper 첫번째 주자로 JDBCTemplate에 RowMapper 탄생
    • 쿼리 수행 결과와 객체 필드 매핑
    • RowMapper로 응답필드 매핑코드 재사용
    • Connection, Statement, ResultSet 반복적 처리 대신 해줌
    • 하지만 결과값을 객체 인스턴스에 매핑하는데 여전히 많은 코드가 필요함

  • RowMapper.mapRow : RowMapper를 상속받아 mapRow() 메서드를 구현하면 JDBCTemplate에서 row 응답을 mapRow() 메서드에 rs 파라미터로 넘겨주어 객체에 매핑하기 쉽도록 도와줌
  • findUserById : jdbcTemplate.queryForObject() 메서드에서는 두번째 인자로 RowMapper를 넣어줄 경우 해당 RowMappermapRow() 메서드를 사용하여 응답을 하도록 동작함
// UserRowMapper.java
import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;


public class UserRowMapper implements RowMapper<User> {

	// JDBCTemplate 에서 row 응답을 mapRow() 메서드에 rs 파라미터로 넘겨주어 객체에 매핑하기 쉽도록 도와준다.
  @Override
  public User mapRow(ResultSet rs, int rowNum) throws SQLException {
    var user = new User();
    user.setId(rs.getInt("ID"));
    user.setName(rs.getString("NAME"));
    return user;
  }
}
// 사용자 ID로 User 조회 (Read)
public User findUserById(Long id) {
	return jdbcTemplate.queryForObject(
		"SELECT * FROM users WHERE id = ?",
		new UserRowMapper(),  // 이자리에 매퍼를 생성자로 넣어주면 됨
		id
	);
}

'TIL' 카테고리의 다른 글

<5조 Trell5> Spring 심화 프로젝트 S.A  (0) 2024.07.10
[TIL] 230703 <JPA> JpaRepository, QueryDSL  (0) 2024.07.03
[TIL] 230628 <AWS> RDS  (0) 2024.06.28
[TIL] 230627 <AWS> EC2, ELB  (0) 2024.06.27
[TIL] 230626 <AWS> AWS 기초, IAM  (0) 2024.06.26
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.