-
Notifications
You must be signed in to change notification settings - Fork 35
User Guide
Spring Data JDBC Reference Documentation
-
Spring Boot Starter Data JDBC Plus SQL
- 1.1. Gradle / Maven Dependency
- 1.2. JdbcRepositorySupport, JdbcDaoSupport
- 1.3. JdbcReactiveDaoSupport
- 1.4. EntityRowMapper, AggregateResultSetExtractor
- 1.4.1. EntityRowMapper
- 1.4.2. AggregateResultSetExtractor
- 1.5. SqlGeneratorSupport (SqlAware)
- 1.6. SqlParameterSource
- 1.7. SingleValueSelectTrait
- 1.8. SqlParameterSourceFactory
-
Spring Boot Starter Data JDBC Plus Repository
- 2.1. JdbcRepository
- 2.2. Reactive Type Support
Spring Data JDBC
를 사용하면서, 직접 SQL 을 작성할 때 도움이 되는 기능들을 제공합니다.
- Gradle
dependencies {
implementation("com.navercorp.spring:spring-boot-starter-data-jdbc-plus-sql:2.3.4")
}
- Maven
<dependency>
<groupId>com.navercorp.spring</groupId>
<artifactId>spring-boot-starter-data-jdbc-plus-sql</artifactId>
<version>2.3.4</version>
</dependency>
- Java Codes
@Table("n_order")
@Data
public class Order {
@Id
@Column("order_no")
private Long orderNo;
@Column("price")
private long price;
@Column("purchaser_no")
private String purchaserNo;
}
public interface OrderRepository extends CrudRepository<Order, Long>, OrderRepositoryCustom {
}
public interface OrderRepositoryCustom {
List<Order> findByPurchaserNo(String purchaserNo);
}
public class OrderRepositoryImpl extends JdbcRepositorySupport<Order> implements OrderRepositoryCustom {
private final OrderSql sqls;
public OrderRepositoryImpl(EntityJdbcProvider entityJdbcProvider) {
super(Order.class, entityJdbcProvider);
this.sql = super.sqls(OrderSql::new);
}
@Override
public List<Order> findByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return find(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo));
}
}
- Groovy codes for SQL
implementation("org.codehaus.groovy:groovy:${groovyVersion}")
class OrderSql extends SqlGeneratorSupport {
String selectByPurchaserNo() {
"""
SELECT ${sql.columns(Order)}
FROM n_order
WHERE purchaser_no = :purchaserNo
"""
}
}
- Customizing Individual Repositories 로 SQL 실행 코드를 직접 작성할 때 필요한 코드 및 지원 메소드를 제공한다.
- 내부적으로
JdbcOperations(NamedParameterJdbcTemplate)
을 사용합니다.
# JdbcRepositorySupport
public class OrderRepositoryImpl extends JdbcRepositorySupport<Order> implements OrderRepositoryCustom {
private final OrderSql sqls;
public OrderRepositoryImpl(EntityJdbcProvider entityJdbcProvider) {
super(Order.class, entityJdbcProvider);
this.sql = super.sqls(OrderSql::new);
}
@Override
public List<Order> findByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return find(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo));
}
}
# JdbcDaoSupport
public class OrderDaoImpl extends JdbcDaoSupport implements OrderRepositoryCustom {
private final OrderSql sqls;
public OrderDaoImpl(EntityJdbcProvider entityJdbcProvider) {
this.sql = super.sqls(OrderSql::new);
}
@Override
public List<OrderDto> selectByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return select(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo),
OrderDto.class);
}
}
Repository 와 Dao 를 개념적으로 분리해서 사용하도록 각각 제공합니다. JdbcRepositorySupport 와 JdbcDaoSupport 가 제공하는 기능은 유사하지만, 구현 관점에서 차이가 있습니다.
(1) 조회 실행 메소드 명
- JdbcRepositorySupport 는
find
로 실행합니다. - JdbcDaoSupport 는
select
로 실행합니다. - 기능적인 차이는 없습니다.
(2) 반환 타입
- JdbcRepositorySupport 는 Repository 에 대응되는 AggregateRoot(Entity) 타입을 기본 타입으로 사용합니다.
- 생성자에서 AggregateRoot 타입을 설정합니다.
- JdbcDaoSupport 는 다양한 QueryModel 을 반환할 수 있으므로, 기본 타입을 지정하지 않고 사용할 때 타입을 결정합니다.
# JdbcRepositorySupport
public List<Order> findByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return find(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo)); // 생성자에서 설정한 Order 타입을 기본으로 사용한다.
}
# JdbcDaoSupport
public List<OrderDto> selectByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return select(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo),
OrderDto.class); // Query 실행시 반환 타입을 전달한다.
}
- JdbcRepositorySupport 를 사용하더라도, 다른 반환 타입을 전달할 수 있습니다.
(3) 조회 결과 매핑
- JdbcRepositorySupport 는
AggregateResultSetExtractor
를 사용해서 조회 결과를 매핑합니다.-
AggregateResultSetExtractor
는1 : N
조회 결과를 반환 타입(Aggregate) 결과에 매핑합니다.
-
- JdbcDaoSupport 는
EntityRowMapper
를 사용해서 조회 결과를 매핑합니다.-
EntityRowMapper
는 Row 단위 결과를 매핑합니다.
-
2가지 매핑 방식에 따라 작성해야 되는 SQL 에도 차이가 발생합니다.
JdbcRepositorySupport, JdbcDaoSupport 를 사용하더라도 다른 방식의 결과 매핑을 사용할 수 있습니다.
# JdbcRepositorySupport
public List<Order> findByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return find(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo),
this.getRowMapper()); // 조회 결과를 EntityRowMapper 로 매핑합니다.
}
# JdbcDaoSupport
public List<OrderDto> selectByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return select(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo),
this.getAggregateResultSetExtractor(OrderDto.class)); // 조회 결과를 AggregateResultSetExtractor 로 매핑합니다.
}
(4) 조회 결과를 AfterLoadEvent
, AfterLoadCallback
로 발행 4.8. Entity Callbacks
-
JdbcRepositorySupport
는 조회 결과 Event 및 Callback 을ApplicationEventPublisher
,EntityCallbacks
에 발행합니다.
-
JdbcDaoSupport
를 상속하고, Reactive(Flux) 조회 메소드를 제공한다. - JDBC Query 실행은 BLOCKING 이지만, Excel Download 와 같이
Asynchronous Stream
이 효과적일 때 사용할 수 있다. -
CustomRepository
확장과 같이 사용하기 위해서는spring-data-jdbc-plus-repository
의reactive-support
옵션이 활성화 되야 한다.Reactive Type Support
AggregateResultSetExtractor 와 EntityRowMapper 는 조회 결과 매핑 방식에 차이가 있으며, 연관관계가 존재할 경우 SQL 의 JOIN 구문도 이에 맞춰서 작성해야 합니다.
- 1:1, 1:N 연관관계 엔티티
@Table("n_board")
public class Board {
@Id
private Long id;
private String name;
@Column("board_id")
private Audit audit; // 1:1 관계 (FK Column `n_audit.board_id`)
@MappedCollection(idColumn = "board_id", keyColumn = "board_index")
private List<Post> posts = new ArrayList<>(); // 1:N 관계 (FK Column `n_post.board_id`, Order By Column `n_post.board_index`)
}
@Table("n_audit")
public class Audit {
@Id
private Long id;
private String name;
}
@Table("n_post")
public class Post {
@Id
private Long id;
private String title;
private String content;
}
- 1:1 관계(n_board -> b_audit)는 JOIN 하더라도, 기준 테이블 "n_board" 의 ROW 결과가 중복되지 않습니다.
- 1:N 관계(n_board -> n_post)는 JOIN 했을 때, n_post 의 연결 갯수만큼 기준 테이블 "n_board" 의 ROW 결과가 중복됩니다.
(연관관계 데이터가 없을 수도 있다면,
LEFT OUTER JOIN
으로 작성해야 합니다.)
Spring Data JDBC 의 CrudRepository 에서 사용하는 RowMapper 입니다.
-
EntityRowMapper 는 SQL 조회 결과를 ROW 단위로 1:1 연관관계까지 매핑합니다.
-
1:N 연관관계는 추가 SQL 을 실행(EAGER Fetch)해서 결과를 매핑합니다.
-
SQL 을 직접 작성할 때, 1:1 연관관계까지
SELECT
구문과FROM
구문(JOIN 포함)을 작성해줘야 합니다. -
실행 SQL 작성
SELECT n_board.id AS id, n_board.name AS name, audit.id AS audit_id, audit.name AS audit_name
FROM n_board
LEFT OUTER JOIN n_audit AS audit
WHERE id = :id
-
n_post
과의 1:N 연관관계는 추가 SQL 이 자동으로 실행되면서 결과가 매핑됩니다.
SELECT n_post.id AS id, n_post.title AS title, n_post.content AS content
FROM n_post
WHERE n_post.board_id = :board_id
ORDER BY board_index
- Spring Data JDBC CrudRepository 동작과 동일하며,
N+1
쿼리가 실행될 수 있습니다.
SELECT
조회 결과 중 1:N 관계 결과까지 Grouping 해서 결과 객체에 매핑합니다.
-
@EntityGraph
와 비슷하게 EAGER Fetch 없이 한번에 조회한 결과를 매핑합니다. -
1:N
LEFT OUTER JOIN 결과를1:N
결과 객체에 Grouping 해서 매핑합니다.
Board | Audit | Post |
---|---|---|
Board 1 | Audit 1 | Post 1 |
Board 1 | Audit 1 | Post 2 |
Board 1 | Audit 1 | Post 3 |
Board 2 | Audit 2 | Post 4 |
Board 2 | Audit 2 | Post 5 |
# 매핑 결과
1. Board 1 / Audit 1 / Post 1, Post 2
2. Board 2 / Audit 2 / Post 4, Post 5
-
AggregateResultSetExtractor
를 사용하기 위해서 Grouping Entity 는@Id
컬럼 필드는 필수입니다.
SQL 작성을 지원하는 SqlProvider
를 주입 받아서 제공합니다.
SqlProvider
는 columns
, tables
, aggregateColumns
, aggregateTables
메소드를 제공한다.
- JdbcRepositorySupport, JdbcDaoSupport 의
sqls
메소드를 사용하여,SqlProvider
를 주입 받을 수 있습니다.
public class BoardRepositoryImpl extends JdbcRepositorySupport<Board> implements BoardRepositoryCustom {
private final BoardSql sqls;
public BoardRepositoryImpl(EntityJdbcProvider entityJdbcProvider) {
super(Board.class, entityJdbcProvider);
this.sql = super.sqls(BoardSql::new); // BoardSql 생성 및 SqlProvider 객체 주입
}
}
SQL 작성 클래스는 Groovy 나 Kotlin 과 같이 MultiLine String 을 지원하는 언어의 도움을 받을 수 있다.
Java 13 JEP 355: Text Blocks(Preview), Java 14 JEP 368: Text Blocks(Second Preview) 를 활용할 수도 있다.
# Groovy
class BoardSql extends SqlGeneratorSupport {
/**
* SELECT n_board.id AS id, n_board.name AS name, audit.id AS audit_id, audit.name AS audit_name
* FROM n_board
* LEFT OUTER JOIN n_audit AS audit
* WHERE name = :name
*/
String selectByName() {
"""
SELECT ${sql.columns(Board)}
FROM ${sql.tables(Board)}
WHERE name = :name
"""
}
/**
* SELECT n_board.id AS id, n_board.name AS name, audit.id AS audit_id, audit.name AS audit_name, post.id AS post_id, post.title AS post_title, post.content AS post_content
* FROM n_board
* LEFT OUTER JOIN n_audit AS audit
* LEFT OUTER JOIN n_post AS post
* WHERE name = :name
*/
String selectAggregateByName() {
"""
SELECT ${sql.aggregateColumns(Board)}
FROM ${sql.aggregateTables(Board)}
WHERE name = :name
"""
}
}
-
${sql.columns(Board)}
:1:1
연관관계에 해당하는 SELECT Column 구문을 출력한다. (EntityRowMapper 에 대응) -
${sql.tables(Board)}
:1:1
연관관계에 해당하는 FROM JOIN 구문을 출력한다. (EntityRowMapper 에 대응) -
${sql.aggregateColumns(Board)}
:1:N
연관관계를 포함한 SELECT Column 구문을 출력한다. (AggregateResultSetExtractor 에 대응) -
${sql.aggregateTables(Board)}
:1:N
연관관계를 포함한 FROM JOIN 구문을 출력한다. (AggregateResultSetExtractor 에 대응)
-
JdbcOperations
에서 SQL 파라미터를 바인딩하깅 위해 SqlParameterSource 를 제공해야 합니다. - SqlParameterSource 는
beanParameterSource
,mapParameterSource
,entityParameterSource
,compositeSqlParameterSource
를 제공합니다.
# JdbcRepositorySupport
public List<Order> find(OrderCriteria criteria) {
String sql = this.sql.select();
return find(sql, beanParameterSource(criteria)); // beanParameterSource
}
public List<Order> findByPurchaserNo(String purchaserNo) {
String sql = this.sql.selectByPurchaserNo();
return find(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo)); // mapParameterSource
}
public List<Order> findByExample(Order order) {
String sql = this.sql.select();
return find(sql, entityParameterSource(order)); // entityParameterSource
}
public List<Order> findByPurchaserNo(String purchaserNo, OrderCriteria criteria) {
String sql = this.sql.selectExample;
return find(sql, compositeSqlParameterSource(
mapParameterSource().addValue("purchaserNo", purchaserNo),
beanParameterSource(criteria)
); // compositeSqlParameterSource
}
- beanParameterSource: parameter 객체의 getter 를 사용해서 binding 할 수 있다.
- mapParameterSource: map 에 key/value 를 파라미터로 전달해서 binding 할 수 있다.
- entityParameterSource: Spring Data JDBC 의 매핑 정보를 사용해서 파라미터 binding 할 수 있다. (@Column)
- compositeSqlParameterSource: 복합 SqlParameterSource 를 조합한 파라미터로 전달해서 binding 할 수 있다.
count
결과와 같이 단일 컬럼 결과를 매핑하는 JdbcOperations
호출할 때 SingleValueSelectTrait
을 사용할 수 있습니다.
public class OrderRepositoryImpl extends JdbcRepositorySupport<Order>
implements OrderRepositoryCustom, SingleValueSelectTrait {
private final OrderSql sqls;
public OrderRepositoryImpl(EntityJdbcProvider entityJdbcProvider) {
super(Order.class, entityJdbcProvider);
this.sql = super.sqls(OrderSql::new);
}
@Override
public Long countByPurchaserNo(String purchaserNo) {
String sql = this.sql.countByPurchaserNo();
return selectSingleValue(sql, mapParameterSource()
.addValue("purchaserNo", purchaserNo),
Long.class);
}
}
class OrderSql extends SqlGeneratorSupport {
String countByPurchaserNo() {
"""
SELECT count(id)
FROM n_order
WHERE purchaser_no = :purchaserNo
"""
}
SqlParameterSource (beanParameterSource
, mapParameterSource
, entityParameterSource
) 를 생성한다.
DefaultSqlParameterSourceFactory
(Default) 와 EntityConvertibleSqlParameterSourceFactory
가 제공된다.
SqlParameterSourceFactory 에 등록된 Converter 등의 설정은 Spring Data JDBC CrudRepository 와 별도로 설정됩니다.
- 기본 JDBC Parameter 컨버팅 전략 사용 (Default 설정)
- 생성한 ParameterSource 는 JdbcDriver 의 Type Converting 전략에 의존한다.
- 몇가지 타입에 대한 ParameterSource Type Converter 등록 지원
@Configuration
public class JdbcConfig extends JdbcPlusSqlConfiguration {
@Bean
@Override
public SqlParameterSourceFactory sqlParameterSourceFactory(
JdbcMappingContext jdbcMappingContext, JdbcConverter jdbcConverter, Dialect dialect) {
return new EntityConvertibleSqlParameterSourceFactory(
this.parameterSourceConverter(),
jdbcMappingContext,
jdbcConverter,
dialect.getIdentifierProcessing());
}
private ConvertibleParameterSourceFactory parameterSourceConverter() {
JdbcParameterSourceConverter converter = new DefaultJdbcParameterSourceConverter();
ConvertibleParameterSourceFactory parameterSourceFactory = new ConvertibleParameterSourceFactory(converter, null);
parameterSourceFactory.setPaddingIterableParam(true);
return parameterSourceFactory;
}
}
JdbcParameterSourceConverter
, FallbackParameterSource
, PaddingIterable
설정을 적용한 SqlParameterSource 를 생성한다.
- JdbcParameterSourceConverter: ParameterSource Converting 에 적용할 Converter 를 등록한다.
- FallbackParameterSource: SQL Binding 에 필요한 Parameter 가 ParameterSource 에 존재하지 않을 때 처리할 전략을 주입한다.
- PaddingIterable: Iterable(List, Set, Collection) 파라미터 바인딩시 SQL Parsing 비용을 줄이기 위해 바인딩 파라미터 갯수를 균일하게 조정한다. 특히
WHERE IN
조건에 주로 사용된다.-
parameterSourceFactory.setPaddingIterableParam(true);
로 padding 설정을 활성화 할 수 있다. -
this.setPaddingIterableBoundaries(...);
로 padding scope 패턴을 지정할 수 있다.
default boundaries:
new int[]{0, 1, 2, 3, 4, 8, 16, 32, 50, 100, 200, 300, 500, 1000, 1500, 2000}
-
SELECT *
FROM n_order
WHERE id in (:ids)
mapParameterSource()
.add("ids", Arrays.asList("1", "2", "3", "4", "5", "6"));
-->
SELECT *
FROM n_order
WEHERE id IN (?, ?, ?, ?, ?, ?, ?, ?)
-->
SELECT *
FROM n_order
WEHERE id IN ("1", "2", "3", "4", "5", "6", "6", "6")
Spring Data JDBC CrudRepository 에는 적용되지 않습니다.
Default Converter
가 내장되어 있으며, 추가 타입 Converter, Unwrapper 를 등록할 수 있다.
-
Default Converter
- InstantParameterTypeConverter:
Instant
타입을Date
로 변환한다. - LocalDateTimeParameterTypeConverter:
LocalDateTime
타입을Date
로 변환한다. - LocalDateParameterTypeConverter:
LocalDate
타입을Date
로 변환한다. - ZonedDateTimeParameterTypeConverter:
ZonedDateTime
타입을Date
로 변환한다. - UuidToStringTypeConverter:
UUID
타입을String
으로 변환한다. (VARCHAR(36)) - EnumToNameConverter:
ENUM
타입을name
으로 변환한다.
- InstantParameterTypeConverter:
-
Unwrapper
-
AggregateReference
와 같이 Wrapping 된 값을 Unwrapping 할 수 있는 Unwrapper 를 등록할 수 있다. - Unwrapper 가 적용되면, Unwrapping 한 결과를 Converter 로 한번 더 변환 동작할 수 있다.
-
Converter 와 Unwrapper 는 정확한 타입에만 매칭되서 적용된다.
매칭 조건을 직접 작성할 필요가 있다면, ConditionalConverter 와 ConditionalUnwrapper 를 등록할 수 있다.
matches
메소드를 구현하면 조건에 맞는 Converter 와 Unwrapper 가 선택된다.
Spring Data JDBC CrudRepository 에는 적용되지 않습니다.
- Spring Data JDBC 의 CrudRepository 에 확장 및 추가 기능을 제공합니다.
- Spring Data JDBC 의 CrudRepository 는 save 메소드를 제공 합니다. (merge)
-
@Id
생성 전략에 따라 insert 를 직접 호출할 필요가 있습니다. - insert / update 를 직접 호출할 때
JdbcRepository
를 상속해서 기능을 제공할 수 있습니다. - Spring Data JDBC 에서는 insert / update 메소드를 직접 제공할 계획이 없습니다. DATAJDBC-282
-
spring-data-jdbc-plus-repository
dependency 를 가지면, Entity 에@Table
을 선언해야 합니다.
- Gradle
dependencies {
implementation("com.navercorp.spring:spring-boot-starter-data-jdbc-plus-repository:2.3.4")
}
- Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>com.navercorp.spring:spring-boot-starter-data-jdbc-plus-repository</artifactId>
<version>2.3.4</version>
</dependency>
- Java Codes
@Table("n_order")
@Data
public class Order {
@Id
@Column("order_no")
private Long orderNo;
@Column("price")
private long price;
@Column("purchaser_no")
private String purchaserNo;
}
public interface OrderRepository extends JdbcRepository<Order, Long> {
}
@Service
public class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repository) {
this.repository = repository;
}
public Order save(Order order) {
return this.repository.save(order);
}
// JdbcRepository 추가된 insert / update 메소드를 직접 사용
public Order insert(Order order) {
return this.repository.insert(order);
}
public Order update(Order order) {
return this.repository.update(order);
}
}
- Spring Data JDBC 에서는
CrudRepository
확장 메소드 반환 타입으로 Reactive(Flux
,Mono
) 타입을 허용하지 않는다. - 간단한 설정으로 Reactive(
Flux
,Mono
) 타입을 반환타입으로 가지는 확장 메소드를 선언할 수 있도록 지원한다.
spring:
data:
jdbc:
plus:
repositories:
reactive-support: true
public interface OrderRepository extends CrudRepository<Order, Long>, OrderRepositoryCustom {
}
public interface OrderRepositoryCustom {
Flux<Order> findOrders(String purchaserId);
}
Spring Data JDBC Reference Documentation
1.1. Gradle / Maven Dependency
1.2. JdbcRepositorySupport, JdbcDaoSupport
1.4. EntityRowMapper, AggregateResultSetExtractor
1.4.1. EntityRowMapper
1.4.2. AggregateResultSetExtractor
1.5. SqlGeneratorSupport (SqlAware)
1.6. SqlParameterSource
1.8. SqlParameterSourceFactory
1.8.1. DefaultSqlParameterSourceFactory
1.8.2. EntityConvertibleSqlParameterSourceFactory
1.8.3. ConvertibleParameterSourceFactory
1.8.4. JdbcParameterSourceConverter (DefaultJdbcParameterSourceConverter)
2.1. JdbcRepository