JDBC
- Java Database Connectivity
- 데이터베이스와의 연결과 쿼리 수행을 제공하는 Java의 API
대표기능 3가지
- db와 같은 데이터 소스와 연결
- db에 쿼리 전송
- 요청에 따른 db쿼리 결과 처리
주로 쓰이는 JDBC API 클래스와 인터페이스
- java.sql.Connection
- Connection객체: Java 프로그램이 외부 데이터베이스 시스템과 연결될 때 사용됨.
- db와의 연결을 설정하고 유지하며, SQL 쿼리를 실행하거나 트랜잭션을 관리
- db연결 생성 및 종료, 커밋/롤백 등 트랜잭션 관리, Statement/PreparedStatement 등 객체 생성 기능 제공
- setAutoCommit(true) → default
- 롤백처리가 필요하다면 false로 설정하고, 명시적으로 rollback() 또는 commit()을 호출할 수 있음.
- java.sql.Statement
- SQL 쿼리를 실행하기 위한 가장 기본적인 인터페이스
- SQL 쿼리 실행할 때마다 새롭게 컴파일 되며, static 쿼리에 해당함.
- 쿼리를 직접 실행하기 때문에 SQL Injection 공격에 취약함.
- java.sql.PreparedStatement
- 미리 컴파일된 SQL 쿼리를 실행하는 데 사용되는 쿼리
- 쿼리의 구조는 주로 고정되어 변수 값을 Java 메서드로 안전하게 삽입하여 Statement보다 보안이 좋음.
- 쿼리가 미리 컴파일되기 때문에 동일 쿼리를 여러 번 실행될 때 성능이 향상됨. 이때, classParameters() 메서드로 인자 값을 초기화할 수 있음.
SQL Injection은 애플리케이션 단에서 db로 보내는 SQL 쿼리의 허점을 악용하는 db 공격 기법. PreparedStatement는 SQL 쿼리를 미리 컴파일하고, ? 등의 placeholder로 파라미러를 받아 수행하기 때문에 삽입되는 데이터는 SQL문이 아닌 일반적인 데이터로 취급됨. 코드와 데이터의 분리로 인해 삽입된 데이터는 SQL 구조에 영향을 끼칠 수 없으며, SQL Injection에 흔히 사용되는 특수 문자 등도 철저히 데이터로만 취급되어 무시됨.
- java.sql.ResultSet
- SQL 쿼리의 실행 결과를 저장하는 객체
- 테이블 형식으로 데이터를 저장함.
- java.sql.Blob
- 대용량의 이진 데이터를 저장하고 처리하기 위한 객체
- 이미지, 비디오, 파일 등과 같은 큰 바이너리 데이터를 db에 저장할 때 사용됨.
- java.sql.CallableStatement
- Stored Procedure를 호출함.
- 이는 저장되어 재사용될 수 있는, 준비된 SQL 코드. 자주 쓰이는 SQL 질의는 stored procedure에 저장하여 단순 호출로 재수행할 수 있음.
- 이미 저장되어 있는 코드를 단순히 호출하기 때문에 속도가 더 빠를 수 있고, SQL 코드 없이 Java 코드만 사용하기 때문에 SQL에 독립적인 코딩이 가능함.
JDBC의 구조
- JDBC manager간 소통을 지원하는 JDBC API, JDBC manager
- 데이터베이스 드라이버 간 소통을 지원하는 JDBC Driver
- Java 애플리케이션은 JDBC API와 소통하고, JDBC API는 JDBC Driver와 통신하여, JDBC Driver는 해당 db와 통신
- JDBC API
- 여러 종류의 다수의 데이터베이스와 분산 환경을 이뤄 사용할 수 있음.
- JDBC Driver Manager
- Java 애플리케이션을 JDBC Driver에 연결하는 객체를 정의함.
- JDBC Test Suite
- Test Suite: 테스트의 묶음.
- JDBC Driver가 잘 동작하는지, JDBC API의 주요 기능을 테스트해보는 역할을 수행.
- JDBC-ODBC Bridge
- ODBC는 언어 독립적인 db 접근 표준
- Java Software Bridge는 ODBC driver를 통한 JDBC 접근을 제공함.
- 성능 등의 이유로 ODBC driver를 통한 접근은 잘 사용되지 않는 방법.
JDBC의 동작
- 애플리케이션은 데이터베이스의 JDBC 자원을 얻기 위해 JNDI API를 호출함.
- JDBC 자원을 통해 애플리케이션은 데이터베이스 연결을 얻음.
- 이 때부터 데이터베이스 조회, 수정, 삭제, 삽입이 가능.
- 애플리케이션이 데이터베이스 작업 완료 후에는 데이터베이스 연결을 닫고 커넥션 풀에 반납함.
- 커넥션 풀
- 데이터베이스 커넥션을 미리 구비하고, 애플리케이션이 커넥션을 요청하거나 반납할 수 있음.
- 매번 요청이 들어온 순간부터 커넥션을 만들어 사용하는 것보다 효율적이며, 데이터 또는 클라이언트 수의 증가에 빠르게 대응 가능.
JDBC 결론
- Java 코드로 데이터베이스를 관리할 수 있는 장점
- 드라이버 설정, 연결 관리, SQL 쿼리 작성, 그리고 쿼리 결과 처리와 같은 작업들을 직접 처리해야함.
- 이런 반복적이고 오류가 발생하기 쉬운 작업들 → 생산성을 높이고 코드를 더 간결하게 만들기 위해 이러한 작업들을 자동으로 처리해주는 여러 프레임워크의 등장.
ORM
- Object-Relational Mapping: 객체지향적으로 데이터베이스에 질의하고 데이터를 처리하는 기술
- ORM 라이브러리는 주로 데이터를 처리하기 위해 필요한 코드(SQL문 포함)를 캡슐화하여 이 라이브러리를 사용하는 개발자들은 SQL문 없이 해당 라이브러리의 언어로만 데이터 조작 가능.
사용 이유
- 코드의 중복 감소
- 데이터 모델을 한 곳에 정의하고 이를 통해 데이터베이스와 상호작용
- 매번 SQL 쿼리를 작성하는 대신, 모델을 통해 데이터 조회/삽입/삭제 등을 반복적으로 사용할 수 있어 코드 중복이 줄어듬.
- 데이터베이스 독립성 보장
- 특정 DBMS에 의존적인 SQL 문법도 추상화되어 DBMS를 바꾸더라도 최소한의 변경사항만으로 프로젝트를 정상적으로 수행 가능.
- 테이블 관리가 쉬워짐
- ORM은 엔티티 매핑으로 테이블을 자동으로 생성하는 등 변경 사항을 자동으로 반영해주는 도구를 제공 ⇒ 데이터베이스 관리가 쉬워짐.
- MVC 아키텍처를 강제
- 데이터 모델을 통해서 데이터베이스를 관리해야 하기 때문에 비즈니스 로직을 처리하는 레이어와 자연스럽게 분리됨.
- 역할의 분리 → 유지보수와 테스트 등의 용이성으로 이엉짐.
- 최소한의 보안과 쿼리 최적화가 보장됨
- 언어의 객체지향성을 보장하며 데이터베이스와 매핑 가능
- ORM은 객체지향 언어와 RDB 간의 간극을 해소하여 데이터베이스 조작을 더 용이하게 해줌.
- 객체로 데이터베이스의 레코드를 다룰 수 있게 해주어 객체의 속성 및 메서드에 집중하여 코드를 짤 수 있음.
- ex. 상속과 같이 관계형으로 표현하기 까다로워지는 엔티티 관계도 ORM으로 간단하게 매핑될 수 있음.
단점
- 아무래도 DBA가 더 좋은 성능의 쿼리를 짤 것 …
Hibernate
- Java를 위한 ORM 프레임워크
- JDBC만 활용하면 개발자가 SQL문을 직접 짜야하는 반면, Hibernate는 ORM 프레임워크기 때문에 개발자는 Java의 객체지향성에 집중하여 Java로만 개발 가능
- JDBC에 대한 의존성이 100% 사라진 것은 아님.
- Hibernate 또한 JDBC를 통해 데이터베이스와 상호작용하고, JDBC API위에 만들어졌기 때문.
- 그러나 Hibernate가 JDBC API를 추상화해주는 덕에 더 쉽고 편하게 데이터베이스 관리가 가능함.
JPA
- JPA vs. Hibernate
- JPA: 명세
- Hibernate: 구현
- JPA라는 하나의 ORM 명세 표준에 대한 여러 구현 프레임워크 중 하나가 Hibernate.
- 단순히 어떻게 ORM을 구현해야 하는가에 대한 약속만 정의하여 구현체가 없는, 추상적인 인터페이스로 구성되어 있음. ⇒ 실질적인 기능은 제공하지 않음.
- JPA는 ORM의 일관된 표준화를 추구하여 개발자들이 일관된 API를 사용하고, 특정 프레임워크에 종속되지 않는 코드를 작성할 수 있도록 함.
- @Entity, @Table 등 객체와 데이터베이스 간 매핑 정의, 영속성 컨텍스트 관리, 트랙잭션 등을 모두 정의.
Persistence
- 영속성 컨텍스트: 애플리케이션이 종료된 후에도 그 애플리케이션이 생성한 데이터가 저장된다는 개념
- 특성에 따라 1개 이상의 엔티티 매니저와 연관되어 엔티티 인스턴스와 그 라이프사이클이 관리되는 공간이기도 함.
- 각 엔티티 인스턴스가 생성, 저장, 삭제되는 범위를 정의함.
- 영속성 엔티티 집합을 소유하는 캐시와도 같음.
- 하나의 트랜잭션이 완료되면 persistent 객체는 모두 persistent context로부터 준영속 상태가 되어 더이상 관리되지 않음.
JPA의 구성
- Persistence unit
- Java 클래스가 관계형 데이터베이스에 어떻게 매핑되는지 정의하고, 데이터베이스 연결 설정, 트랜잭션 관리 등에 대한 정보를 포함함.
- EntityManagerFactory는 이 데이터로 영속성 컨텍스트를 만들어 EntityManager를 통해 제공함.
- EntityManagerFactory
- 데이터베이스 작업을 위한 EntityManager 인스턴스를 생성함.
- Persistence context
- 영속성 컨텍스트는 엔티티 인스턴스의 라이프사이클을 관리하는 런타임 환경.
- 현재 애플리케이션이 조작하는 활성 엔티티 인스턴스의 집합을 보유함.
- 이 컨텍스트는 엔티티의 변경사항을 추적하고, 데이터베이스와의 상태를 관리함.
- 수동으로 생성하거나 의존성 주입으로 얻을 수 있으며, 주로 애플리케이션 서버에 의해 관리됨.
- EntityManager
- 애플리케이션과 영속성 컨텍스트를 잇는 역할을 수행.
- 엔티티 인스턴스를 생성, 수정, 읽기, 삭제하는 메서드를 제공
- 객체와 관계형 간 매핑의 메타데이터를 관리
- EntityManager의 인스턴스는 의존성 주입 또는 EntityManagerFactory에서 직접 얻을 수 있음.
- Entity 객체들
- 엔티티 객체는 데이터베이스 테이블의 행 하나를 표현하는 단순한 Java 클래스
엔티티의 라이프사이클
- 비영속(Transient)
- 엔티티가 새로 생성되어 아직 영속성 컨텍스트에 저장되지 않은 상태
- 영속(Persistent)
- 영속성 컨텍스트에 저장된 상태.
- 어떠한 변경이 일어나면 캐시에 반영됨.
- 준영속(Detached)
- 영속 상태에서 데이터베이스와의 연결이 끊어진 상태.
- 데이터베이스와의 동기화가 이루어지지않음.
- ex. 세션이 종료되거나 명시적으로 분리된 상태
- 삭제(Removed)
- 아직은 영속성 컨텍스트에 있는 엔티티가 데이터베이스에서 삭제된 상태
영속 상태 관리하기(강제 저장하기)
- 영속 컨텍스트는 메모리에 저장되고, 간헐적으로 엔티티 매니저에 의해 데이터베이스와 동기화됨.
- 이 과정을 flushing이라고 함.
플러쉬는 아래와 같은 상황에 수행됨.
- 쿼리 수행 전
- java.persistence.EntityTransaction.commit()이 호출되었을 때
- EntityManager.flush()가 호출되었을 때
플러쉬에 의해 SQL문은 아래와 같은 순서로 수행됨.
- 모든 엔티티 삽입: EntityManager.persist()가 사용된 순서를 유지하여 삽입
- 모든 엔티티 수정
- 모든 컬렉션 삭제
- 모든 컬렉션 요소 삭제/수정/삽입
- 모든 컬렉션 삽입
- 모든 엔티티 삭제
- flush() 메서드를 명시적으로 호출하지 않는 이상, 엔티티 매니저가 즉각적으로 데이터베이스와 동기화될 거라는 보장은 없음.
- but, Hibernate는 Query.getResult(), Query.getSingleResult()의 메서드가 꼭 최신의 데이터를 반환하는 것을 보장함.
JPQL
- JPQL(Java Persistence Query Language)
- JPA가 제공하는 객체 지향 쿼리 언어
- 데이터베이스 테이블 대신 JPA 엔티티 객체를 대상으로 쿼리를 수행.
- SQL과 유사한 문법을 사용하지만, 데이터베이스 테이블이 아닌, 엔티티의 필드와 관계를 기준으로 작동함.
장점
- 데이터베이스 구조에 직접 의존하지 않음.
- 데이터베이스 스키마에 직접 의존하지 않고, 객체 모델을 기반으로 쿼리를 작성하기 대문에 엔티티 모델에 의존함.
- 엔티티 간 관계를 단순하게 표현하고, 사용할 수 있음.
- JPQL은 객체 모델을 그대로 활용하기 때문에 일대일, 일대다, 상속, 다형성 등 객체 간의 관계를 보다 쉽게 다룰 수 있음.
- 1:1, 1:N 등 관계를 표현하기 위해서는 여러 조인 연산을 필요로 하는 SQL문에 비해 큰 장점
- DBMS 독립성을 보장함.
단점
- 특정 DBMS에 의존적인 기능을 사용하지 못함.
- JPA에 데이터베이스 추상화를 한 레이어 더 추가했기 때문에 JPA에서는 네이티브 쿼리로 가능했던 특정 DBMS에 의존적인 기능도 JPQL에서는 사용 불가능.
- 성능을 보장하기 어려움.
- JPA 구현체가 JPQL 쿼리를 SQL 문으로 번역하는 과정에서 JPA 구현체가 자동으로 쿼리 최적화를 시도하지만, 항상 효율적인 것은 아님.
JPQL 동작 방식
- 애플리케이션이 javax.persistence.EntityManager 인터페이스이 인스턴스를 생성함.
- EntityManager는 javax.persistence.Query 인터페이스의 인스턴스를 생성함.
- Query 인스턴스가 쿼리를 수행함.
JPQL의 종류
- Dynamic Query (동적쿼리)
- 런타임 시점에 애플리케이션의 요구에 따라 동적으로 생성되는 쿼리.
- Named Query
- 동일 쿼리를 여러 번 호출하는 상황에서 쓰이기 위한 쿼리, name 속성으로 식별되고, query 속성에 쿼리 문을 담음.
- 네임드 쿼리는 컴파일된 상태에서 재호출하기 때문에 런타임 동안 성능이 더 낫고, 같은 쿼리문을 다시 작성할 필요를 줄여 코드 재사용성도 향상됨.
QueryDSL
- JPQL의 주요 단점 중 하나는, 쿼리 문자열에 오타 혹은 문법적인 오류가 컴파일 타임이 아닌 런타임 시점에 검출됨. (정적 쿼리일 경우 → 애플리케이션 로딩 시점에 검출됨.)
- QueryDSL은 정적 타입으로 SQL 등 쿼리를 생성하는 프레임워크. 해당 문제점을 아래와 같이 해결하고자 시도함.
- 문자가 아닌 코드로 쿼리를 작성해서 컴파일 시점에 문법 오류를 검출함.
- 동적 쿼리의 작성이 더 편리함.
- 쿼리 작성 시 제약 조건 등을 메서드로 추출하여 코드 재사용성을 높임.
QueryDSL 동작 방식
- 프로젝트 내의 @Entity 선언 클래스를 탐색함.
- JPAAnnotationProcessor를 사용해 각 엔티티에 대한 Q 클래스를 생성함. Q 클래스는 엔티티의 각 필드와 메서드를 정적 타입으로 제공함.
참고자료
- https://velog.io/@becooq81/JDBC-JPA-JPQL-QueryDSL
- 여러 블로그 자료들 ..
'공부' 카테고리의 다른 글
[DDD] Aggregate란? (0) | 2025.04.01 |
---|---|
[JPA] JPA의 N + 1 문제 정리 (0) | 2025.03.27 |
[JPA] FetchType 정리 (0) | 2025.03.27 |