337 lines
10 KiB
Markdown
337 lines
10 KiB
Markdown
# **Spring JDBC Client 소개 및 활용**
|
|
|
|
Spring 6부터 도입된 **`JdbcClient`**는 기존 `JdbcTemplate`보다 **더 간결하고 현대적인 API**를 제공합니다.
|
|
특히, **메서드 체이닝과 함수형 스타일의 코드 작성이 가능**하여, 기존 JDBC 코드보다 훨씬 직관적인 사용이 가능합니다.
|
|
|
|
이 글에서는 `JdbcClient`의 주요 기능과 사용법을 **예제와 함께 설명**하겠습니다.
|
|
|
|
---
|
|
|
|
## **1. JdbcClient란?**
|
|
|
|
`JdbcClient`는 **Spring 6 및 Spring Boot 3에서 새롭게 추가된 기능**으로,
|
|
기존 `JdbcTemplate`이 가진 **장점은 유지하면서도, 코드의 가독성과 유지보수성을 개선한 API**입니다.
|
|
|
|
✔ 기존 `JdbcTemplate`은 **보일러플레이트 코드가 많음**
|
|
✔ `JdbcClient`는 **메서드 체이닝을 지원하여 코드가 간결해짐**
|
|
✔ **SQL 실행 및 데이터 조회를 직관적으로 처리 가능**
|
|
|
|
---
|
|
|
|
## **2. JdbcClient 사용법**
|
|
### **📌 2.1 의존성 추가**
|
|
Spring Boot 3 이상에서는 **자동으로 포함**되므로 별도 의존성 추가는 필요 없습니다.
|
|
하지만, Spring Framework 6에서 단독으로 사용할 경우 다음과 같은 의존성이 필요합니다.
|
|
|
|
```xml
|
|
<dependency>
|
|
<groupId>org.springframework</groupId>
|
|
<artifactId>spring-jdbc</artifactId>
|
|
<version>6.x.x</version>
|
|
</dependency>
|
|
```
|
|
|
|
---
|
|
|
|
### **📌 2.2 `JdbcClient` 빈 설정**
|
|
`JdbcClient`는 **DataSource를 기반으로 생성**됩니다.
|
|
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.context.annotation.Bean;
|
|
import org.springframework.context.annotation.Configuration;
|
|
|
|
import javax.sql.DataSource;
|
|
|
|
@Configuration
|
|
public class DatabaseConfig {
|
|
|
|
@Bean
|
|
public JdbcClient jdbcClient(DataSource dataSource) {
|
|
return JdbcClient.create(dataSource);
|
|
}
|
|
}
|
|
```
|
|
✔ **`JdbcClient.create(dataSource)`** 를 사용하여 인스턴스 생성
|
|
✔ **Spring Boot 환경에서는 자동 설정되므로 따로 등록할 필요 없음**
|
|
|
|
---
|
|
|
|
## **3. CRUD 예제**
|
|
|
|
### **📌 3.1 데이터 삽입 (INSERT)**
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public int insertUser(String name, String email) {
|
|
return jdbcClient.sql("INSERT INTO users (name, email) VALUES (:name, :email)")
|
|
.param("name", name)
|
|
.param("email", email)
|
|
.update();
|
|
}
|
|
}
|
|
```
|
|
✔ `sql("INSERT INTO users ...")` → **SQL 작성**
|
|
✔ `param("name", name)` → **이름 기반 파라미터 바인딩**
|
|
✔ `update()` → **INSERT/UPDATE/DELETE 실행**
|
|
|
|
---
|
|
|
|
### **📌 3.2 단일 데이터 조회 (SELECT - 단건 검색)**
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
import java.util.Optional;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public Optional<User> findById(Long id) {
|
|
return jdbcClient.sql("SELECT * FROM users WHERE id = :id")
|
|
.param("id", id)
|
|
.query(User.class)
|
|
.optional();
|
|
}
|
|
}
|
|
```
|
|
✔ `query(User.class)` → **자동으로 User 객체로 매핑**
|
|
✔ `optional()` → **결과가 없을 경우 Optional.empty() 반환**
|
|
|
|
---
|
|
|
|
### **📌 3.3 여러 데이터 조회 (SELECT - 리스트 검색)**
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
import java.util.List;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public List<User> findAll() {
|
|
return jdbcClient.sql("SELECT * FROM users")
|
|
.query(User.class)
|
|
.list();
|
|
}
|
|
}
|
|
```
|
|
✔ `query(User.class).list()` → **리스트 형태로 결과 반환**
|
|
|
|
---
|
|
|
|
### **📌 3.4 데이터 업데이트 (UPDATE)**
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public int updateEmail(Long id, String email) {
|
|
return jdbcClient.sql("UPDATE users SET email = :email WHERE id = :id")
|
|
.param("email", email)
|
|
.param("id", id)
|
|
.update();
|
|
}
|
|
}
|
|
```
|
|
✔ `update()`를 사용하여 데이터를 수정
|
|
|
|
---
|
|
|
|
### **📌 3.5 데이터 삭제 (DELETE)**
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public int deleteById(Long id) {
|
|
return jdbcClient.sql("DELETE FROM users WHERE id = :id")
|
|
.param("id", id)
|
|
.update();
|
|
}
|
|
}
|
|
```
|
|
✔ `update()`를 사용하여 DELETE 실행
|
|
|
|
---
|
|
|
|
## **4. RowMapper 없이 자동 매핑하기**
|
|
기존 `JdbcTemplate`에서는 **RowMapper를 사용하여 수동으로 매핑**해야 했지만,
|
|
`JdbcClient`는 **자동으로 Java 객체와 매핑**할 수 있습니다.
|
|
|
|
```java
|
|
import org.springframework.jdbc.core.simple.JdbcClient;
|
|
import org.springframework.stereotype.Repository;
|
|
|
|
import java.util.List;
|
|
|
|
@Repository
|
|
public class UserRepository {
|
|
|
|
private final JdbcClient jdbcClient;
|
|
|
|
public UserRepository(JdbcClient jdbcClient) {
|
|
this.jdbcClient = jdbcClient;
|
|
}
|
|
|
|
public List<User> findAll() {
|
|
return jdbcClient.sql("SELECT * FROM users")
|
|
.query(User.class)
|
|
.list();
|
|
}
|
|
}
|
|
```
|
|
✔ **User 객체의 필드명과 DB 컬럼명이 일치하면 자동 매핑**
|
|
|
|
---
|
|
|
|
## **5. 정리**
|
|
| 기능 | JdbcTemplate | JdbcClient |
|
|
|------|-------------|------------|
|
|
| SQL 실행 방식 | `jdbcTemplate.query(sql, rowMapper)` | `jdbcClient.sql(sql).query(clazz)` |
|
|
| 파라미터 바인딩 | `new MapSqlParameterSource()` 사용 | `.param("name", value)` 사용 |
|
|
| 결과 변환 | `RowMapper` 필요 | **자동 매핑 가능** |
|
|
| 코드 스타일 | 절차 지향 | 함수형 스타일 |
|
|
| 가독성 | 비교적 복잡 | **간결하고 직관적** |
|
|
|
|
✔ `JdbcClient`는 **Spring Boot 3 이상에서 사용 가능**
|
|
✔ 기존 `JdbcTemplate` 대비 **더 간결하고 직관적인 코드 작성 가능**
|
|
✔ **자동 매핑 기능 제공**하여 `RowMapper` 작성 불필요
|
|
|
|
Spring Boot 3 이상을 사용한다면, `JdbcTemplate` 대신 **더 강력하고 간결한 `JdbcClient`를 활용하는 것이 추천됩니다!** 🚀
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# **Spring `JdbcClient` 주요 메서드 정리**
|
|
|
|
Spring 6 및 Spring Boot 3에서 도입된 `JdbcClient`는 **기존 `JdbcTemplate`보다 간결한 API**를 제공합니다.
|
|
아래는 `JdbcClient`의 주요 메서드를 **설명과 함께 표로 정리**한 것입니다.
|
|
|
|
---
|
|
|
|
## **1. JdbcClient 주요 메서드 정리**
|
|
|
|
| 메서드 | 설명 | 예제 |
|
|
|--------|------|------|
|
|
| `sql(String sql)` | 실행할 SQL을 설정 | `jdbcClient.sql("SELECT * FROM users")` |
|
|
| `param(String name, Object value)` | **이름 기반** 파라미터 바인딩 | `.param("id", 1L)` |
|
|
| `paramSource(SqlParameterSource params)` | **Map 또는 Bean**을 파라미터로 전달 | `.paramSource(new MapSqlParameterSource(Map.of("id", 1L)))` |
|
|
| `query(Class<T> clazz)` | 결과를 **자동 매핑하여 반환** | `.query(User.class).list()` |
|
|
| `query(RowMapper<T> rowMapper)` | 커스텀 **RowMapper를 사용한 쿼리 실행** | `.query((rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name"))).list()` |
|
|
| `queryForObject(Class<T> clazz)` | 단일 결과 반환 (`Optional<T>` 지원) | `.queryForObject(User.class).optional()` |
|
|
| `list()` | 여러 개의 결과를 리스트로 반환 | `.query(User.class).list()` |
|
|
| `optional()` | 단일 결과를 **Optional**로 반환 | `.queryForObject(User.class).optional()` |
|
|
| `update()` | INSERT, UPDATE, DELETE 실행 | `.sql("UPDATE users SET name = :name WHERE id = :id").update()` |
|
|
| `execute(Function<Connection, T> callback)` | JDBC 커넥션을 직접 다룰 때 사용 | `.execute(conn -> conn.prepareStatement("DELETE FROM users").executeUpdate())` |
|
|
|
|
---
|
|
|
|
## **2. JdbcClient 주요 메서드 예제**
|
|
### **📌 단일 데이터 조회 (`queryForObject`)**
|
|
```java
|
|
Optional<User> user = jdbcClient.sql("SELECT * FROM users WHERE id = :id")
|
|
.param("id", 1L)
|
|
.queryForObject(User.class)
|
|
.optional();
|
|
```
|
|
✔ **SQL 실행 후 `User.class`로 자동 매핑**
|
|
✔ 결과가 없을 경우 `Optional.empty()` 반환
|
|
|
|
---
|
|
|
|
### **📌 여러 데이터 조회 (`query` + `list`)**
|
|
```java
|
|
List<User> users = jdbcClient.sql("SELECT * FROM users")
|
|
.query(User.class)
|
|
.list();
|
|
```
|
|
✔ `User.class` 타입으로 리스트 변환
|
|
|
|
---
|
|
|
|
### **📌 데이터 삽입 (`update`)**
|
|
```java
|
|
int rows = jdbcClient.sql("INSERT INTO users (name, email) VALUES (:name, :email)")
|
|
.param("name", "Alice")
|
|
.param("email", "alice@example.com")
|
|
.update();
|
|
```
|
|
✔ `update()`를 실행하여 INSERT 수행
|
|
✔ 반영된 행 수(`int`) 반환
|
|
|
|
---
|
|
|
|
### **📌 데이터 삭제 (`update`)**
|
|
```java
|
|
int rowsDeleted = jdbcClient.sql("DELETE FROM users WHERE id = :id")
|
|
.param("id", 1L)
|
|
.update();
|
|
```
|
|
✔ 삭제된 행 수 반환
|
|
|
|
---
|
|
|
|
### **📌 수동 매핑 (RowMapper 사용)**
|
|
```java
|
|
List<User> users = jdbcClient.sql("SELECT * FROM users")
|
|
.query((rs, rowNum) -> new User(rs.getLong("id"), rs.getString("name")))
|
|
.list();
|
|
```
|
|
✔ `RowMapper`를 직접 정의하여 결과 매핑 가능
|
|
|
|
---
|
|
|
|
## **3. 정리**
|
|
- `JdbcClient`는 기존 `JdbcTemplate`보다 **간결하고 현대적인 API** 제공
|
|
- **메서드 체이닝 지원**으로 **코드 가독성이 향상됨**
|
|
- `queryForObject()`, `query()`를 활용하면 **자동 매핑 가능**
|
|
- `update()`를 사용하여 **INSERT, UPDATE, DELETE 실행**
|
|
|
|
Spring Boot 3 이상을 사용한다면 **`JdbcTemplate`보다 `JdbcClient`를 적극 활용하는 것이 추천됩니다!** 🚀 |