SpringBoot - 05. JDBC를 통한 데이터 접근 (소스 코드 분석 + 코드 다운로드)

목차

  • JDBC란?
  • Spring Boot에서 JDBC 사용하기
  • 자동 설정 원리
  • JdbcTemplate
  • Swagger 설정 및 테스트
  • CRUD 연산 테스트
  • 에러 해결 방법

一. JDBC란?

JDBC(Java Database Connectivity)는 Java 표준 API 중 하나로, Java 애플리케이션이 데이터베이스에 연결하여 SQL문을 실행할 수 있게 해주는 기술입니다. JDBC는 다음과 같은 주요 기능을 제공합니다:

  • 데이터베이스への接続
  • SQL 문 실행
  • 결과 데이터 처리

二. Spring Boot에서 JDBC 사용하기

2.1 프로젝트 생성 및 의존성 추가

Spring Boot 프로젝트를 생성할 때 다음 의존성을 추가해야 합니다:

  • spring-boot-starter-jdbc: JDBC 지원
  • MySQL 드라이버: MySQL 데이터베이스 연결용
  • spring-boot-starter-web: 테스트용 웹 환경

POM.xml에 추가할 의존성:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <scope>runtime</scope>
</dependency>

2.2 데이터베이스 연결 설정

application.yml 파일에 데이터베이스 연결 정보를 설정합니다:

spring:
  datasource:
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/study-spring-boot?serverTimezone=UTC&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
    driverClassName: com.mysql.cj.jdbc.Driver

참고: 구버전 드라이버 클래스(com.mysql.jdbc.Driver)는 더 이상 사용되지 않으며,新版(com.mysql.cj.jdbc.Driver)를 사용해야 합니다.

2.3 데이터소스 및 연결 확인

다음 테스트 코드로 데이터소스와 데이터베이스 연결을 확인할 수 있습니다:

package com.example.springboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@SpringBootTest
class SpringbootDataApplicationTests {

    @Autowired
    DataSource dataSource;

    @Test
    void testDataSource() throws SQLException {
        System.out.println("데이터소스: " + dataSource.getClass());

        Connection conn = dataSource.getConnection();
        System.out.println("데이터베이스 연결: " + conn);
        conn.close();
    }
}

실행 결과:

  • 기본 데이터소스: class com.zaxxer.hikari.HikariDataSource
  • 데이터베이스 연결: HikariProxyConnection@xxxxxxx wrapping com.mysql.cj.jdbc.ConnectionImpl@xxxxxxx

三. 자동 설정 원리

Spring Boot의 자동 설정은 org.springframework.boot.autoconfigure.jdbc 패키지에서 관리됩니다. DataSourceConfiguration 클래스가 다양한 조건을 기반으로 적절한 데이터소스를 자동 구성합니다.

3.1 데이터소스 자동 선택

사용 가능한 데이터소스 구현체:

// Tomcat Pool DataSource 설정
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(org.apache.tomcat.jdbc.pool.DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type", 
    havingValue = "org.apache.tomcat.jdbc.pool.DataSource", 
    matchIfMissing = true)
static class Tomcat {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.tomcat")
    public DataSource dataSource() {
        // Tomcat 풀 데이터소스 생성
    }
}

3.2 지원되는 데이터소스 유형

Spring Boot는 다음 데이터소스를 기본으로 지원합니다:

  • org.apache.tomcat.jdbc.pool (기본값)
  • com.zaxxer.hikari.HikariDataSource
  • org.apache.commons.dbcp2

3.3 커스텀 데이터소스 지원

DataSourceBuilder를 사용하여 사용자 정의 데이터소스를 생성할 수 있습니다:

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty(name = "spring.datasource.type")
static class Generic {

    @Bean
    DataSource dataSource(DataSourceProperties properties) {
        // Reflection을 사용하여指定된 타입의 데이터소스를 생성하고 속성 바인딩
        return properties.initializeDataSourceBuilder().build();
    }
}

3.4 DataSource 초기화 실행기

DataSourceInitializerInvoker 클래스는 데이터베이스 스키마 및 데이터 스크립트를 자동으로 실행합니다:

  • 스키마 생성: schema-*.sql 파일로 테이블 생성
  • 데이터 초기화: data-*.sql 파일로 초기 데이터 삽입
private List<Resource> getScripts(String propertyName, List<String> resources, String fallback) {
    if (resources != null) {
        return getResources(propertyName, resources, true);
    }
    String platform = this.properties.getPlatform();
    List<String> fallbackResources = new ArrayList<>();
    fallbackResources.add("classpath*:" + fallback + "-" + platform + ".sql");
    fallbackResources.add("classpath*:" + fallback + ".sql");
    return getResources(propertyName, fallbackResources, false);
}

스크립트 실행 모드:

private boolean isEnabled() {
    DataSourceInitializationMode mode = this.properties.getInitializationMode();
    
    if (mode == DataSourceInitializationMode.NEVER) {
        return false;
    }
    if (mode == DataSourceInitializationMode.EMBEDDED && !isEmbedded()) {
        return false;
    }
    return true;
}

실행 모드 옵션:

  • NEVER: 스크립트 실행 안 함
  • EMBEDDED: 임베디드 데이터소스에만 실행 (기본값)
  • ALWAYS: 항상 실행

3.5 사용자 정의 스크립트 지정

application.yml에서 실행할 스크립트를 지정할 수 있습니다:

spring:
  datasource:
    schema:
      - classpath:department.sql
    initialization-mode: always

四. JdbcTemplate

JdbcTemplate은 JDBC를 간단하고 효율적으로 사용할 수 있도록 하는 Spring의 핵심 클래스입니다. JdbcTemplateAutoConfiguration이 자동으로 설정합니다:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, JdbcTemplate.class })
@ConditionalOnSingleCandidate(DataSource.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(JdbcProperties.class)
@Import({ JdbcTemplateConfiguration.class, NamedParameterJdbcTemplateConfiguration.class })
public class JdbcTemplateAutoConfiguration {
}

五. Swagger 설정

5.1 Swagger 의존성 추가

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

5.2 Swagger 설정 클래스

package com.example.springboot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.any())
            .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("Spring Boot REST API 문서")
            .description("Spring Boot RESTful API 문서입니다.")
            .version("1.0")
            .build();
    }
}

5.3 Swagger 문서 접근

애플리케이션 실행 후 다음 URL로 Swagger 문서에 접근합니다:

http://localhost:8080/swagger-ui.html

六. CRUD 연산 테스트

JdbcTemplate을 사용하여 부서(Department) 테이블에 대한 CRUD 연산을 테스트합니다.

6.1 부서 생성

@ApiOperation(value = "1.부서 생성")
@ApiImplicitParams({
    @ApiImplicitParam(name = "name", value = "부서명")
})
@PostMapping("/create")
public int createDepartment(@RequestParam String name) {
    String sql = String.format("INSERT INTO department(departmentName) VALUES('%s')", name);
    int result = jdbcTemplate.update(sql);
    return result;
}

6.2 모든 부서 조회

@ApiOperation(value = "2.모든 부서 조회")
@GetMapping("/getAllDepartment")
public List<Map<String, Object>> getAllDepartment() {
    List<Map<String, Object>> list = jdbcTemplate.queryForList("SELECT * FROM department");
    return list;
}

6.3 특정 부서 조회 (ID로)

@ApiOperation(value = "3.특정 부서 조회")
@ApiImplicitParams({
    @ApiImplicitParam(name = "id", value = "조회할 부서 ID")
})
@GetMapping("/{id}")
public Map<String, Object> getDepartmentById(@PathVariable Long id) {
    String sql = "SELECT * FROM department WHERE id = " + id;
    List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
    return list.get(0);
}

6.4 부서 정보 수정

@ApiOperation(value = "4.부서 정보 수정")
@ApiImplicitParams({
    @ApiImplicitParam(name = "id", value = "수정할 부서 ID"),
    @ApiImplicitParam(name = "name", value = "새 부서명")
})
@PostMapping("/update")
public int updateDepartmentById(@RequestParam Long id, @RequestParam String name) {
    String sql = String.format("UPDATE department SET departmentName = '%s' WHERE id = %d", name, id);
    int result = jdbcTemplate.update(sql);
    return result;
}

6.5 부서 삭제

@ApiOperation(value = "5.부서 삭제")
@ApiImplicitParams({
    @ApiImplicitParam(name = "id", value = "삭제할 부서 ID")
})
@PostMapping("/delete")
public int deleteDepartment(@RequestParam Long id) {
    String sql = String.format("DELETE FROM department WHERE id = %d", id);
    int result = jdbcTemplate.update(sql);
    return result;
}

七. 에러 해결 방법

에러 1: MySQL 서버 연결 거부

에러 메시지:

java.sql.SQLQLException: null, message from server: "Host '호스트명' is not allowed to connect to this MySQL server"

해결 방법:

USE mysql;
SELECT host FROM user;
UPDATE user SET host = '%' WHERE user = 'root';
FLUSH PRIVILEGES;

에러 2: timezone 오류

에러 메시지:

com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value '타임존값' is unrecognized...

해결 방법:

application.yml의 datasource URL에 timezone 파라미터를 추가합니다:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/dbname?serverTimezone=UTC&...

태그: spring-boot JDBC SQL DataSource HikariCP

8월 3일 15:53에 게시됨