Skip to content

User Guide (KR)

ydLee edited this page Oct 5, 2025 · 5 revisions

Contents

Spring Data JDBC Reference Documentation

  1. Spring Boot Starter Data JDBC Plus SQL
  2. Spring Boot Starter Data JDBC Plus Repository

1. Spring Boot Starter Data JDBC Plus SQL

Spring Data JDBC λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ, 직접 SQL 을 μž‘μ„±ν•  λ•Œ 도움이 λ˜λŠ” κΈ°λŠ₯듀을 μ œκ³΅ν•©λ‹ˆλ‹€.

1.1. Gradle / Maven Dependency

  • Gradle
dependencies {
    implementation("com.navercorp.spring:spring-boot-starter-data-jdbc-plus-sql:3.3.5")
}
  • Maven
<dependency>
	<groupId>com.navercorp.spring</groupId>
	<artifactId>spring-boot-starter-data-jdbc-plus-sql</artifactId>
	<version>3.3.5</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
        """
    }
}

1.2. JdbcRepositorySupport, JdbcDaoSupport

  • 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 에 λ°œν–‰ν•©λ‹ˆλ‹€.

1.3. JdbcReactiveDaoSupport

  • JdbcDaoSupport λ₯Ό μƒμ†ν•˜κ³ , Reactive(Flux) 쑰회 λ©”μ†Œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.
  • JDBC Query 싀행은 BLOCKING μ΄μ§€λ§Œ, Excel Download 와 같이 Asynchronous Stream 이 효과적일 λ•Œ μ‚¬μš©ν•  수 μžˆλ‹€.
  • CustomRepository ν™•μž₯κ³Ό 같이 μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” spring-data-jdbc-plus-repository 의 reactive-support μ˜΅μ…˜μ΄ ν™œμ„±ν™” λ˜μ•Ό ν•œλ‹€.Reactive Type Support

1.4. EntityRowMapper, AggregateResultSetExtractor

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 으둜 μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.)

1.4.1. EntityRowMapper

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 쿼리가 싀행될 수 μžˆμŠ΅λ‹ˆλ‹€.

1.4.2. AggregateResultSetExtractor

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 컬럼 ν•„λ“œλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.

1.5. SqlGeneratorSupport (SqlAware)

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 에 λŒ€μ‘)

1.6. SqlParameterSource

  • 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 ν•  수 μžˆλ‹€.

1.7. SingleValueSelectTrait

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
        """
    }

1.8. SqlParameterSourceFactory

SqlParameterSource (beanParameterSource, mapParameterSource, entityParameterSource) λ₯Ό μƒμ„±ν•œλ‹€. DefaultSqlParameterSourceFactory (Default) 와 EntityConvertibleSqlParameterSourceFactory κ°€ μ œκ³΅λœλ‹€. SqlParameterSourceFactory 에 λ“±λ‘λœ Converter λ“±μ˜ 섀정은 Spring Data JDBC CrudRepository 와 λ³„λ„λ‘œ μ„€μ •λ©λ‹ˆλ‹€.

1.8.1 DefaultSqlParameterSourceFactory

  • κΈ°λ³Έ JDBC Parameter μ»¨λ²„νŒ… μ „λž΅ μ‚¬μš© (Default μ„€μ •)
  • μƒμ„±ν•œ ParameterSource λŠ” JdbcDriver 의 Type Converting μ „λž΅μ— μ˜μ‘΄ν•œλ‹€.

1.8.2 EntityConvertibleSqlParameterSourceFactory

  • λͺ‡κ°€μ§€ νƒ€μž…μ— λŒ€ν•œ 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;
    }
}

1.8.3 ConvertibleParameterSourceFactory

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 μ—λŠ” μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

1.8.4 JdbcParameterSourceConverter (DefaultJdbcParameterSourceConverter)

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 으둜 λ³€ν™˜ν•œλ‹€.
  • Unwrapper

    • AggregateReference 와 같이 Wrapping 된 값을 Unwrapping ν•  수 μžˆλŠ” Unwrapper λ₯Ό 등둝할 수 μžˆλ‹€.
    • Unwrapper κ°€ 적용되면, Unwrapping ν•œ κ²°κ³Όλ₯Ό Converter 둜 ν•œλ²ˆ 더 λ³€ν™˜ λ™μž‘ν•  수 μžˆλ‹€.

Converter 와 Unwrapper λŠ” μ •ν™•ν•œ νƒ€μž…μ—λ§Œ λ§€μΉ­λ˜μ„œ μ μš©λœλ‹€. λ§€μΉ­ 쑰건을 직접 μž‘μ„±ν•  ν•„μš”κ°€ μžˆλ‹€λ©΄, ConditionalConverter 와 ConditionalUnwrapper λ₯Ό 등둝할 수 μžˆλ‹€. matches λ©”μ†Œλ“œλ₯Ό κ΅¬ν˜„ν•˜λ©΄ 쑰건에 λ§žλŠ” Converter 와 Unwrapper κ°€ μ„ νƒλœλ‹€. Spring Data JDBC CrudRepository μ—λŠ” μ μš©λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

1.9 SqlTableAlias

@Value
@Builder
@Table("post")
public class PostDto {
    @Id
    Long id;

    @Column
    Post post;

    @SqlTableAlias("p_labels")
    @MappedCollection(idColumn = "board_id")
    Set<Label> labels;
}
// In custom SQL

val sql = """
SELECT ${sql.aggregateColumns(PostDto::class.java)}
FROM n_post AS post
LEFT OUTER JOIN n_label AS p_labels
ON post.board_id = p_labels.board_id
"""

@SqlTableAlias λŠ” SQL μ—μ„œ table 의 AS 이름을 μ§€μ •ν•  수 μžˆλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€. Custom SQL Statement λ₯Ό μž‘μ„±ν•  λ•Œ μœ μš©ν•˜κ²Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€. Class, Field, Method 에 적용될 수 μžˆμŠ΅λ‹ˆλ‹€.

1.10 @SqlFunction

@SqlFunction 은 field λ˜λŠ” method 에 μ„€μ •ν•  수 μžˆλŠ” annotation μž…λ‹ˆλ‹€. νŠΉμ • column 을 SQL function 으둜 λ§€ν•‘ν•˜κ³  싢을 λ•Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

@Table("n_order")
@Getter
@Builder
public class Order {
	@Id
	private Long id;

	@SqlFunction(expressions = {SqlFunction.COLUMN_NAME, "0"})
	private Long price;

	private OrderStatus status;

	private String purchaserNo;

	public void complete() {
		this.status = OrderStatus.COMPLETED;
	}
}

이 Entity λŠ” columns 둜 μ•„λž˜μ˜ SQL 을 기본적으둜 μƒμ„±ν•˜κ²Œλ©λ‹ˆλ‹€.

`n_order`.`id` AS `id`,
COALESCE(`n_order`.`price`, 0) AS `price`,
`n_order`.`status` AS `status`,
`n_order`.`purchaser_no` AS `purchaser_no`

null default λ₯Ό SQL level μ—μ„œ μ„€μ •ν•˜κ³  싢을 λ•Œ μœ μš©ν•©λ‹ˆλ‹€

2. Spring Boot Starter Data JDBC Plus Repository

  • Spring Data JDBC 의 CrudRepository 에 ν™•μž₯ 및 μΆ”κ°€ κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

2.1. JdbcRepository

  • 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:3.3.5")
}
  • Maven
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>com.navercorp.spring:spring-boot-starter-data-jdbc-plus-repository</artifactId>
	<version>3.3.5</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);
    }
}

2.2. Reactive Type Support

  • 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);
}

2.3. @SoftDeleteColumn

@Value
@Builder
@Table("article")
static class SoftDeleteArticle {
	@Id
	Long id;

	String contents;

	@SoftDeleteColumn.Boolean(valueAsDeleted = "true")
	boolean deleted;
}

@SoftDeleteColumn 은 Soft Delete κΈ°λŠ₯을 μœ„ν•΄ λ§Œλ“€μ–΄μ‘ŒμŠ΅λ‹ˆλ‹€ spring-data-jdbc 의 default DELETE κΈ°λŠ₯을 override ν•˜μ—¬ ν•΄λ‹Ή ν•„λ“œμ— λŒ€ν•œ update λ₯Ό μ‹€ν–‰ν•˜κ²Œ λ©λ‹ˆλ‹€.

boolean, String type 을 μ§€μ›ν•˜κ³  있으며 각각 @SoftDeleteColumn.Boolean μ΄λ‚˜ @SoftDeleteColumn.String 을 μ‚¬μš©ν•˜μ—¬ μ„ΈνŒ…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Clone this wiki locally