개발자는 SQL문으로 데이터베이스에 접속해서 기능을 이용할 수 있지만 클라이언트의 유저는 UI로 요청을 보내므로 프로그램은 요청에 맞는 쿼리문을 만들고 DB API를 통해 데이터베이스에 대한 접근 권한을 얻고, 데이터를 검색, 추가, 수정, 삭제 등의 작업을 수행할 수 있습니다.
DB API로는 JDBC, ODBC, ADO.NET 등이 있는데 그중 자바에서 데이터베이스 프로그래밍을 하기 위해 사용되는 API 중 하나인 JDBC를 사용해보려고 합니다.
✔ JDBC 드라이버(JDBC Driver)
JDBC 드라이버는 데이터베이스와의 통신을 담당하는 인터페이스인데, Oracle이나 MS SQL, MySQL 같은 다양한 벤더에서는 해당 벤더에 맞는 JDBC 드라이버를 구현해서 제공을 하게 되고, 우리는 이 JDBC 드라이버의 구현체를 이용해서 특정 벤더의 데이터베이스에 액세스 할 수 있습니다.
🔍 JDBC API가 없다면?
데이터베이스 관리 시스템 회사마다 DB API의 기능은 동일하지만, 작동하는 함수 이름 등이 차이가 있을 것이므로, 데이터베이스에 연결하는 코드에는 차이가 있습니다.
똑같은 쿼리문의 기능을 수행하더라도 데이터베이스에 연결하는 코드에는 차이가 있어서 DBMS마다 가지고있는 각각의 기능을 전부 알아야한다는 문제점을 가지고 있습니다.
만약 오라클 DB를 이용하다가 MS SQL DB로 변경하게 된다면 코드의 수정이 많을 것입니다.
JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접근하고 조작하기 위한프로그래밍을 하기 위한 API(Application Programming Interface)입니다. JDBC는 프로그램과 DBMS 간의 통신을 중간에서 번역하는 역할을 합니다. 이를 통해 개발자는 데이터베이스에 SQL 문으로 접근하여 데이터를 주고 받을 수 있게 됩니다.
JDBC 자체는 인터페이스와 클래스로 구성된 API이며, 이 API를 사용하는 데 필요한 실제 코드는 JDBC를 구현하는 라이브러리에서 제공됩니다. 데이터베이스 벤더들은 이러한 인터페이스를 구현하는 클래스를 제공하여 개발자가 특정 데이터베이스와 상호작용할 수 있도록 합니다. 이런식으로 JDBC 인터페이스와 구현체 클래스가 결합하여 데이터베이스와의 통신을 처리합니다.
JDBC는 오라클과 같은 DBMS를 연결하는 도구들을 어플리케이션 사용자가 직접 사용하지 않고, 단일화된 도구를 가지고 있습니다. 이를 통해 DBMS의 차이점을 모르더라도 JDBC를 통해 DB와 연결을 하는 API를 이용할 수 있습니다.
🔍 JDBC API를 사용한다면
JDBC API는 여러 DBMS에 대한 드라이버를 제공합니다. JDBC를 사용함으로써 오라클DB에서 mySQL으로 DBMS를 변경하더라도 JDBC API의 코드를 수정하지 않아도 되어 코드 유지보수를 용이하게 합니다. JDBC를 이용하면 각각의 DBMS를 다루는 방법을 배울 필요 없이 JDBC의 표준 문법만으로 여러 DBMS를 사용할 수 있습니다
일반적으로 데이터베이스에서 쿼리를 실행하면 그 결과는 일반적으로 데이터베이스 테이블의 행 및 열 형태로 반환됩니다. 하지만 웹 서비스에서 데이터를 전송하거나 다른 응용 프로그램에서 데이터를 공유할 때는 XML 형식을 사용합니다. JDBC는 테이블 데이터를 XML형식으로 자동으로 변환하는 JDBC API의 기능을 제공하는 장점도 있습니다.
JDBC API를 사용해 데이터베이스와 구체적인 인터랙션을 하기 위해서는 JDBC 드라이버를 먼저 로딩한 후에 데이터베이스와 연결을 해야 합니다.
JDBC의 구체적인 API 사용법을 알 필요는 없지만 JDBC의 동작 흐름을 알면 Spring에서 지원하는 데이터 액세스 기술을 사용하는데 도움이 됩니다.
JDBC Driver는 이 JDBC API를 구현하기 위한 소프트웨어 모듈로, 데이터베이스 벤더(Oracle, MySQL, SQL Server 등)에서 제공됩니다.
JDBC 드라이버는 데이터베이스 벤더별로 제공되며, JDBC 드라이버는 각각의 데이터베이스 벤더에서 구현되어 있습니다. 예를들어, 개발자가 오라클 데이터베이스와 통신하려면 해당 데이터베이스 벤더인 오라클 드라이버를 사용해야 합니다.
JDBC 드라이버는 일반적으로 클래스로 구현되며, 이 클래스는 JDBC 드라이버 매니저에 등록되어야 합니다. JDBC 드라이버 매니저는 드라이버 클래스를 로드하고, 드라이버 클래스의 객체를 생성하여 데이터베이스와의 연결을 관리합니다.
1. 사용하고자 하는 JDBC 드라이버를 로딩합니다.
JDBC 드라이버를 로딩하는 방법
- DriverManager 클래스는 JDBC 드라이버 매니저로, JDBC 드라이버를 등록하고, 데이터베이스와 연결을 관리합니다.
- Class.forName() 메서드는 자바 언어에서 동적으로 DriverManager 클래스를 로딩하기 위해 제공되는 메서드입니다.
JDBC 드라이버가 정상적으로 로딩되면 DriverManager.getConnection() 메서드를 사용하여 JDBC 드라이버를 로딩하고, 데이터베이스와 연결하는 세션(Session)인 Connection 객체를 생성합니다. 연결에 필요한 정보(주소, 사용자 이름, 비밀번호 등)를 매개변수로 받으며, 이 정보를 사용하여 데이터베이스와 연결을 수립합니다.
작성된 SQL 쿼리문을 실행하기 위한 Statement 객체를 생성합니다.
Statement, PreparedStatement, CallableStatement 등을 사용하여 SQL 문을 생성하고 실행합니다. Statement는 정적인 SQL 문을 처리하는 데 사용되며, PreparedStatement는 동적인 SQL 문을 처리하는 데 사용되며, CallableStatement는 저장 프로시저를 호출하는 데 사용됩니다.
생성된 Statement 객체를 이용해서 입력한 SQL 쿼리를 실행합니다.
SQL 문 실행 결과를 ResultSet 객체로 반환 받습니다. ResultSet 객체의 next() 메서드를 사용하여 결과 집합의 다음 레코드로 이동하며, 각 레코드에서 필드 값을 가져와 처리합니다.
ResultSet, Statement, Connection 객체 등 사용한 자원들은 반드시 close() 메서드를 사용하여 해제해야 합니다.
JDBC는 데이터베이스와의 연결을 위한 저수준(low level) API이기 때문에 JDBC를 사용하여 데이터베이스와 상호작용하기 위해서는, 개발자가 직접 SQL 문장을 작성하고, 결과를 처리하며, 연결 및 자원 해제를 수행해야 한다는 것을 의미합니다. 하지만, 이를 통해 높은 수준의 커스터마이징이 가능하며, 개발자가 직접적인 제어권을 가질 수 있다는 장점이 있습니다.
ResultSet, Statement, Connection 객체 등과 같은 자원은 데이터베이스 연결 및 쿼리 수행과 관련된 리소스를 할당받습니다. 사용한 자원은 반드시 해제(close)해야 합니다. 자원을 해제하면, 해당 자원이 사용한 시스템 리소스가 반환되고, 메모리 누수와 같은 문제를 예방할 수 있고, 데이터베이스에서 발생할 수 있는 부하를 감소시킬 수 있습니다.
예외처리
JDBC API는 데이터베이스와의 상호작용에서 예외가 발생할 가능성이 매우 높기 때문에 예외 처리를 적절히 수행하지 않을 경우, 프로그램이 비정상적으로 종료될 가능성이 있습니다.
대부분 연결 문제, SQL 문장 구문 오류, 데이터베이스 작업 실패 등의 상황에서 발생하는 SQLException 예외인데 try-catch-finally 블록을 사용하여 처리합니다.
Connection Pool은 데이터베이스 연결(Connection) 객체를 미리 생성하여 Pool에 보관하고, 요청이 있을 때마다 Pool에서 Connection 객체를 제공해주는 기술입니다.
JDBC API를 사용해서 데이터베이스와의 연결을 위한 Connection 객체를 생성하는 작업은 비용이 많이 들고 시스템의 성능을 떨어뜨리는 작업 중 하나입니다.
따라서 애플리케이션 로딩 시점에 Connection 객체를 미리 생성해 두고, 데이터베이스에 연결이 필요할 경우, Connection 객체를 새로 생성하는 것이 아니라 미리 만들어 둔 Connection 객체를 사용함으로써 애플리케이션의 성능을 향상할 수 있습니다.
이처럼 데이터베이스 Connection을 미리 만들어서 보관하고 애플리케이션이 필요할 때 이 Connection을 제공해 주는 역할을 하는 Connection 관리자를 바로 Connection Pool이라고 합니다.
Spring Boot 2.0 이전 버전에는 Apache 재단의 오픈 소스인 Apache Commons DBCP(Database Connection Pool, DBCP)를 주로 사용했지만 Spring Boot 2.0부터는 성능면에서 더 나은 이점을 가지고 있는 HikariCP를 기본 DBCP로 채택했습니다.
애플리케이션에서 데이터베이스에 접근하기 위해 SQL 쿼리문을 애플리케이션 내부에 직접적으로 작성하는 것이 중심이 되는 기술입니다.
대표적으로 mybatis와 Spring JDBC가 있습니다.
<select id="findMember" resultType="Member">
SELECT * FROM MEMBER WHERE member_id = #{memberId}
</select>
// mybatis, SQL Mapper라는 설정 파일이 존재하고, SQL 쿼리문을 직접적으로 작성
Member member = this.jdbcTemplate.queryForObject(
"select * from member where member_id=?", 1, Member.class);
// Spring JDBC, JdbcTemplate이라는 템플릿 클래스를 사용한 데이터베이스 접근 예시
이처럼 SQL 쿼리문이 직접적으로 포함이 되는 방식은 과거부터 많이 사용하던 방식이고, 현재도 사용이 되고 있긴 하지만 Java 진영에서는 SQL 중심의 기술에서 객체(Object) 중심의 기술로 지속적으로 이전을 하고 있는 추세입니다.
객체 중심 기술은 데이터를 SQL 쿼리문 위주로 생각하는 것이 아니라 모든 데이터를 객체 관점으로 바라보는 기술입니다.
객체 중심의 기술에서는 ORM(Object-Relational Mapping) 프레임워크가 대표적입니다. ORM 프레임워크를 사용하면 SQL 쿼리문을 직접 작성하지 않아도 객체 간의 관계를 표현할 수 있으며, ORM 프레임워크가 이를 해석하여 적절한 SQL 쿼리문을 생성하여 데이터베이스에 접근합니다. 대표적인 ORM 프레임워크로는 JPA(Java Persistence API)와 Hibernate가 있습니다.
JPA를 편리하게 사용하기위한 spring Data JPA와, JPA를 경량화한 spring Data JDBC도 있습니다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
private String productName;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
}
// JPA를 사용하여 Member 객체와 Order 객체 간의 다대일 관계를 매핑,
// ORM프레임워크가 애플리케이션 내부에서 Java 객체를 SQL 쿼리문으로 자동 변환한 후,
// 이를 데이터베이스에 전달하여 데이터를 조작합니다.
객체 지향적인 코드를 작성하면, SQL 쿼리문 작성에 따른 부수적인 작업을 줄이고, 코드의 가독성을 높일 수 있습니다.