자바를 사용해서 DB와 상호 작용하기 위한 자바 표준 인터페이스로, DBMS와 통신하여 데이터를 CRUD할 수 있게 해준다.

JDBC 특징

  1. 표준 API
    • 대부분의 RDBMS(관계형 DBMS)에 대한 드라이버가 제공되어 여러 종류의 DB 대해 일관된 방식으로 상호 작용할 수 있다.
    • Database 종류가 바뀌어도 쿼리문이 실행된다.
  2. 데이터베이스 연결
  3. SQL 쿼리 실행
  4. Prepared Statement
  5. 결과 집합 처리(Result Set)
    • 데이터베이스로부터 반환된 결과 집합을 처리할 수 있다.

 

6. 트랜잭션 관리

  • JDBC를 사용하여 데이터베이스 트랜잭션을 시작, 커밋(성공) 또는 롤백(실패)하는 등의 트랜잭션 관리 작업을 수행할 수 있다.

 


Statement VS Prepared Statement

Java에서 데이터베이스에 SQL 쿼리를 실행하기 위한 인터페이스

 

1. Statement

  • DB와 연결되어 있는 Connection 객체를 통해 SQL문을 Database에 전달하여 실행하고, 결과를 반환받는 객체
  • SQL 쿼리를 직접 문자열로 작성하여 DB에 전달
  • 쿼리는 문자열 형태로 전달되고, 실행 시점에 DB에 파싱되어 실행
  • => 실행할 때 마다 쿼리를 파싱하므로 성능에 영향을 미칠 수 있고, 보안 취약점을 가질 수 있다.
public class StatementExample {
    public static void main(String[] args) {
        try {
            // MySqlDriver 파일을 라이브러리에 추가한다.

            // Driver 연결
            Class.forName("mysql.jdbc.driver.MySqlDriver");

            // Database와 연결(계정 접속)
            Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost/mydatabase", "username", "password");

            // Statement 인스턴스 생성
            Statement statement = connection.createStatement();

            // SQL Query 작성
            String query = "SELECT * FROM MEMBER WHERE NAME = 'wonuk'";

            // Query 실행 -> 결과는 ResultSet으로 반환됨
            ResultSet rs = statement.executeQuery(query);

            // 결과 처리
            while (rs.next()) {
                // 결과 처리 로직
            }

            // 수동으로 연결 해제
            rs.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

 

 


2. Prepared Statement

  • SQL 쿼리를 미리 컴파일(preCompile)하여 DB에 전송할 때 값만 바뀌는 형태로 전달
    • prepareStatement()를 호출할 때, 쿼리를 DB에 전달하여 미리 컴파일
    • 이후 excuteUpdate()를 호출할 때, ?에 동적인 입력값을 바인딩해서 실행
  • 쿼리가 한 번 컴파일되면 여러 번 실행할 수 있으며, 성능이 향상되고 보안 측면에서 더 안전함
  • 동적인 입력값을 placeholder ?로 대체하고 파라미터 바인딩을 통해 쿼리를 삽입
public class PreparedStatementExample {
    public static void main(String[] args) {
        try {
            // MySqlDriver 파일을 라이브러리에 추가한다.
            Class.forName("mysql.jdbc.driver.MysqlDriver");

            // Database와 연결
            Connection connection = DriverManager.getConnection(
                "jdbc:mysql://localhost/mydatabase", "username", "password");

            // SQL Query 작성
            String query = "SELECT * FROM employees WHERE department = ?";

            // PreparedStatement 생성 및 값 설정
            PreparedStatement preparedStatement = connection.prepareStatement(query);
            preparedStatement.setString(1, "HR");  // ? 위치에 HR 바인딩

            // Query 실행
            ResultSet resultSet = preparedStatement.executeQuery();

            // 결과 처리
            while (resultSet.next()) {
                // 결과 처리 코드
            }

            // 연결 해제
            resultSet.close();
            preparedStatement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

 


Persistence Framework

JDBC의 한계

  • 간단한 SQL을 실행하는 경우에도 중복 코드가 많다.
  • DB에 따라 일관성 없는 정보를 가진 채로 Checked Exception(SQL Exception) 처리를 한다.
    • Checked Exception인 SQLException은 개발자가 명시적으로 예외처리해야 하는데, DBMS마다 고유한 SQL 문법과 오류 코드 체계를 가지고 있어서 모든 DBMS에 적합한 예외처리를 수행할 수 없다.
  • Connection과 같은 공유 자원을 제대로 반환하지 않으면 한정된 시스템 자원(CPU, Memory)에 의해 서버가 다운되는 등의 문제가 발생한다.
  • SQL Query를 개발자가 직접 작성한다.

Persistence Framework

  • JDBC 처럼 복잡함이나 번거로움 없이 간단한 작업만으로 Database와 연동되는 시스템
  • 모든 Persistence Framework는 내부적으로 JDBC API를 이용하므로 preparedStatement를 기본적으로 사용
  • 크게 SQL Mapper, ORM 두가지로 나눌 수 있다.

SQL Mapper

  • SQL 문의 실행 결과 <-> 객체(Object)의 필드를 매핑하여 데이터를 객체화
  • 대표적인 SQL Mapper로 Spring JDBC Template, MyBatis가 있다.
  • 한계
    • SQL을 직접 다룬다.
    • 특정 DB에 종속적으로 사용하기 쉽다.
    • 테이블마다 비슷한 CRUD SQL, DAO(Data Access Object) 개발이 반복된다 (코드 중복)
    • 테이블 필드가 변경될 시 이와 관련된 모든 DAO의 SQL문, 객체의 필드 등을 수정해야 한다.
    • 객체와의 관계는 사라지고 DB에 대한 처리에 집중하게 된다.
// JDBC template

// 1. XML OR Gradle에 Spring JDBC 의존성 추가
// 2. application.properties OR application.yml에 데이터베이스 연결 설정

@RestController
public class MemberController {
    private final MemberRepository memberRepository;

    public MemberController(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @GetMapping("/members")
    public List<Member> findById(Long id) {
        return memberRepository.findById(id);
    }
}

// Member Object
public class Member {
    private Long id;
    private String name;
    private int age;

    // Getter and Setter methods
}

// Repository Anotation의 역할에 대해 공부해주세요.
@Repository
public class MemberRepository {
    private final JdbcTemplate jdbcTemplate;

    public MemberRepository(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

		// Member 객체로 리턴한다.
    public List<Member> findById(Long id) {
        String query = "SELECT * FROM MEMBER WHERE id = " + id;
        return jdbcTemplate.query(query, (rs, rowNum) -> {
            Member member = new Member ();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            member.setAge(rs.getInt("age"));
            return member;
        });
    }
}

 

=> 객체 중심의 객체지향데이터 중심의 RDB의 패러다임 불일치 문제를 해결하기 위해 나온 것이 ORM