JDBC 이해

고동현·2024년 5월 31일
0

DB

목록 보기
1/13

JDBC 이해

JDBC가 왜 등장했을까?

우리는 웹브라우저나 APP으로부터 중요한 정보를 DB에다가 저장해놓는다.
그러나, 애플리케이션 서버와 DB를 직접 커넥팅하면
1. 커넥션 연결: TCP/IP로 커넥션 2. SQL 전달 3. 결과응답 이러한 과정으로 로직이 수행되는데
문제는 이 DB가 MySQL에서 Oracle DB로 바뀌면 각 데이터 베이스마다 사용법이 다르므로, 이 3가지 과정이 전부 다 다르다는 문제점이 있다.
그래서 이 문제를 해결하기 위해서 JDBC라는 자바 표준이 등장하게 된다.

이제 애플리케이션 로직이 DB를 바로 바라보는게 아니라 JDBC표준 인터페이스를 바라보게 한다.
그러면, 우리는 각 DB에 해당하는 Connection Statement ResultSet을 알필요없이 표준 인터페이스만 사용해서 개발하면 된다.

그러면 이런 궁금증이 든다. 어? 그러면 인터페이스 구현체는 어디있지? 바로 이 JDBC인터페이스를 각각의 DB회사에서 자신의 DB에 맞도록 구현해서 라이브러리로 제공하는데 이걸 JDBC 드라이버라 한다. MySQL이면 MySQL 드라이버라 하는것임

JDBC와 최신 데이터 접근 기술

JDBC를 편리하게 사용하는 기술, SQL Mapper와 ORM 기술로 나눌 수 있음.
1. JDBC 직접 사용
애플리케이션 로직 -> JDBC(SQL 직접전달)
2. SQL Mapper
애플리케이션 로직 ->(SQL 전달) SQL Mapper(JdbcTemplate,MyBatis) ->(SQL 전달) JDBC
개발자가 직접 SQL을 작성해야함
3. ORM 기술

객체를 RDB테이블과 매핑해줌, 객체만 전달해도 객체의 연관정보를 파악하여서 SQL 만들어서 JDBC에 전달

데이터 베이스 연결

주의: H2데이터 베이스 서버를 먼저 실행해 둬야함

public abstract class ConnectionConst {
    public static final String URL = "jdbc:h2:tcp://localhost/~/test";
    public static final String USERNAME = "sa";
    public static final String PASSWORD = "";
}

DB접근에 필요한 정보들을 상수로 만듬(abstract)

@Slf4j
public class DBConnectionUtil {
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        log.info("get connection = {}, class = {}",connection,connection.getClass());
        return connection;
    }
}

여기서 Connection은 import java.sql.Connection 인터페이스임
DB정보로 getConnection호출하고 Connection return

Test

@Slf4j
class DBConnectionUtilTest {
    @Test
    void connection() throws SQLException {
        Connection connection = DBConnectionUtil.getConnection();
        assertThat(connection).isNotNull();
    }

}

getConnection방법.

1. 애플리케이션이 DB와 커넥션이 필요하면 DriverManager.getConnection()호출
2. 이미 build.gradle에다가 H2.driver설치 해놨고, DriverManger가 라이브러리에 등록된 드라이버 목록 자동인식
3. 드라이버들한테 설정정보를 넘겨서 커넥션을 획득할 수 있는지 확인
4. 가능하면 커넥션 구현체가 반환

JDBC개발 - 등록

JDBC를 사용해서 애플리케이션을 개발해보자.

@Data
public class Member {
    private String memberId;
    private int money;

    public Member(){
    }
    public Member(String memberId,int money){
        this.memberId = memberId;
        this.money = money;
    }
}

회원등록

@Slf4j
public class MemberRepositoryV0 {
    public Member save(Member member) throws SQLException {
        String sql = "insert into member(member_id, money) values(?, ?)";//sql쿼리문
        Connection con = null;
        PreparedStatement pstmt = null;
        try {
            con = getConnection(); // 커넥션맺고
            pstmt = con.prepareStatement(sql); //PreparedStatement로 sql날림
            pstmt.setString(1, member.getMemberId()); //?부분 바인딩
            pstmt.setInt(2, member.getMoney());
            pstmt.executeUpdate(); //실행
            return member;
        } catch (SQLException e){
            log.error("db error",e);
            throw e;
        }finally {
            //항상 finally에 connection종료
            close(con,pstmt,null);
        }
    }

    private void close(Connection con, Statement stmt, ResultSet rs) {
        //close할때 Connection,Statement,ResultSet 각각 try catch를 사용하여 닫아야함
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                log.info("error", e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                log.info("error", e);
            }
        }
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                log.info("error", e);
            }
        }
    }

    private Connection getConnection() throws SQLException {
        return DBConnectionUtil.getConnection();
    }
}
  1. getConnection()->데이터베이스 커넥션 획득
  2. sql작성하고
  3. con.prepareSatement(sql): db에 날릴 sql과 파라미터로 전달할 data 준비
  4. pstmt.set~(): ?에 바인딩할 파라미터 설정
  5. pstmt.excuteUpdate(): DB에 sql 날림
  6. 리소스정리
    정리할때는 항상 역순, 항상 finally에 둬서 예외 발생여부와 관계없이 항상 수행, Conncetion,PreparedStatement,Resultselt 각각 전부 try catch로 리소스 반납 - 안하면 커넥션이 안끊어지고 계속 유지

Test

class MemberRepositoryV0Test {

    MemberRepositoryV0 repository = new MemberRepositoryV0();

    @Test
    void crud() throws SQLException{
        //save
        Member member = new Member("memberV0",10000);
        repository.save(member);
    }
}

결과

JDBC 개발 - 조회

  public Member findById(String memberId) throws SQLException {
        String sql = "select * from member where member_id = ?";
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        Connection con = null;
        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1, memberId);

            rs = pstmt.executeQuery();

            if (rs.next()) {
                Member member = new Member();
                member.setMemberId(rs.getString("member_id"));
                member.setMoney(rs.getInt("money"));
                return member;
            } else {
                throw new NoSuchElementException("member not found memberId=" + memberId);
            }

        }catch (SQLException e){
            log.error("db error",e);
        }finally {
            close(con,pstmt,rs);
        }
        return null;
    }

조회할때는 executeQuery()수행, 결과를 ResultSet에 담아서 반환
최초의 커서는 데이터를 가르키고 있지 않으므로, rs.next()를 호출하여서 커서가 데이터를 가르키게 함.
ResultSet으로 Pk에 해당하는 하나의 데이터만 넘어오므로,if문으로 rs.next()수행
만약 여러개의 ResultSet이 넘어오면 while문으로 돌려야함

Test

 @Test
    void crud() throws SQLException{
        //save
        Member member = new Member("memberV0",10000);
        repository.save(member);

        Member findMember = repository.findById(member.getMemberId());
        log.info("findMember={}",findMember);
        assertThat(findMember).isEqualTo(member);
    }

JDBC 개발 - 수정, 삭제

데이터를 변경하는 쿼리(등록,수정,삭제) executeUpdate사용 -> 여기서는 ResultSet사용안하므로, close메서드 호출할때 rs 인자 null임
수정

public  void update(String memberId,int money){
        String sql = "update member set money=? where member_id=?";

        Connection con = null;
        PreparedStatement pstmt = null;

        try {
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setInt(1,money);
            pstmt.setString(2,memberId);
            int resultSize = pstmt.executeUpdate();
            log.info("resultSize = {}",resultSize);
        } catch (SQLException e) {
            log.error("db error",e);
            throw new RuntimeException(e);
        }finally {
            close(con,pstmt,null);
        }
    }

삭제

public void delete (String memberId) throws SQLException{
        String sql = "delete from member where member_id=?";
        Connection con = null;
        PreparedStatement pstmt = null;

        try{
            con = getConnection();
            pstmt = con.prepareStatement(sql);
            pstmt.setString(1,memberId);
            pstmt.executeUpdate();
        } catch (SQLException e){
            throw e;
        }finally {
            close(con,pstmt,null);
        }
 }

Test code

   @Test
    void crud() throws SQLException{
        //save
        Member member = new Member("memberV8",10000);
        repository.save(member);

        Member findMember = repository.findById(member.getMemberId());
        log.info("findMember={}",findMember);
        assertThat(findMember).isEqualTo(member);

        //update: money를 1000 => 20000
        repository.update(member.getMemberId(),20000);
        Member updatedMember = repository.findById(member.getMemberId());
        assertThat(updatedMember.getMoney()).isEqualTo(20000);

        //delete
        repository.delete(member.getMemberId());
        assertThatThrownBy(() -> repository.findById(member.getMemberId())).isInstanceOf(NoSuchElementException.class);
    }

}

delete부분에서 findById하면 삭제했으므로, NosuchEelementException발생(findById메서드에서 catch부분에 NosuchEelementException던졌음)
assertThatThrownBy로 검증

profile
항상 Why?[왜썻는지] What?[이를 통해 무엇을 얻었는지 생각하겠습니다.]

0개의 댓글