본문 바로가기
Java/Spring, SpringBoot

[SpringBoot] JDBC를 통해 SQL 실행하기(JDBC Connection과 Memory Exceed)

by 마라민초닭발로제 2025. 1. 23.

글은 SpringBoot에서 JDBC를 통해 SQL을 실행하면서 공부했던 것을 정리하기 위함입니다. 

 

1. Dependency등록하기

JDBC를 쓰기 위해서 Database Dependency를 등록해야합니다. 가장 간단한 Memory Dependency를 설정하기 위해 H2를 활용하여 Dependency를 설정했습니다. 만약 mysql이나 oracle사용할 경우 다른 Dependency를 설정하면 됩니다. 

 

dependencies {
    implementation 'com.h2database:h2:2.1.214'
}

 

 

2. application.properties 설정

Database에서 어떤 사용자가 쓸 것인지 연결 설정을 추가합니다. h2Database를 통해 설정한다면 기본은 다음과 같습니다. 

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver

 

 

 

3. Repository 클래스 생성

DataBase을 담당하는 객체가 필요합니다. 이 객체를 통해서 DB와 원활한 소통을 할 수 있게 합니다. Repository 클래스를 통해서 DriverManager 를 불러오고 Connection을 할당합니다. 

public class UserRepository {
    private static final String URL = "jdbc:h2:mem:testdb";
    private static final String USER = "sa";
    private static final String PASSWORD = "";


    public void executeQuery(String sql) {
        try ( // ✅ 실제 Connection을 추가하고, preparedStatement를 추가하여 sql을 등록합니다. 
        		Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
	            PreparedStatement preparedStatement = connection.prepareStatement(sql);
        ) {
            // ✅ sql을 실행시킵니다.
            ResultSet resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                // ✅ 실행한 SQL에 객체가 있는지(COLUMN) 검사하고 이를 출력합니다. 
                System.out.println("Result: " + resultSet.getString(1));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

 

4. JDBC란?

JDBC(Java 데이터베이스 연결)는 애플리케이션이 데이터베이스와 상호 작용할 수 있게 해주는 Java의 API입니다. 이를 통해 Java 프로그램은 데이터베이스에 연결하고, 쿼리를 실행하고, 데이터를 검색 및 조작할 수 있습니다. JDBC는 표준 인터페이스를 제공함으로써 Java 애플리케이션이 MySQL, Oracle, PostgreSQL 등과 같은 다양한 관계형 데이터베이스와 함께 작동할 수 있도록 합니다.

 

https://www.geeksforgeeks.org/introduction-to-jdbc/

  • Application: 데이터 소스와 통신하는 Java 애플릿 또는 서블릿입니다.
  • JDBC API: Java 프로그램에서 SQL 쿼리를 실행하고 결과를 검색할 수 있게 해줍니다. 주요 인터페이스에는 Driver, ResultSet, RowSet, PreparedStatement 및 Connection이 포함됩니다. 중요한 클래스로는 DriverManager, Types, Blob, Clob 등이 있습니다.
  • DriverManager: JDBC 아키텍처에서 중요한 역할을 합니다. 일부 데이터베이스 전용 드라이버를 사용하여 엔터프라이즈 애플리케이션을 데이터베이스에 효과적으로 연결합니다.
  • JDBC drivers: 이러한 드라이버는 애플리케이션과 데이터베이스 간의 상호 작용을 처리합니다.

JDBC 아키텍처는 데이터베이스에 액세스하기 위한 2계층 및 3계층 처리 모델로 구성됩니다. 아래에 설명되어 있습니다: 

 

1. Two-Tier Architecture

Java application은 database를 JDBC driver를 통해서 직접적으로 소통하는 방식을 말한다. 쿼리들은 데이터베이스로 보내지며, 결과들은 어플리케이션에 직접적으로 전달된다. 클라이언트 - 서버 에서는, 유저 원격 데이터베이스에 직접 소통하는 것을 말한다. 

Client Application (Java) -> JDBC Driver -> Database

 

 

2. Three-Tier Architecture

유저 쿼리들은 미들 티어 서비스로 전달된다. 이 미들티어로 보내진 쿼리들이 데이터베이스와 인터렉트 합니다. 데이터베이스의 결과는 미들티어에게 제안되고 다시 유저에게 전달됩니다. 

Client Application -> Application Server -> JDBC Driver -> Database

3. JDBC Componenets

 

  • JDBC API: 데이터베이스와 커뮤니케이션하기위한 다양한 매서드와 인터페이스를 제공합니다. 이는 두가지 패키지로 나뉩니다.
    • java.sql: 패키지는 JavaStandardEdition(JavaSE) 관계형 데이터베이스에서 접근과 프로세싱 및 핵심적인 인터페이스를 갖고 있습니다. 
    • javax.sql:  Java Enterprise Edition(java EE). 이는 java.sql을 역량을 향상시키게 합니다. 이는 connection pooling과 statement pooling 그리고 데이터 소스 메니징을 같은 기술들로 확장시켰습니다. 
  • JDBC Driver Manager
    • Driver Manager는 데이터베이스와의 연결을 설젛아기 위해 올바른 Database별 Driver를 로드하는 역할을 합니다. 사용 가능한 Driver를 관리하고 올바른 Driver가 사용자 요청을 처리하고 DB와 상호작용 하는데 사용되는지 확인합니다.
  • JDBC Test Suite
    • 보통 JDBC 드라이버가 test operation에 활용될 수 있게 사용됩니다.
  • JDBC Drivers
    • JDBC 드라이버는 클라이언트 측 어댑터(서버가 아닌 클라이언트 시스템에 설치됨)로, Java 프로그램의 요청을 DBMS가 이해할 수 있는 프로토콜로 변환하는 역할을 합니다. 

 

5. JDBC를 사용할 때 주의 점 Connection, PreparedStatement 자원관리

Connection이나 PreparedStatement를 명시적으로 닫지 않으면 자원이 해제되지 않아 메모리 누수가 발생할 수 있습니다. 이를 방지하려면 try-with-resources를 사용해야 합니다. 만약 try with resoureces를 사용하지 않는다면 직접 해제해 줘야 합니다. 

public void executeQueryWithAutoClose(String sql) {
    try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
         PreparedStatement preparedStatement = connection.prepareStatement(sql)) {

        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            System.out.println("Result: " + resultSet.getString(1));
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// ✅ With No try-with-resources
// Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
// PreparedStatement preparedStatement = connection.prepareStatement(sql);
// connection.close();
// preparedStatement.close();

 

 

6. 진짜 connection 안닫으면 메모리 차지할까?

그냥 가바지 컬렉터로 사라질때 지워지지 않을까 하는 고민으로 시작해봤습니다. 메모리누수를 직접 보고 위험성을 알고 싶어서 실험해보았습니다. 아래는 connection을 해제 않는 코드 입니다. 

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

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

@Repository
@RequiredArgsConstructor
public class MemoryExceedExample {
    private static final String URL = "jdbc:h2:mem:test";
    private static final String USER = "sa";
    private static final String PASSWORD = "sa";

    public static void main(String[] args) {
        try {
            // H2 메모리 데이터베이스에 연결
            Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);

            // 메모리를 많이 사용하도록 수많은 데이터를 삽입
            for (int i = 1; i <= 1000000; i++) {
                insertData(connection, i, "User" + i);
            }

            System.out.println("데이터 삽입 완료.");
            // Connection을 닫지 않음
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private static void insertData(Connection connection, int id, String name) {
        try {
            String insertSql = "INSERT INTO todos(user_id, is_show, title, content) VALUES (1,true, 'title1', 'content1');";
            PreparedStatement preparedStatement = connection.prepareStatement(insertSql);

            preparedStatement.executeUpdate();
            // PreparedStatement를 닫지 않음
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


메모리가 진짜로 해제되지 않는 모습을 볼 수 있었습니다. 역시 API를 사용할때는 문서를 꼼꼼히 읽어야 합니다.

 

메모리 600 mb
두번째 DB호출시 1GB의 메모리를 사용하는 모습을 확인할 수 있었습니다.

 

 

레퍼런스

https://www.geeksforgeeks.org/introduction-to-jdbc/