JDBC와 Spring JDBC란

비구름·2024년 12월 22일

Spring

목록 보기
1/10
post-thumbnail

1. JDBC란

JDBC 간단한 설명

JDBC(Java Database Connectivity)란 Java 애플리케이션이 데이터베이스 드라이버를 통해 데이터베이스의 데이터를 생성, 수정, 삭제, 조회가 가능하도록 만들어진 API입니다.

라이브러리의 위치는 java.sql에 위치하고 있습니다.

JDBC의 구조

JDBC는 크게 3가지로 분류됩니다.

Connection

자바와 데이터베이스의 연결을 담당하며 자바 애플리케이션에서 데이터베이스와 통신하는 가장 중심이 되는 메서드라고 생각할 수 있습니다.

데이터베이스와 연결을 위한 모든 정보가 Connection에서 사용됩니다.

Connection conn = DriverManager.getConnection({데이터베이스URL}, {유저 아이디}, {유저 비밀번호});
  • 데이터베이스URL : 접속할 데이터베이스의 주소를 뜻하며 프로토콜://서버주소:포트번호/데이터베이스이름으로 구성됩니다. (MariaDB의 경우 jdbc:mariadb://localhost:3306/todo)
  • 유저 아이디/패스워드 : 데이터베이스에 저장된 유저로 데이터베이스 기준으로 작성

Statement

연결된 Connection을 통해 질문할 내용을 작성하며 SQL을 사용하는 데이터베이스의 경우 SQL문이 Statement에 정보를 넣고 송신하게 됩니다.

데이터베이스와 통신하는 정보를 가지고 있는 객체입니다.

Statement stmt = conn.createStatement();

생성자를 통해 생성하는 것이 아닌 connection을 통해 새롭게 생성됩니다.

ResultSet

데이터베이스와 통신을 한 후 수신받는 데이터를 모아둔 객체입니다.

Statement객체를 통해 데이터베이스에게 얻는 정보를 모아둔 객체입니다.

ResultSet은 Statement에 있으며 getResultSet()으로 가져올 수 있습니다.

ResultSet rs = stmt.executeQuery({sql문});
  • sql문은 sql을 가진 데이터베이스 기준입니다.

2. JDBC 사용하는 방법 (조회 기준)

1. 변수 및 SQL문 선언

try-catch-finally에서 finally에서 모든 변수를 close()처리를 하기 위해 try문 밖에 변수를 미리 선언합니다.

String sql = "SELECT " +
                "id, title, state, create_at " +
                "FROM " +
                "todo " +
                "WHERE " +
                "delete_state = 0";
                
Connection conn = null;
Statement stmt = null;;
ResultSet rs = null;

List<TodoTitleDto> result = new ArrayList<>();

쿼리를 날릴 SQL문을 미리 선언해도 되지만 선언하지않고 바로 메서드에 넣어도 좋습니다.

변수가 아닌 다른 방법을 사용해도 괜찮습니다

2. JDBC 드라이버 로딩

사용할 DB와 통신하기 위해 해당 DB에 맞는 JDBC드라이버를 로딩합니다.

해당 드라이버가 존재하지 않을 경우 ClassNotFoundException이 발생합니다.

  • MySQL - com.mysql.jdbc.driver
  • MariaDB - org.mariadb.jdbc.Driver
Class.forName("org.mariadb.jdbc.Driver");

3. Connection 생성

DriverManager를 통해 인스턴스를 받아옵니다.

DriverManager는 해당 드라이버 라이브러리가 존재하는지 확인 후 반환해주는 역할

conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/todo");

해당 URL을 통해 해당 URL에 맞는 드라이버를 찾아 Connection을 생성합니다.

URL이 Null이거나 해당 URL을 통해 데이터베이스에 연결이 되지 않는다면 SQLException이 발생합니다.

4. Statement 생성

Connection에서 DB에 쿼리를 보내기 위한 Statement 인스턴스를 받아옵니다.

stmt = conn.createStatement();

5. SQL문 실행

Statement를 생성한 후 SQL을 통해 데이터베이스와 통신합니다.

rs = stmt.executeQuery(sql);

excute를 통해 실행한 후 getResultSet()을 통해 결과를 받아올 수 있지만 이를 한번에 처리해주는 executeQuery를 사용하여 select문의 결과를 빠르게 가져올 수 있습니다.

6. 결과를 처리

rs는 Iterator처럼 사용할 수 있습니다.

결과를 한 줄씩 가져올 수 있으며 getInt와 같이 get{Type}을 사용하여 원하는 타입으로 가져올 수 있습니다.

next를 통해 다음 줄로 넘어갈 수 있습니다.

while (rs.next()) {
    result.add(TodoTitleDto.builder()
            .id(rs.getInt("id"))
            .title(rs.getString("title"))
            .state(rs.getInt("state"))
            .createAt(rs.getTimestamp("create_at").toLocalDateTime())
            .build());
}

7. 인스턴스 닫기

사용한 인스턴스를 닫아줍니다.

conn.close();
stmt.close();
rs.close();

전체 코드

public List<TodoTitleDto> findAll() {
    String sql = "SELECT " +
            "id, title, state, create_at " +
            "FROM " +
            "todo " +
            "WHERE " +
            "delete_state = 0";
    Connection conn = null;
    Statement stmt = null;;
    ResultSet rs = null;

    List<TodoTitleDto> result = new ArrayList<>();

    try {
        Class.forName("org.mariadb.jdbc.Driver");

        conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/todo");
        stmt = conn.createStatement();
        rs = stmt.executeQuery(sql);
        while (rs.next()) {
            result.add(TodoTitleDto.builder()
                    .id(rs.getInt("id"))
                    .title(rs.getString("title"))
                    .state(rs.getInt("state"))
                    .createAt(rs.getTimestamp("create_at").toLocalDateTime())
                    .build());
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            conn.close();
            stmt.close();
            rs.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    return result;
}

3. Spring JDBC란

Spring프레임워크에서 제공하는 JDBC API입니다.

Spring JDBC와 JDBC의 차이

기존 JDBC는 Connection을 만들기 위한 작업을 줄이고 Statement를 따로 꺼내는 작업을 할 필요없이 이를 단순화시킨 것입니다. 또한 Spring형태에 일부가 변형되었으며 JdbcTemplate을 통해 일괄적으로 모든 과정을 처리합니다.

쉽게 말해서 복잡한 과정을 단순화시켜 편의성을 높이고 Spring의 형태에 맞게 변형시킨 것을 말합니다.

NamedParameterJdbcTemplate와 같은 Template으로 편의성을 더 높이는 것도 존재합니다.

4. Spring JDBC를 사용하는 방법

Spring Dependecy

1. application.yml을 사용한 환경변수 생성

데이터베이스와 연결하기 위해 필요한 정보를 작성합니다.

반드시 이 경로로 작성해야하며 properties를 사용하여도 마찬가지입니다.

만약 yml을 이용하지 않는다면 DataSource객체에 setter를 통해 해당 정보를 직접 추가해주어야 합니다.

spring:
  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: jdbc:mariadb://localhost:3306/{데이터베이스 이름}
    username: {유저 이름}
    password: {유저 비밀번호}

2. config에서 만들어 빈으로 등록 (환경변수에 적어둔 데이터베이스 정보를 사용)

아래 Configuration이 생성될 때 환경변수(yml)에 넣어둔 값을 이용하여 만들어진 DataSource를 사용하여 빈을 생성합니다.

@Configuration
@RequiredArgsConstructor
public class config {
    private final DataSource dataSource;

    @Bean
    public TodoDao todoDao() {
        return new TodoDaoImpl(dataSource);
    }
}

만약 yml을 사용하지 않고 제작한다면 DataSource를 만들어 setter를 통해 해당 값을 직접 넣어주면 좋습니다.

yml을 사용한 이유

  1. 환경 변수를 통해 데이터베이스의 위치가 외부에 노출될 위험을 줄입니다.
  2. setter를 추가적으로 사용하지 않고 간결하게 코드를 작성할 수 있어 편의성이 올라갑니다.

만약 여러 데이터베이스를 JDBC를 통해 사용한다면…

여러 데이터베이스의 경우 이 방법을 통해 만들지는 못하겠지만 Value어노테이션과 yml에 커스텀 경로에 데이터베이스와 연결할 정보를 넣어두어 사용할 것 같습니다.

3. JdbcTemplate 생성

SQL문을 다룰 때 ?와 같이 빈칸을 만든 후 순서대로 값을 넣는 JdbcTemplate과 달리 :{변수명}을 통해 더 가독성도 높고 실수가 적은 NamedParameterJdbcTemplate를 사용하였습니다.

TodoDaoImpl(DataSource dataSource) {
    this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

4-1. query(map) - select

SQL문에 :{변수명}을 사용하여 해당 위치에 들어갈 값을 지정할 수 있습니다. Map<String, ?>를 통해 해당 변수에 값을 지정할 수 있습니다. Key가 변수명 Value가 변수가 가진 값입니다.

public List<TodoTitleDto> findByState(int state) {
    Map<String, Object> map = new HashMap<>();
    map.put("state", state);

    String sql = "SELECT " +
            "id, title, state, create_at " +
            "FROM " +
            "todo " +
            "WHERE " +
            "state = :state";

    return jdbcTemplate.query(sql, map, todoTitleMapper);
}

4-2. execute(SqlParameterSource) - insert

위와 같이 map을 통해 사용할 수 있지만 스프링에서 주로 사용하는 DTO에 getter가 있다면 SqlParameterSource을 통해 객체로 바로 작성할 수 있습니다.

KeyHolder를 통해 추가된 데이터의 인덱스값을 가져올 수 있습니다.

public int postTodo(PostRequestDto postRequestDto) {
    String sql = "INSERT INTO todo(title, content, create_at, update_at)" +
            "VALUES(:title, :content, :createAt, :updateAt)";

    SqlParameterSource param = new BeanPropertySqlParameterSource(Todo.builder()
            .title(postRequestDto.getTitle())
            .content(postRequestDto.getContent())
            .createAt(LocalDateTime.now())
            .updateAt(LocalDateTime.now())
            .build());

    KeyHolder keyHolder = new GeneratedKeyHolder();

    jdbcTemplate.update(sql, param, keyHolder);

    return keyHolder.getKey().intValue();
}

@Getter
@AllArgsConstructor
public class PostRequestDto {
    String title;
    String content;
}

마무리

JPA만 사용하던 사람으로써 JDBC와 Spring JDBC에 대해 알아보는 시간을 가졌습니다.

인터넷 서칭을 하여도 자세하게 설명해주는 블로그가 없어 아쉬웠던 와중 직접 JDBC를 통해 간단한 DAO를 만들어보며 사용법을 간단하게나마 익혔습니다.

JDBC와 Spring JDBC는 동작과정 자체에는 큰 차이가 없었지만 Spring JDBC는 Spring JDBC이란 이름에 맞게 Spring구조에 굉장히 밀접하게 만들어져있어 기존의 JDBC를 Spring의 스타일로 추상화시켰다고 생각할 수 있었습니다.!

profile
기본부터 정리해나가는 기록용 블로그

0개의 댓글