JDBC 기본 내용과 실습하기

말하는 감자·2025년 3월 21일

내일배움캠프

목록 보기
25/73

JDBC

Java 언어를 사용하여 DB와 상호 작용하기 위한 자바 표준 인터페이스로 데이터베이스 관리 시스템(DBMS)과 통신하여 데이터를 삽입(C), 검색(R) , 수정(U) 및 삭제(D)할 수 있게 해준다.

JDBC의 주요 특징

  1. 표준 API
    1. 대부분의 RDBMS (관계형 데이터베이스 관리 시스템)에 대한 드라이버가 제공되어 여러 종류의 DB 대해 일관된 방식으로 상호 작용할 수 있다.

      ex) Database 종류가 바뀌어도 쿼리문이 실행된다. MySQL → ORACLE

  2. 데이터베이스 연결
  3. SQL 쿼리 실행
  4. Prepared Statement
  5. 결과 집합 처리(Result Set)

오.......... 예전에 PDO 에서 제공하는 Prepared Statement 써봤었는데
자바는 JDBC가 제공하는가보다

Prepared Statement는 SQL Injection 방지해줌.
파라미터 들어오면 거기에 쿼리문 들어와도 다 스트링으로 해석해서 못뚫게함





Persistence Framework

애플리케이션에서 데이터를 영구적으로 저장하고 관리하기 위해 데이터베이스와 같은 저장소와의 상호 작용을 단순화하는 소프트웨어 도구이다.

문제점

  • SQL 한문장쓰려고 준비해야하는 클래스/ 메서드가 너무 많다.
    1️⃣연결-> 2️⃣쿼리문PREPARED -> 3️⃣값 바인딩 -> 4️⃣결과값 처리

  • 일관성 없는 예외가 발생할 수 있음( 휴먼오류 )
    Checked Exception인 SQLException은 개발자가 명시적으로 처리해야 한다.

    • JDBC에서 발생하는 SQLException은 이러한 DBMS에 따라 달라질 수 있으며, 예외 메시지나 코드도 DB마다 다를 수 있다. 즉, 모두 그에 맞게 처리해야 한다.
  • Connection과 같은 공유 자원을 제대로 반환하지 않으면 한정된 시스템 자원(CPU, Memory)에 의해 서버가 다운되는 등의 문제가 발생한다.

  • SQL Query를 개발자가 직접 작성한다.

    • 중복 적인 쿼리 및 코드 작성이 필요하다.
    • 대부분의 테이블에 CRUD하는 쿼리가 포함된다.



🔥이런 문제점 개선을 위해 Persistence Framework 등장
-> 내부적으로 JDBC API 사용하면서 간단한 작업으로 DB와 연동할 수 있음
-> Prepared Statement 을 기본으로 사용함

크게 🍋SQL Mapper,🍊 ORM 으로 나뉜다.

<특징 및 요약>

  1. JDBC → 🍋SQL Mapper(JDBC Template, Mybatis) → 🍊ORM(JPA Hibernate)
    → 모두 "영속성(Persistence)"을 지닌다.
    즉, 영구히 저장된다
  2. Persistence Framework는 모두 JDBC API를 내부적으로 사용하고 있다.
  3. SQL Mapper, ORM 들은 내부적으로 Prepared Statement를 사용하고있다.
  4. SQL Mapper, ORM 들은 Database Connection등 리소스 관리를 자동으로 해주고있다.
  5. Persistence Framework는 저장소와의 상호 작용을 단순화 한다.

개선점1 - 🍋SQL Mapper

SQL 문과 자바 객체의 필드를 매핑함
-> 데이터를 객체처럼 만들어보린다

대표적인 SQL Mapper로 Spring JDBC Template, MyBatis가 있다.


Spring JDBC Template

Spring Framework에서 제공하는 JDBC 작업을 단순화하고 개선한 유틸리티 클래스

1️⃣ 연결 쉬워짐 : Connection 관련 코드들을 yml 혹은 properties 파일에 설정만으로 해결
4️⃣ 결과 집합(ResultSet) 처리 : 데이터를 자바 객체로 변환할 수 있도록 돕는다.

MyBatis

  • SQL 쿼리들을 XML 파일에 작성하여 코드와 SQL을 분리하여 관리되도록 만들어준다.
  • SQL과 Java Code의 분리가 핵심
  • Query를 XML에서 작성함

1️⃣ 자동 연결 관리 : 자동으로 Connection 관리를 해주면서 JDBC 사용할 때의 중복 작업 대부분을 없애준다.
2️⃣ 복잡한 쿼리나 다이나믹하게(동적쿼리) 변경되는 쿼리 작성이 쉽다.
- 동적쿼리 : 상황에 따라 분기처리(IF)를 통해 쿼리를 동적으로 만들어주는것.
4️⃣ 결과 집합(ResultSet) 처리 : 데이터를 자바 객체로 매핑할 수 있다.

🔥개선점1 - 🍋SQL Mapper의 한계

  • SQL을 직접 다룬다. : 쿼리문을 직접 작성한다는 것에 차이점이 없다.
    → 직접 작성하게되는 쿼리는 사용하는 DB마다 쿼리문과 함수가 다름.
    → DB 바꾸면 쿼리도 변경해야함
  • 코드 중복 : 직접 작성되는 SQL문은 테이블마다 다 존재함
    → 테이블마다 비슷한 CRUD, DAO 가 필요할텐데 이거 개발 반복됨
    → 마찬가지로 테이블 구성 변경되면 관련된 필드, SQL문 다 변경해야함
  • 객체와의 관계는 사라지고 DB에 대한 처리에 집중하게 된다.
    → ‼️SQL 의존적인 개발
  • 관계형 DB와 객체지향의 패러다임 불일치 :
    객체지향으로 설계된 데이터 ↔️ 관계형 테이블에 저장된 데이터
    →사이 왔다갔다가 어렵다.
  • 객체지향 : 캡슐화, 추상화, 상속, 다형성 → 객체 중심
  • 관계형 데이터베이스(RDB) → 데이터 중심의 구조
  • 각각 지향하는 목적이 다르기 때문에 사용 방법과 표현 방식에 차이가 있다.

🔥 패러다임 불일치 문제를 해결하기 위해 이후 🍊ORM이 등장하게 된다. 🔥

ORM에 대한 내용은 다음 강의에 나오면 보완해서 쓰겠삼






이제 JDBC Template CRUD연습을 해야한다.

JDBC Template CRUD

C reate

데이터 하나 넣기

    @Override
    public MemoResponseDto saveMemo(Memo memo) {
        //인서트 쿼리를 직접 문자열로 지정하지 않아도 된다.
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        //메모라는 테이블에 인서트를 하겠다. + 이 테이블의 키값은 "id"라는 컬럼에 있다.
        jdbcInsert.withTableName("memo").usingGeneratedKeyColumns("id");

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("title", memo.getTitle());
        parameters.put("contents", memo.getContents());

        //저장 후 생성된 key값을 Number타입으로 반환해줌
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));

        return new MemoResponseDto(key.longValue(), memo.getTitle(), memo.getContents());
    }

R ead

id에 맞는 row값 읽기

    @Override
    public Memo fineMemoByIdOrElseThrow(Long id) {
        List<Memo> result =  jdbcTemplate.query("select * from memo where id = ?", memoRowMapperV2(), id);
        return result.stream().findAny().orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = "+id));
    }
    
    private RowMapper<Memo> memoRowMapperV2() {
        return new RowMapper<Memo>() {
            @Override
            public Memo mapRow(ResultSet rs, int rowNum) throws SQLException {
                return new Memo(
                        rs.getLong("id"),
                        rs.getString("title"),
                        rs.getString("contents")
                );
            }
        };
    }
    

다읽기

    @Override
    public List<MemoResponseDto> finaAllMemos() {

        return jdbcTemplate.query("SELECT * FROM memo", memoRowMapper());
    }
    
    
    private RowMapper<MemoResponseDto> memoRowMapper(){
        return new RowMapper<MemoResponseDto>() {
            @Override
            public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                //여기서 행 하나씪 읽어서 MemoResponseDto객체 만들어줌
                return new MemoResponseDto(
                        rs.getLong("id"),
                        rs.getString("title"),
                        rs.getString("contents")
                );
            }
        };
    }
    






U pdate

데이터 내용을 변경할때는 변경 후에 알맞게 변했는지, row가 잘 있는지 확인해야함.

   @Override
    public Memo fineMemoByIdOrElseThrow(Long id) {
        List<Memo> result =  jdbcTemplate.query("select * from memo where id = ?", memoRowMapperV2(), id);
        return result.stream().findAny().orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = "+id));
    }

여기서 id값에 해당되는 코드가 얼마나 되는지 받아오는데,
만약에 result값이 텅텅 비었다면 (해당되는 값이 없다면 ElseThrow가 돼서 예외 리턴하게됨)

result.stream().findAny().orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "Does not exist id = "+id))

1.findAny() : filter 조건에 일치하는 element 1개를 Optional로 리턴함
여기서 filter없이 바로 .findAny()를 했으니 리스트가 비었는지 확인하겠다ㅡㄴ 소리

  1. orElseThrow : findAny()가 옵셔널 값이니깐 Null상태일때 출력(예외처리)

참고 : https://devjjsjjj.tistory.com/entry/Java-Stream-findAny와-findFirst의-차이점

    @Override
    public int updateTitle(Long id, String title) {
        //업데이트에 반영된 row의 수를 반환하.ㅁ
        return jdbcTemplate.update("update memo set title = ? where id = ?", title, id);
    }

D elete

   @Override
    public int deleteMemo(Long id) {
        return jdbcTemplate.update("delete from memo where id = ?", id);
    }

특이한 점은 삭제이지만 jdbcTemplate의 update함수를 쓴다는 것

profile
대충 데굴데굴 굴러가는 개발?자

0개의 댓글