DB 연결 (1)

JIWOO YUN·2024년 4월 5일
0

SpringDB

목록 보기
2/11
post-custom-banner

데이터베이스 연결

  • H2 데이터베이스를 사용하기 때문에 H2 데이터베이스 서버를 먼저 실행해두고 진행.

필요한 기본 정보를 편리하게 사용하기 위해서 추상 함수 추가

public abstract class ConnectionConst {

     public static final String URL = "jdbc:h2:tcp://localhost/~/Jdbc";
    public static final String USERNAME = "sa";
    public static final String PASSWORD = "1234";
}

실제 DB에 연결하는 코드

@Slf4j
public class DBConnectionUtil {

    public static Connection getConnection(){

        try {
            Connection connection = DriverManager.getConnection(URL,USERNAME,PASSWORD);
            log.info("get Connection={} ,class={}",connection,connection.getClass());

            return connection;

        }catch (SQLException e){
            throw new IllegalStateException(e);
        }
    }
}

데이터 베이스 연결 테스트

@Slf4j
class DBConnectionUtilTest {

    @Test
    void connection(){
        Connection connection = DBConnectionUtil.getConnection();
        Assertions.assertThat(connection).isNotNull();
    }

}
  • 주의점

    • slf4j 어노테이션이 작동이 안될수 있는데 gradle에 설정이 추가로 필요하다.

    • testAnnotationProcessor 'org.projectlombok:lombok'
      testImplementation 'org.projectlombok:lombok'

실행결과

INFO hello.jdbc.connection.DBConnectionUtil -- get Connection=conn0: url=jdbc:h2:tcp://localhost/~/Jdbc user=SA ,class=class org.h2.jdbc.JdbcConnection

--> H2 전용 데이터베이스 드라이버가 제공하는 H2전용 커넥션

JDBC DriverManager 연결 이해

JDBC 는 java.sql.Connection 표준 커넥터 인터페이스를 정의

  • H2 데이터베이스 드라이버는 JDBC Connection 인터페이스를 구현한 org.h2.jdbc.JdbcConnection 구현체 제공

  1. 애플리케이션 로직에서 커넥션이 필요하면 DriverManagㅋer.getConnection() 호출
  2. DriverManager는 라이브러리에 등록된 드라이버 목록을 자동으로 인식
    1. 이 드라이버들에게 순서대로 다음 정보를 넘겨서 커넥션을 획득할 수있는지 체크
      • URL :현재 예시로는 jdbc:h2:tcp://localhost/~/Jdbc
      • 이름, 추가 비밀번호등 접속에 필요한 정보
      • URL 정보를 체크해서 본인이 처리할 수있는 요청인지 확인한다. 위의 경우 jdbc:h2로 시작하기 때문에 H2 데이터베이스에 접근 규칙을 사용중이기 때문에 H2 드라이버는 본인이 처리할 수있기 때문에 실제 데이터베이스에 연결해서 커넥션을 획득
      • 만약 MySQL 드라이버가 먼저 실행시 본인이 처리할 수 없기 때문에 처리할 수없다는 반환을 보내고 다음 드라이버에게 순서가 넘어가게 된다.
  3. 찾은 커넥션 구현체가 클라이언트에 반환된다.

JDBC 개발 - 등록

JDBC 를 사용해서 회원 데이터를 데이터베이스의 관리하는 기능을 개발진행.

Member 객체 추가

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

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

회원을 등록하는 MemberRepositoryV0 버전 추가 진행

  • 내부코드
    public Member save(Member member) throws SQLException {
        String sql = "inset into member(member_id, money) values(?, ?)";

        Connection con = null;
        PreparedStatement pstmt = null;

        try {

            con = getConnection();
            pstmt = con.prepareStatement(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 {
            close(con, pstmt, null);
        }
    }

    private void close(Connection con, Statement stmt, ResultSet rs) {
        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(){
        return DBConnectionUtil.getConnection();
    }
  • save 함수
    • String sql => 데이터베이스를 SQL을 정의
    • con.prepareStatement(sql) : 데이터베이스에 전달할 SQL 과 파라미터로 전달할 데이터 준비
      • pstmt.setString(1,member.getMemberId()) : SQL의 첫번쨰 ? 에 값을 지정
        • 문자이기 때문에 setString 사용
      • pstme.setInt(2,member.getMoney()) : SQL의 두번째 ? 에 값 지정
        • Int 형 숫자기 때문에 setInt 사용
    • pstmt.executeUpdate();
      • Statement를 통해 준비된 SQL을 커넥션을 통해 실제 데이터베이스에 전달
        • 이 함수는 int를 반환해줌 -> 영향받은 DB row 수를 반환해줌.
  • close 함수를 사용하는 이유
    • 만약 pstmt를 닫는 와중에 SQLException이 발생할 경우 try catch가 끝나서 Connection이 끊어지지 않게 되는 상황이 발생하게됨 -> 리소스 누수 발생
      • 이걸 해결하기위해서 각각에 try catch를 해줘야하는 상황이기 때문에 하나의 함수에 묶어서 각각의 try - catch 문으로 묶어준다.

쿼리 실행하고 리소스 정리는 꼭 해주자.

  • 리소스를 정리할 때는 역순으로 진행
  • Connection을 통해서 prepareStatement를 만들었기 때문에 리소스 반환시 prepareStatement 먼저 종료하고 Connection을 종료

만약 정리를 하지 않게되면 어떻게 될까?

  • 커넥션이 끊어지지 않고 계속 유지되어 리소스 누수가 발생해서 나중에 커넥션 부족으로 장애가 발생할 수있다.

PrepareStatement 사용을 권장

  • PreparedStatement 의 경우 파라미터 바인딩을 통해서 파라미터를 넣어준다.

  • SqlInjection 예방 가능

    • 쿼리가 들어오는 것을 방지 -> 단순하게 데이터로만 취급되기 때문에 쿼리가 들어오는 것을 방지함.

JDBC 개발 - 조회

repositoryV0 에 조회하는 코드 추가

public Member findById(String memberId) throws SQLException{
    String sql = "select * from member where member_id = ?";

    Connection con = null;
    PreparedStatement pstmt = null;
    ResultSet rs =  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);
        throw  e;

    } finally {
        close(con,pstmt,rs);
    }

}

pstmt.executeQuery()

  • 이 코드의 경우에는 데이터를 조회하기 때문에 executeQuery()를 보내고 결과를 ResultSet 에 담아서 반환

  • 데이터 변경시 에서는 executeUpdate() 사용


Result Set이란?

  • select 쿼리의 결과가 순서대로 들어가 있는 데이터
  • 만약 select member_id,money 라고 지정한경우 -> member_id,money라는 이름으로 데이터가 저장됨.
  • select * 을 사용시 테이블의 모든 컬럼 다 저장.

ResultSet의 경우 최초의 커서의 경우 데이터를 가리키고 있지 않기 때문에 rs.next()를 최소 한번 호출 해줘야 데이터를 조회할 수있다는 것을 기억하자.

데이터를 꺼내는 방법

  • 커서를 이동 시키고 member_id를 꺼내고 싶은 경우
    • rs.getString("member_id") : 현재 커서가 가리키고 있는 위치의 member_id 데이터를 String 타입으로 반환
  • money를 꺼내고 싶은 경우
    • rs.getInt("money") : 현재 커서가 가리키고 있는 위치의 money 데이터를 int 타입으로 반환

profile
열심히하자
post-custom-banner

0개의 댓글