본문 바로가기
공식문서

[Spring Docs] Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling #2

by sangyunpark99 2025. 3. 30.

 

이번글은 공식 문서에서 소개하는 Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling에 대해 정리했습니다.

 

Using NamedParameterJdbcTemplate

The NamedParameterJdbcTemplate class adds support for programming JDBC statements by using named parameters, as opposed to programming JDBC statements using only classic placeholder ( '?') arguments. The NamedParameterJdbcTemplate class wraps a JdbcTemplate and delegates to the wrapped JdbcTemplate to do much of its work. This section describes only those areas of the NamedParameterJdbcTemplate class that differ from the JdbcTemplate itself — namely, programming JDBC statements by using named parameters. The following example shows how to use NamedParameterJdbcTemplate:

 

NamedParameterJdbcTemplate 클래스는 기존의 물음표('?')만을 사용하는 방식 대신, 이름이 지정된 매개변수를 사용하여 JDBC 문을 작성할 수 있도록 지원을 추가합니다.

NamedParameterJdbcTemplate 클래스는 JdbcTemplate을 감싸고 있으며, 많은 작업을 이 감싼 JdbcTemplate에게 위임합니다.

이 섹션에서는 NamedParameterJdbcTemplate 클래스가 JdbcTemplate 자체와 다른 부분, 즉 이름이 지정된 매개변수를 사용하여 JDBC 문을 작성하는 방법만을 설명합니다.

다음 예시는 NamedParameterJdbcTemplate를 사용하는 방법을 보여줍니다.

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
	this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {
	String sql = "select count(*) from t_actor where first_name = :first_name";
	SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
	return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

 

Notice the use of the named parameter notation in the value assigned to the sql variable and the corresponding value that is plugged into the namedParameters variable (of type MapSqlParameterSource).

Alternatively, you can pass along named parameters and their corresponding values to a NamedParameterJdbcTemplate instance by using the Map-based style. The remaining methods exposed by the NamedParameterJdbcOperations and implemented by the NamedParameterJdbcTemplate class follow a similar pattern and are not covered here.

The following example shows the use of the Map-based style:

 

sql 변수에 할당된 값에서 이름이 지정된 매개변수 표기법이 사용된 점과, namedParameters 변수(타입은 MapSqlParameterSource)에 삽입된 해당 이름의 매개변수 값을 주목하세요.

 

또는, Map 기반 스타일을 사용하여 이름이 지정된 매개변수와 그에 해당하는 값을 NamedParameterJdbcTemplate 인스턴스에 전달할 수도 있습니다.

NamedParameterJdbcOperations이 노출하고, NamedParameterJdbcTemplate 클래스가 구현한 나머지 메서드들은 유사한 패턴을 따르며, 이 문서에서는 다루지 않습니다.

 

다음 예시는 Map 기반 스타일의 사용 예를 보여줍니다.

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
	this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {
	String sql = "select count(*) from t_actor where first_name = :first_name";
	Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
	return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

 

One nice feature related to the NamedParameterJdbcTemplate (and existing in the same Java package) is the SqlParameterSource interface. You have already seen an example of an implementation of this interface in one of the previous code snippets (the MapSqlParameterSource class). An SqlParameterSource is a source of named parameter values to a NamedParameterJdbcTemplate. The MapSqlParameterSource class is a simple implementation that is an adapter around a java.util.Map, where the keys are the parameter names and the values are the parameter values.

Another SqlParameterSource implementation is the BeanPropertySqlParameterSource class. This class wraps an arbitrary JavaBean (that is, an instance of a class that adheres to the JavaBean conventions) and uses the properties of the wrapped JavaBean as the source of named parameter values.

The following example shows a typical JavaBean:

 

NamedParameterJdbcTemplate과 관련된 멋진 기능 중 하나(그리고 동일한 Java 패키지에 존재하는)는 SqlParameterSource 인터페이스입니다.

이 인터페이스의 구현체 예시는 앞의 코드 조각 중 하나(즉, MapSqlParameterSource 클래스)에서 이미 본 적이 있습니다.

SqlParameterSourceNamedParameterJdbcTemplate에 이름이 지정된 매개변수 값을 제공하는 원천(source)입니다.

MapSqlParameterSource 클래스는 간단한 구현체로, java.util.Map을 감싸는 어댑터이며, 그 Map에서 키는 매개변수 이름, 값은 매개변수 값을 나타냅니다.

 

또 다른 SqlParameterSource 구현체는 BeanPropertySqlParameterSource 클래스입니다.

이 클래스는 임의의 JavaBean(즉, JavaBean 규칙을 따르는 클래스의 인스턴스)을 감싸고, 그 JavaBean의 속성(properties)을 이름이 지정된 매개변수 값의 원천으로 사용합니다.

이 설명은 NamedParameterJdbcTemplate을 사용할 때, SQL에 값을 주입하는 다양한 방식 중 SqlParameterSource를 어떻게 활용할 수 있는지를 소개하는 내용입니다.

특히 Map 대신 JavaBean으로 넘길 수 있는 구조(BeanPropertySqlParameterSource)를 통해 더 깔끔하고 안전하게 쿼리를 작성할 수 있다는 게 핵심입니다.

 

다음 예시는 전형적인 JavaBean을 보여줍니다.

public class Actor {

	private Long id;
	private String firstName;
	private String lastName;

	public String getFirstName() {
		return this.firstName;
	}

	public String getLastName() {
		return this.lastName;
	}

	public Long getId() {
		return this.id;
	}

	// setters omitted...
}

 

The following example uses a NamedParameterJdbcTemplate to return the count of the members of the class shown in the preceding example

 

다음 예제는 앞서 나온 예제에 나오는 클래스의 구성원 수(count)를 반환하기 위해 NamedParameterJdbcTemplate을 사용하는 예제입니다.

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
	this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActors(Actor exampleActor) {
	// notice how the named parameters match the properties of the above 'Actor' class
	String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName";
	SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
	return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

 

Remember that the NamedParameterJdbcTemplate class wraps a classic JdbcTemplate template. If you need access to the wrapped JdbcTemplate instance to access functionality that is present only in the JdbcTemplate class, you can use the getJdbcOperations() method to access the wrapped JdbcTemplate through the JdbcOperations interface.

See also JdbcTemplate Best Practices for guidelines on using the NamedParameterJdbcTemplate class in the context of an application.

NamedParameterJdbcTemplate 클래스가 기존의 JdbcTemplate 템플릿을 감싸고 있다는 점을 기억해야 합니다.

만약 JdbcTemplate 클래스에만 존재하는 기능에 접근하기 위해 감싸진(wrapped) JdbcTemplate 인스턴스에 접근할 필요가 있다면,

getJdbcOperations() 메서드를 사용하여 JdbcOperations 인터페이스를 통해 감싸진 JdbcTemplate에 접근할 수 있습니다.

 

애플리케이션의 문맥에서 NamedParameterJdbcTemplate 클래스를 사용할 때의 가이드라인은 JdbcTemplate 모범 사례(JdbcTemplate Best Practices) 도 함께 참조하세요.

 


Unified JDBC Query/Update Operations: JdbcClient

As of 6.1, the named parameter statements of NamedParameterJdbcTemplate and the positional parameter statements of a regular JdbcTemplate are available through a unified client API with a fluent interaction model.

6.1 버전부터는, NamedParameterJdbcTemplate의 이름이 지정된(named) 파라미터 문(statement)과 일반 JdbcTemplate의 위치 기반(positional) 파라미터 문(statement)이 유창한(fluent) 상호작용 모델을 갖춘 통합 클라이언트 API를 통해 제공됩니다.

For example, with positional parameters:

예를 들어, 위치 기반 파라미터를 사용하는 경우는 다음과 같습니다:

private JdbcClient jdbcClient = JdbcClient.create(dataSource);

public int countOfActorsByFirstName(String firstName) {
	return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?")
			.param(firstName)
			.query(Integer.class).single();
}

 

 

For example, with named parameters:

예를 들어, 이름이 지정된(named) 파라미터를 사용하는 경우는 다음과 같습니다.

private JdbcClient jdbcClient = JdbcClient.create(dataSource);

public int countOfActorsByFirstName(String firstName) {
	return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName")
			.param("firstName", firstName)
			.query(Integer.class).single();
}

 

RowMapper capabilities are available as well, with flexible result resolution:

유연한 결과 매핑 기능과 함께 RowMapper 기능도 사용할 수 있습니다.

List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
		.query((rs, rowNum) -> new Actor(rs.getString("first_name"), rs.getString("last_name")))
		.list();

 

Instead of a custom RowMapper, you may also specify a class to map to. For example, assuming that Actor has firstName and lastName properties as a record class, a custom constructor, bean properties, or plain fields:

사용자 정의 RowMapper 대신, 매핑할 클래스를 지정할 수도 있습니다.

예를 들어, Actor 클래스가 firstNamelastName 속성을 갖는 레코드 클래스, 사용자 정의 생성자, 빈 프로퍼티, 또는 단순 필드로 구성되어 있다고 가정합니다:

List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor")
		.query(Actor.class)
		.list();

 

With a required single object result:

필수 단일 객체 결과가 필요한 경우:

Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.single();

 

With a java.util.Optional result:

Optional을 사용한 결과가 필요한 경우:

Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?")
		.param(1212L)
		.query(Actor.class)
		.optional();

 

And for an update statement:

그리고 업데이트 문에 대해서는:

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)")
		.param("Leonor").param("Watling")
		.update();

 

Or an update statement with named parameters:

또는 이름이 지정된 매개변수를 사용하는 업데이트 문:

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
		.param("firstName", "Leonor").param("lastName", "Watling")
		.update();

 

Instead of individual named parameters, you may also specify a parameter source object – for example, a record class, a class with bean properties, or a plain field holder which provides firstName and lastName properties, such as the Actor class from above:

개별적으로 이름이 지정된 매개변수를 사용하는 대신, 매개변수 소스 객체를 지정할 수도 있다 — 예를 들어, 위의 Actor 클래스처럼 firstName과 lastName 속성을 제공하는 레코드 클래스, 빈 프로퍼티를 가진 클래스, 또는 단순 필드 보유 객체 등이 있습니다.

this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)")
		.paramSource(new Actor("Leonor", "Watling")
		.update();

 

The automatic Actor class mapping for parameters as well as the query results above is provided through implicit SimplePropertySqlParameterSource and SimplePropertyRowMapper strategies which are also available for direct use. They can serve as a common replacement for BeanPropertySqlParameterSource and BeanPropertyRowMapper/DataClassRowMapper, also with JdbcTemplate and NamedParameterJdbcTemplate themselves.

 

위에서 매개변수와 쿼리 결과에 대해 자동으로 이루어진 Actor 클래스 매핑은

암시적으로 사용되는 SimplePropertySqlParameterSourceSimplePropertyRowMapper 전략을 통해 제공됩니다.

이 전략들은 직접 사용하는 것도 가능하며, BeanPropertySqlParameterSourceBeanPropertyRowMapper 또는 DataClassRowMapper대체하는 일반적인 방식으로도 사용할 수 있습니다.

이는 JdbcTemplateNamedParameterJdbcTemplate 모두에서 적용 가능합니다.

 

JdbcClient is a flexible but simplified facade for JDBC query/update statements. Advanced capabilities such as batch inserts and stored procedure calls typically require extra customization: consider Spring’s SimpleJdbcInsert and SimpleJdbcCall classes or plain direct JdbcTemplate usage for any such capabilities not available in JdbcClient.

 

JdbcClient는 JDBC 쿼리/업데이트 문에 대한 유연하면서도 단순화된 파사드(facade)입니다.

배치 삽입이나 저장 프로시저 호출과 같은 고급 기능은 일반적으로 추가적인 커스터마이징이 필요합니다.

이러한 기능이 JdbcClient에서 제공되지 않는 경우에는 Spring의 SimpleJdbcInsertSimpleJdbcCall 클래스나

기본적인 JdbcTemplate 사용을 고려해야 합니다.

 

Using SQLExceptionTranslator

SQLExceptionTranslator is an interface to be implemented by classes that can translate between SQLExceptions and Spring’s own org.springframework.dao.DataAccessException, which is agnostic in regard to data access strategy. Implementations can be generic (for example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error codes) for greater precision. This exception translation mechanism is used behind the common JdbcTemplate and JdbcTransactionManager entry points which do not propagate SQLException but rather DataAccessException.

 

SQLExceptionTranslator는 SQLException을 Spring 고유의 org.springframework.dao.DataAccessException으로 변환할 수 있는 클래스들이 구현해야 하는 인터페이스입니다. DataAccessException은 데이터 접근 전략에 대해 중립적입니다.

구현체는 일반적일 수도 있고 (예: JDBC용 SQLState 코드를 사용하는 경우), 더 정밀도를 높이기 위해 벤더 고유일 수도 있습니다. (예: Oracle 오류 코드를 사용하는 경우). 이 예외 변환 메커니즘은 JdbcTemplateJdbcTransactionManager 같은 일반적인 진입점들 내부에서 사용되며, 이들은 SQLException을 그대로 전달하지 않고, 대신 DataAccessException으로 변환하여 처리합니다.

 

Spring은 JDBC에서 발생하는 SQLException을 직접 다루지 않고,자체 예외 타입인 DataAccessException으로 바꿔서 처리합니다.이걸 해주는 게 SQLExceptionTranslator라는 인터페이스입니다.

 

As of 6.0, the default exception translator is SQLExceptionSubclassTranslator, detecting JDBC 4 SQLException subclasses with a few extra checks, and with a fallback to SQLState introspection through SQLStateSQLExceptionTranslator. This is usually sufficient for common database access and does not require vendor-specific detection. For backwards compatibility, consider using SQLErrorCodeSQLExceptionTranslator as described below, potentially with custom error code mappings.

 

6.0부터는, 기본 예외 변환기는 SQLExceptionSubclassTranslator이며, JDBC 4의 SQLException 서브클래스를 몇 가지 추가적인 검사와 함께 감지합니다. 그리고 SQLStateSQLExceptionTranslator를 통해 SQLState를 분석하는 방식으로 대체(fallback)한다.

이 방식은 일반적인 데이터베이스 접근에는 보통 충분하며, 벤더(제조사)별 식별이 필요하지 않습니다.

하위 호환성이 필요할 경우, 아래에 설명된 SQLErrorCodeSQLExceptionTranslator의 사용을 고려하세요.

이때는 사용자 정의 오류 코드 매핑을 사용할 수도 있습니다.

 

SQLErrorCodeSQLExceptionTranslator is the implementation of SQLExceptionTranslator that is used by default when a file named sql-error-codes.xml is present in the root of the classpath. This implementation uses specific vendor codes. It is more precise than SQLState or SQLException subclass translation. The error code translations are based on codes held in a JavaBean type class called SQLErrorCodes. This class is created and populated by an SQLErrorCodesFactory, which (as the name suggests) is a factory for creating SQLErrorCodes based on the contents of a configuration file named sql-error-codes.xml. This file is populated with vendor codes and based on the DatabaseProductName taken from DatabaseMetaData. The codes for the actual database you are using are used.

SQLErrorCodeSQLExceptionTranslator는 SQLExceptionTranslator의 구현체로, sql-error-codes.xml이라는 파일이 클래스패스의 루트에 존재할 때 기본적으로 사용됩니다.

이 구현은 특정 벤더 코드를 사용하며, SQLStateSQLException 서브클래스 변환보다 더 정확합니다.

오류 코드 변환은 SQLErrorCodes라는 JavaBean 타입 클래스에 저장된 코드들을 기반으로 합니다.

이 클래스는 SQLErrorCodesFactory에 의해 생성되고 채워지며, SQLErrorCodesFactory는 (이름에서 알 수 있듯이) sql-error-codes.xml 파일의 내용을 기반으로 SQLErrorCodes를 생성하는 팩토리입니다.

이 파일은 벤더 코드를 채워넣고, DatabaseMetaData에서 가져온 DatabaseProductName을 기반으로 합니다.

실제로 사용하는 데이터베이스의 코드들이 사용됩니다.

 

The SQLErrorCodeSQLExceptionTranslator applies matching rules in the following sequence:

  1. Any custom translation implemented by a subclass. Normally, the provided concrete SQLErrorCodeSQLExceptionTranslator is used, so this rule does not apply. It applies only if you have actually provided a subclass implementation.
  2. Any custom implementation of the SQLExceptionTranslator interface that is provided as the customSqlExceptionTranslator property of the SQLErrorCodes class.
  3. The list of instances of the CustomSQLErrorCodesTranslation class (provided for the customTranslations property of the SQLErrorCodes class) are searched for a match.
  4. Error code matching is applied.
  5. Use the fallback translator. SQLExceptionSubclassTranslator is the default fallback translator. If this translation is not available, the next fallback translator is the SQLStateSQLExceptionTranslator.

SQLErrorCodeSQLExceptionTranslator는 다음과 같은 순서로 일치하는 규칙을 적용합니다.

 

1. 서브클래스에서 구현한 사용자 정의 변환

보통 제공되는 구체적인 SQLErrorCodeSQLExceptionTranslator가 사용되므로 이 규칙은 적용되지 않습니다.

이 규칙은 실제로 서브클래스 구현을 제공한 경우에만 적용됩니다.

 

2. SQLErrorCodes 클래스의 customSqlExceptionTranslator 속성으로 제공된 SQLExceptionTranslator 인터페이스의 사용자 정의 구현

 

3. SQLErrorCodes 클래스의 customTranslations 속성으로 제공된 CustomSQLErrorCodesTranslation 클래스 인스턴스들의 목록을 검색하여 일치하는 항목을 찾습니다.

 

4. 오류 코드 일치가 적용됩니다.

 

5. 대체 번역기를 사용합니다.

SQLExceptionSubclassTranslator가 기본 대체 번역기이며, 이 번역기가 사용되지 않으면,

그 다음 대체 번역기는 SQLStateSQLExceptionTranslator가 됩니다.