Loading...
Spring Framework Reference Documentation 7.0.2의 Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
이 섹션에서는 error handling을 포함하여 기본 JDBC 처리을 제어하기 위해 JDBC core 클래스를 사용하는 방법을 다룹니다. 다음 주제를 포함합니다:
JdbcTemplateNamedParameterJdbcTemplateJdbcClientSQLExceptionTranslatorJdbcTemplateJdbcTemplate은 JDBC core 패키지의 central 클래스입니다. 이 클래스는 리소스의 생성과 해제를 처리하여 커넥션을 닫는 것을 잊는 것과 같은 일반적인 error를 피할 수 있도록 도와줍니다. 이 클래스는 core JDBC 워크플로우(예: statement 생성 및 실행)의 기본적인 작업을 수행하고, 애플리케이션 코드는 SQL을 제공하고 result를 추출하도록 남겨 둡니다.
JdbcTemplate 클래스는 다음을 수행합니다:
ResultSet 인스턴스에 대한 iteration 및 반환된 parameter value의 extraction 수행.org.springframework.dao 패키지에 정의된 일반적이고 더 유익한 exception hierarchy로 변환합니다. (Consistent Exception Hierarchy를 참조하세요.)코드에서 JdbcTemplate을 사용할 때는 callback 인터페이스만 구현하면 되며, 이들에게 명확하게 정의된 contract를 제공합니다. JdbcTemplate 클래스가 제공하는 Connection을 사용하여 PreparedStatementCreator callback 인터페이스는 SQL과 필요한 parameter를 제공하여 prepared statement를 생성합니다. CallableStatementCreator 인터페이스도 마찬가지로 callable statement를 생성합니다.
RowCallbackHandler 인터페이스는 ResultSet의 각 row에서 value를 추출합니다.
JdbcTemplate은 DAO 구현 내에서 DataSource reference와 함께 직접 인스턴스화하여 사용할 수도 있고, Spring IoC 컨테이너에서 구성한 후 빈 reference로 DAO에 제공할 수도 있습니다.
DataSource는 항상 Spring IoC 컨테이너에서 빈으로 구성해야 합니다. 첫 번째 경우에는 빈이 서비스에 직접 제공되고, 두 번째 경우에는 준비된 template에 제공됩니다.
이 클래스에 의해 실행되는 모든 SQL은 template 인스턴스의 fully qualified 클래스 이름(일반적으로 JdbcTemplate이지만, JdbcTemplate 클래스의 custom 서브클래스를 사용하는 경우에는 다를 수 있음)에 해당하는 category 아래에서 DEBUG level로 로깅됩니다.
다음 섹션에서는 JdbcTemplate 사용 예제를 제공합니다. 이 예제는 JdbcTemplate이 노출하는 모든 기능의 exhaustive한 목록은 아닙니다. 이에 대해서는 관련 javadoc을 참조하세요.
SELECT)다음 query는 relation의 row 수를 가져옵니다:
1int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
1val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
다음 query는 bind variable을 사용합니다:
1int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject( 2 "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
1val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>( 2 "select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
다음 query는 String을 찾습니다:
1String lastName = this.jdbcTemplate.queryForObject( 2 "select last_name from t_actor where id = ?", 3 String.class, 1212L);
1val lastName = jdbcTemplate.queryForObject<String>( 2 "select last_name from t_actor where id = ?", 3 arrayOf(1212L))!!
다음 query는 단일 domain 객체를 찾고 채웁니다:
1Actor actor = jdbcTemplate.queryForObject( 2 "select first_name, last_name from t_actor where id = ?", 3 (resultSet, rowNum) -> { 4 Actor newActor = new Actor(); 5 newActor.setFirstName(resultSet.getString("first_name")); 6 newActor.setLastName(resultSet.getString("last_name")); 7 return newActor; 8 }, 9 1212L);
1val actor = jdbcTemplate.queryForObject( 2 "select first_name, last_name from t_actor where id = ?", 3 arrayOf(1212L) 4) { rs, _ -> 5 Actor(rs.getString("first_name"), rs.getString("last_name")) 6}
다음 query는 domain 객체 list를 찾고 채웁니다:
1List<Actor> actors = this.jdbcTemplate.query( 2 "select first_name, last_name from t_actor", 3 (resultSet, rowNum) -> { 4 Actor actor = new Actor(); 5 actor.setFirstName(resultSet.getString("first_name")); 6 actor.setLastName(resultSet.getString("last_name")); 7 return actor; 8 });
1val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ -> 2 Actor(rs.getString("first_name"), rs.getString("last_name")) 3}
만약 마지막 두 code snippet이 실제로 같은 애플리케이션에 존재한다면, 두 RowMapper lambda expression에 있는 중복을 제거하고 이를 하나의 field로 추출하여 필요에 따라 DAO 메서드가 reference하도록 하는 것이 합리적입니다.
예를 들어, 앞의 code snippet을 다음과 같이 작성하는 것이 더 나을 수 있습니다:
1private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> { 2 Actor actor = new Actor(); 3 actor.setFirstName(resultSet.getString("first_name")); 4 actor.setLastName(resultSet.getString("last_name")); 5 return actor; 6}; 7 8public List<Actor> findAllActors() { 9 return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper); 10}
1val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int -> 2 Actor(rs.getString("first_name"), rs.getString("last_name")) 3} 4 5fun findAllActors(): List<Actor> { 6 return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper) 7}
INSERT, UPDATE, and DELETE) with JdbcTemplateupdate(..) 메서드를 사용하여 insert, update, delete operation을 수행할 수 있습니다.
Parameter value는 보통 variable argument로 제공되거나, 또는 객체 array로 제공됩니다.
다음 예제는 새 entry를 insert합니다:
1this.jdbcTemplate.update( 2 "insert into t_actor (first_name, last_name) values (?, ?)", 3 "Leonor", "Watling");
1jdbcTemplate.update( 2 "insert into t_actor (first_name, last_name) values (?, ?)", 3 "Leonor", "Watling" 4)
다음 예제는 기존 entry를 update합니다:
1this.jdbcTemplate.update( 2 "update t_actor set last_name = ? where id = ?", 3 "Banjo", 5276L);
1jdbcTemplate.update( 2 "update t_actor set last_name = ? where id = ?", 3 "Banjo", 5276L 4)
다음 예제는 entry를 delete합니다:
1this.jdbcTemplate.update( 2 "delete from t_actor where id = ?", 3 Long.valueOf(actorId));
1jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
JdbcTemplate Operationsexecute(..) 메서드를 사용하여 임의의 SQL을 실행할 수 있습니다. 따라서 이 메서드는 종종 DDL statement에 사용됩니다. 이 메서드는 callback 인터페이스, binding variable array 등을 받는 다양한 variant로 심하게 overloading되어 있습니다.
다음 예제는 table을 생성합니다:
1this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
1jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
다음 예제는 stored procedure를 호출합니다:
1this.jdbcTemplate.update( 2 "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", 3 Long.valueOf(unionId));
1jdbcTemplate.update( 2 "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)", 3 unionId.toLong() 4)
더 정교한 stored procedure 지원은 뒷부분에서 다룹니다.
JdbcTemplate Best PracticesJdbcTemplate 클래스의 인스턴스는 한 번 구성되면 thread-safe합니다. 이는 하나의 JdbcTemplate 인스턴스를 구성한 다음 이 공유 reference를 여러 DAO(또는 repository)에 안전하게 주입할 수 있다는 점에서 중요합니다. JdbcTemplate은 DataSource에 대한 reference를 유지한다는 점에서 stateful이지만, 이 state는 conversation state가 아닙니다.
JdbcTemplate 클래스(및 연관된 NamedParameterJdbcTemplate 클래스)를 사용할 때의 일반적인 practice는 Spring 설정 파일에 DataSource를 구성한 다음 그 공유 DataSource 빈을 DAO 클래스에 dependency-inject하는 것입니다.
JdbcTemplate은 DataSource에 대한 setter 또는 constructor에서 생성됩니다. 이는 다음과 같은 DAO를 만들게 됩니다:
1public class JdbcCorporateEventDao implements CorporateEventDao { 2 3 private final JdbcTemplate jdbcTemplate; 4 5 public JdbcCorporateEventDao(DataSource dataSource) { 6 this.jdbcTemplate = new JdbcTemplate(dataSource); 7 } 8 9 // JDBC-backed implementations of the methods on the CorporateEventDao follow... 10}
1class JdbcCorporateEventDao(dataSource: DataSource): CorporateEventDao { 2 3 private val jdbcTemplate = JdbcTemplate(dataSource) 4 5 // JDBC-backed implementations of the methods on the CorporateEventDao follow... 6}
다음 예제는 이에 상응하는 설정을 보여줍니다:
1@Bean 2JdbcCorporateEventDao corporateEventDao(DataSource dataSource) { 3 return new JdbcCorporateEventDao(dataSource); 4} 5 6@Bean(destroyMethod = "close") 7BasicDataSource dataSource() { 8 BasicDataSource dataSource = new BasicDataSource(); 9 dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); 10 dataSource.setUrl("jdbc:hsqldb:hsql://localhost:"); 11 dataSource.setUsername("sa"); 12 dataSource.setPassword(""); 13 return dataSource; 14}
1@Bean 2fun corporateEventDao(dataSource: DataSource) = JdbcCorporateEventDao(dataSource) 3 4@Bean(destroyMethod = "close") 5fun dataSource() = BasicDataSource().apply { 6 driverClassName = "org.hsqldb.jdbcDriver" 7 url = "jdbc:hsqldb:hsql://localhost:" 8 username = "sa" 9 password = "" 10}
1<bean id="corporateEventDao" class="org.example.jdbc.JdbcCorporateEventDao"> 2 <constructor-arg ref="dataSource"/> 3</bean> 4 5<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> 6 <property name="driverClassName" value="${jdbc.driverClassName}"/> 7 <property name="url" value="${jdbc.url}"/> 8 <property name="username" value="${jdbc.username}"/> 9 <property name="password" value="${jdbc.password}"/> 10</bean> 11 12<context:property-placeholder location="jdbc.properties"/>
명시적인 설정의 대안으로 component-scanning과 annotation 기반 dependency injection 지원을 사용할 수 있습니다. 이 경우 클래스에 @Repository를 어노테이션으로 달아(이로써 component-scanning의 candidate가 됨) 사용할 수 있습니다.
다음 예제는 그 방법을 보여줍니다:
1@Repository 2public class JdbcCorporateEventRepository implements CorporateEventRepository { 3 4 private JdbcTemplate jdbcTemplate; 5 6 // Implicitly autowire the DataSource constructor parameter 7 public JdbcCorporateEventRepository(DataSource dataSource) { 8 this.jdbcTemplate = new JdbcTemplate(dataSource); 9 } 10 11 // JDBC-backed implementations of the methods on the CorporateEventRepository follow... 12}
다음 예제는 이에 상응하는 설정을 보여줍니다:
1@Configuration 2@ComponentScan("org.example.jdbc") 3public class JdbcCorporateEventRepositoryConfiguration { 4 5 @Bean(destroyMethod = "close") 6 BasicDataSource dataSource() { 7 BasicDataSource dataSource = new BasicDataSource(); 8 dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); 9 dataSource.setUrl("jdbc:hsqldb:hsql://localhost:"); 10 dataSource.setUsername("sa"); 11 dataSource.setPassword(""); 12 return dataSource; 13 } 14 15}
1@Configuration 2@ComponentScan("org.example.jdbc") 3class JdbcCorporateEventRepositoryConfiguration { 4 5 @Bean(destroyMethod = "close") 6 fun dataSource() = BasicDataSource().apply { 7 driverClassName = "org.hsqldb.jdbcDriver" 8 url = "jdbc:hsqldb:hsql://localhost:" 9 username = "sa" 10 password = "" 11 } 12 13}
1<!-- Scans within the base package of the application for @Component classes to configure as beans --> 2<context:component-scan base-package="org.example.jdbc" /> 3 4<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> 5 <property name="driverClassName" value="${jdbc.driverClassName}"/> 6 <property name="url" value="${jdbc.url}"/> 7 <property name="username" value="${jdbc.username}"/> 8 <property name="password" value="${jdbc.password}"/> 9</bean> 10 11<context:property-placeholder location="jdbc.properties"/>
Spring의 JdbcDaoSupport 클래스를 사용하고 다양한 JDBC-backed DAO 클래스가 이를 상속하는 경우, 서브클래스는 JdbcDaoSupport 클래스로부터 setDataSource(..) 메서드를 상속받습니다. 이 클래스를 상속할지 여부는 선택 사항입니다. JdbcDaoSupport 클래스는 편의를 위해 제공될 뿐입니다.
위의 template initialization style 중 어느 것을 사용하든(또는 사용하지 않든), SQL을 실행할 때마다 새로운 JdbcTemplate 클래스 인스턴스를 생성해야 하는 경우는 거의 없습니다. 한 번 구성된 JdbcTemplate 인스턴스는 thread-safe입니다. 애플리케이션이 여러 database에 접근하는 경우, 여러 DataSource와 그에 따라 다르게 구성된 여러 JdbcTemplate 인스턴스가 필요할 수 있습니다.
NamedParameterJdbcTemplateNamedParameterJdbcTemplate 클래스는 classic placeholder('?') argument만을 사용하여 JDBC statement를 programming하는 대신 named parameter를 사용하여 JDBC statement를 programming하는 것을 지원합니다. NamedParameterJdbcTemplate 클래스는 JdbcTemplate을 wrapping하고 많은 작업을 wrapped JdbcTemplate에 위임합니다.
이 섹션에서는 JdbcTemplate 자체와 다른 NamedParameterJdbcTemplate 클래스의 영역, 즉 named parameter를 사용하여 JDBC statement를 programming하는 부분만 설명합니다. 다음 예제는 NamedParameterJdbcTemplate 사용 방법을 보여줍니다:
1// some JDBC-backed DAO class... 2private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 3 4public void setDataSource(DataSource dataSource) { 5 this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); 6} 7 8public int countOfActorsByFirstName(String firstName) { 9 String sql = "select count(*) from t_actor where first_name = :first_name"; 10 SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName); 11 return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); 12}
1private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) 2 3fun countOfActorsByFirstName(firstName: String): Int { 4 val sql = "select count(*) from t_actor where first_name = :first_name" 5 val namedParameters = MapSqlParameterSource("first_name", firstName) 6 return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! 7}
sql 변수에 할당된 value와 MapSqlParameterSource 타입의 namedParameters 변수에 연결된 value에서 named parameter 표기법이 사용되는 것에 주목하세요.
또는 Map 기반 style을 사용하여 named parameter와 그에 상응하는 value를 NamedParameterJdbcTemplate 인스턴스에 전달할 수 있습니다. NamedParameterJdbcOperations가 노출하고 NamedParameterJdbcTemplate 클래스가 구현하는 나머지 메서드도 유사한 pattern을 따르며 여기서는 다루지 않습니다.
다음 예제는 Map 기반 style의 사용을 보여줍니다:
1// some JDBC-backed DAO class... 2private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 3 4public void setDataSource(DataSource dataSource) { 5 this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); 6} 7 8public int countOfActorsByFirstName(String firstName) { 9 String sql = "select count(*) from t_actor where first_name = :first_name"; 10 Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName); 11 return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); 12}
1// some JDBC-backed DAO class... 2private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) 3 4fun countOfActorsByFirstName(firstName: String): Int { 5 val sql = "select count(*) from t_actor where first_name = :first_name" 6 val namedParameters = mapOf("first_name" to firstName) 7 return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! 8}
NamedParameterJdbcTemplate(및 동일한 Java 패키지에 존재)와 관련된 좋은 feature 중 하나는 SqlParameterSource 인터페이스입니다. 이전 code snippet 중 하나에서 이 인터페이스의 구현 예(MapSqlParameterSource 클래스)를 이미 보았습니다. SqlParameterSource는 NamedParameterJdbcTemplate에 대한 named parameter value의 source입니다. MapSqlParameterSource 클래스는 key가 parameter name이고 value가 parameter value인 java.util.Map을 감싸는 간단한 구현입니다.
또 다른 SqlParameterSource 구현은 BeanPropertySqlParameterSource 클래스입니다. 이 클래스는 임의의 JavaBean(즉, JavaBean conventions을 따르는 클래스 인스턴스)을 wrapping하고, wrapping된 JavaBean의 property를 named parameter value의 source로 사용합니다.
다음 예제는 typical JavaBean을 보여줍니다:
1public class Actor { 2 3 private Long id; 4 private String firstName; 5 private String lastName; 6 7 public String getFirstName() { 8 return this.firstName; 9 } 10 11 public String getLastName() { 12 return this.lastName; 13 } 14 15 public Long getId() { 16 return this.id; 17 } 18 19 // setters omitted... 20}
1data class Actor(val id: Long, val firstName: String, val lastName: String)
다음 예제는 앞의 예제에 나온 클래스의 member 수를 반환하기 위해 NamedParameterJdbcTemplate을 사용합니다:
1// some JDBC-backed DAO class... 2private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 3 4public void setDataSource(DataSource dataSource) { 5 this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); 6} 7 8public int countOfActors(Actor exampleActor) { 9 // notice how the named parameters match the properties of the above 'Actor' class 10 String sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName"; 11 SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); 12 return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class); 13}
1// some JDBC-backed DAO class... 2private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource) 3 4fun countOfActors(exampleActor: Actor): Int { 5 // notice how the named parameters match the properties of the above 'Actor' class 6 val sql = "select count(*) from t_actor where first_name = :firstName and last_name = :lastName" 7 val namedParameters = BeanPropertySqlParameterSource(exampleActor) 8 return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!! 9}
NamedParameterJdbcTemplate 클래스가 classic JdbcTemplate template을 wrapping한다는 점을 기억하세요. JdbcTemplate 클래스에만 존재하는 기능에 접근하기 위해 wrapping된 JdbcTemplate 인스턴스에 접근해야 하는 경우, getJdbcOperations() 메서드를 사용하여 JdbcOperations 인터페이스를 통해 wrapping된 JdbcTemplate에 접근할 수 있습니다.
애플리케이션 컨텍스트에서 NamedParameterJdbcTemplate 클래스를 사용하는 방법에 대한 guideline은 JdbcTemplate Best Practices를 참조하세요.
JdbcClient6.1부터 NamedParameterJdbcTemplate의 named parameter statement와 일반 JdbcTemplate의 positional parameter statement는 fluent interaction model을 가진 unified client API를 통해 사용할 수 있습니다.
예를 들어, positional parameter를 사용하는 경우:
1private JdbcClient jdbcClient = JdbcClient.create(dataSource); 2 3public int countOfActorsByFirstName(String firstName) { 4 return this.jdbcClient.sql("select count(*) from t_actor where first_name = ?") 5 .param(firstName) 6 .query(Integer.class).single(); 7}
예를 들어, named parameter를 사용하는 경우:
1private JdbcClient jdbcClient = JdbcClient.create(dataSource); 2 3public int countOfActorsByFirstName(String firstName) { 4 return this.jdbcClient.sql("select count(*) from t_actor where first_name = :firstName") 5 .param("firstName", firstName) 6 .query(Integer.class).single(); 7}
RowMapper 기능도 사용할 수 있으며, flexible한 result resolution을 제공합니다:
1List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor") 2 .query((rs, rowNum) -> new Actor(rs.getString("first_name"), rs.getString("last_name"))) 3 .list();
custom RowMapper 대신 mapping할 클래스를 지정할 수도 있습니다.
예를 들어, Actor가 record 클래스, custom constructor, bean property, plain field로 firstName과 lastName property를 가진다고 가정하면:
1List<Actor> actors = this.jdbcClient.sql("select first_name, last_name from t_actor") 2 .query(Actor.class) 3 .list();
필수 single 객체 result의 경우:
1Actor actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?") 2 .param(1212L) 3 .query(Actor.class) 4 .single();
java.util.Optional result의 경우:
1Optional<Actor> actor = this.jdbcClient.sql("select first_name, last_name from t_actor where id = ?") 2 .param(1212L) 3 .query(Actor.class) 4 .optional();
update statement의 경우:
1this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (?, ?)") 2 .param("Leonor").param("Watling") 3 .update();
또는 named parameter를 사용하는 update statement의 경우:
1this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)") 2 .param("firstName", "Leonor").param("lastName", "Watling") 3 .update();
개별 named parameter 대신 parameter source 객체를 지정할 수도 있습니다.
예를 들어, 위의 Actor 클래스와 같이 firstName과 lastName property를 제공하는 record 클래스, bean property를 가진 클래스, plain field holder:
1this.jdbcClient.sql("insert into t_actor (first_name, last_name) values (:firstName, :lastName)") 2 .paramSource(new Actor("Leonor", "Watling")) 3 .update();
위에서 parameter와 query result 모두에 대한 자동 Actor 클래스 mapping은 implicit SimplePropertySqlParameterSource 및 SimplePropertyRowMapper strategy를 통해 제공되며, 이 strategy는 직접 사용도 가능합니다. 이들은 BeanPropertySqlParameterSource 및 BeanPropertyRowMapper/DataClassRowMapper의 공통 대체제로 사용할 수 있으며, JdbcTemplate 및 NamedParameterJdbcTemplate 자체와 함께 사용할 수도 있습니다.
JdbcClient는 JDBC query/update statement를 위한 flexible하지만 단순화된 facade입니다. batch insert 및 stored procedure 호출과 같은 고급 기능은 일반적으로 추가 customization이 필요합니다. 이러한 기능 중JdbcClient에서 사용할 수 없는 기능에 대해서는 Spring의SimpleJdbcInsert및SimpleJdbcCall클래스 또는 plain directJdbcTemplate사용을 고려하세요.
SQLExceptionTranslatorSQLExceptionTranslator는 SQLException과 data access strategy에 대해 agnostic한 Spring의 org.springframework.dao.DataAccessException 사이를 변환할 수 있는 클래스가 구현해야 하는 인터페이스입니다. 구현은 generic(JDBC용 SQLState code 사용 등)일 수도 있고, 보다 정확성을 위해 proprietary(Oracle error code 사용 등)일 수도 있습니다.
이 exception translation mechanism은 SQLException을 전파하지 않고 DataAccessException을 전파하는 common JdbcTemplate 및 JdbcTransactionManager entry point 뒤에서 사용됩니다.
6.0부터 default exception translator는 JDBC 4
SQLException서브클래스를 감지하고 몇 가지 추가 check를 수행하며,SQLStateSQLExceptionTranslator를 통한SQLStateintrospection으로 fallback하는SQLExceptionSubclassTranslator입니다. 이는 일반적인 database access에 대해 보통 충분하며 vendor-specific detection을 필요로 하지 않습니다. 이전 버전과의 호환성을 위해, 아래에 설명된 대로 custom error code mapping과 함께SQLErrorCodeSQLExceptionTranslator를 사용하는 것을 고려하세요.
SQLErrorCodeSQLExceptionTranslator는 classpath root에 sql-error-codes.xml이라는 file이 존재할 때 default로 사용되는 SQLExceptionTranslator 구현입니다. 이 구현은 특정 vendor code를 사용합니다. 이는 SQLState 또는 SQLException 서브클래스 translation보다 더 정확합니다.
error code translation은 SQLErrorCodes라는 JavaBean 타입 클래스에 보관된 code를 기반으로 합니다. 이 클래스는 SQLErrorCodesFactory에 의해 생성되고 채워지며, 이름에서 알 수 있듯이 sql-error-codes.xml이라는 설정 파일의 내용을 기반으로 SQLErrorCodes를 생성하는 factory입니다.
이 file은 vendor code로 채워져 있으며 사용 중인 database의 DatabaseMetaData에서 가져온 DatabaseProductName을 기반으로 합니다. 사용 중인 실제 database에 대한 code가 사용됩니다.
SQLErrorCodeSQLExceptionTranslator는 다음 순서로 matching rule을 적용합니다:
서브클래스에 의해 구현된 custom translation. 일반적으로 제공된 concrete
SQLErrorCodeSQLExceptionTranslator가 사용되므로 이 rule은 적용되지 않습니다.
이는 실제로 서브클래스 구현을 제공한 경우에만 적용됩니다.
SQLErrorCodes 클래스의 customSqlExceptionTranslator property로 제공된
SQLExceptionTranslator 인터페이스의 custom 구현.
SQLErrorCodes 클래스의 customTranslations property에 대해 제공된
CustomSQLErrorCodesTranslation 클래스 인스턴스 list에서 match를 검색합니다.
error code matching이 적용됩니다.
fallback translator를 사용합니다. SQLExceptionSubclassTranslator는 default fallback
translator입니다. 이 translation이 사용 가능하지 않은 경우, 다음 fallback translator는
SQLStateSQLExceptionTranslator입니다.
SQLErrorCodesFactory는 기본적으로 error code와 custom exception translation을 정의하는 데 사용됩니다. 이들은 classpath에서sql-error-codes.xml이라는 file에서 검색되며, 사용 중인 database의 database metadata에서 가져온 database name을 기반으로 matching되는SQLErrorCodes인스턴스가 위치합니다.
다음 예제에서 보듯이 SQLErrorCodeSQLExceptionTranslator를 확장할 수 있습니다:
1public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { 2 3 protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) { 4 if (sqlEx.getErrorCode() == -12345) { 5 return new DeadlockLoserDataAccessException(task, sqlEx); 6 } 7 return null; 8 } 9}
1class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() { 2 3 override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? { 4 if (sqlEx.errorCode == -12345) { 5 return DeadlockLoserDataAccessException(task, sqlEx) 6 } 7 return null 8 } 9}
앞의 예제에서 특정 error code(-12345)는 변환되지만 다른 error는 default translator 구현에 의해 변환되도록 남겨집니다. 이 custom translator를 사용하려면 setExceptionTranslator 메서드를 통해 이를 JdbcTemplate에 전달해야 하며, 이 translator가 필요한 모든 data access 처리에 대해 이 JdbcTemplate을 사용해야 합니다.
다음 예제는 이 custom translator를 사용하는 방법을 보여줍니다:
1private JdbcTemplate jdbcTemplate; 2 3public void setDataSource(DataSource dataSource) { 4 // create a JdbcTemplate and set data source 5 this.jdbcTemplate = new JdbcTemplate(); 6 this.jdbcTemplate.setDataSource(dataSource); 7 8 // create a custom translator and set the DataSource for the default translation lookup 9 CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator(); 10 tr.setDataSource(dataSource); 11 this.jdbcTemplate.setExceptionTranslator(tr); 12} 13 14public void updateShippingCharge(long orderId, long pct) { 15 // use the prepared JdbcTemplate for this update 16 this.jdbcTemplate.update("update orders" + 17 " set shipping_charge = shipping_charge * ? / 100" + 18 " where id = ?", pct, orderId); 19}
1// create a JdbcTemplate and set data source 2private val jdbcTemplate = JdbcTemplate(dataSource).apply { 3 // create a custom translator and set the DataSource for the default translation lookup 4 exceptionTranslator = CustomSQLErrorCodesTranslator().apply { 5 this.dataSource = dataSource 6 } 7} 8 9fun updateShippingCharge(orderId: Long, pct: Long) { 10 // use the prepared JdbcTemplate for this update 11 this.jdbcTemplate.update( 12 "update orders" + 13 " set shipping_charge = shipping_charge * ? / 100" + 14 " where id = ?", 15 pct, 16 orderId 17 ) 18}
custom translator는 sql-error-codes.xml에서 error code를 lookup하기 위해 data source를 전달받습니다.
SQL statement를 실행하는 데는 매우 적은 코드만 필요합니다. DataSource와
JdbcTemplate, 그리고 JdbcTemplate이 제공하는 convenience 메서드가 필요합니다.
다음 예제는 새 table을 생성하는 최소한이지만 완전히 동작하는 클래스에 포함해야 할 내용을 보여줍니다:
1import javax.sql.DataSource; 2import org.springframework.jdbc.core.JdbcTemplate; 3 4public class ExecuteAStatement { 5 6 private JdbcTemplate jdbcTemplate; 7 8 public void setDataSource(DataSource dataSource) { 9 this.jdbcTemplate = new JdbcTemplate(dataSource); 10 } 11 12 public void doExecute() { 13 this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))"); 14 } 15}
1import javax.sql.DataSource 2import org.springframework.jdbc.core.JdbcTemplate 3 4class ExecuteAStatement(dataSource: DataSource) { 5 6 private val jdbcTemplate = JdbcTemplate(dataSource) 7 8 fun doExecute() { 9 jdbcTemplate.execute("create table mytable (id integer, name varchar(100))") 10 } 11}
일부 query 메서드는 single value를 반환합니다. count 또는 한 row에서 특정 value를 가져오려면 queryForObject(..)를 사용하세요. 후자는 반환된 JDBC Type을 argument로 전달된 Java 클래스로 변환합니다. type 변환이 유효하지 않으면 InvalidDataAccessApiUsageException이 발생합니다.
다음 예제에는 int용 하나와 String을 query하는 하나, 두 개의 query 메서드가 포함됩니다:
1import javax.sql.DataSource; 2import org.springframework.jdbc.core.JdbcTemplate; 3 4public class RunAQuery { 5 6 private JdbcTemplate jdbcTemplate; 7 8 public void setDataSource(DataSource dataSource) { 9 this.jdbcTemplate = new JdbcTemplate(dataSource); 10 } 11 12 public int getCount() { 13 return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class); 14 } 15 16 public String getName() { 17 return this.jdbcTemplate.queryForObject("select name from mytable", String.class); 18 } 19}
1import javax.sql.DataSource 2import org.springframework.jdbc.core.JdbcTemplate 3 4class RunAQuery(dataSource: DataSource) { 5 6 private val jdbcTemplate = JdbcTemplate(dataSource) 7 8 val count: Int 9 get() = jdbcTemplate.queryForObject("select count(*) from mytable")!! 10 11 val name: String? 12 get() = jdbcTemplate.queryForObject("select name from mytable") 13}
single result query 메서드 외에도, query가 반환한 각 row에 대한 entry를 포함하는 list를 반환하는 여러 메서드가 있습니다. 가장 generic한 메서드는 queryForList(..)로, 각 element가 column name을 key로 사용하는 각 column에 대한 entry를 포함하는 Map인 List를 반환합니다.
앞의 예제에 모든 row의 list를 검색하는 메서드를 추가하면 다음과 같을 수 있습니다:
1private JdbcTemplate jdbcTemplate; 2 3public void setDataSource(DataSource dataSource) { 4 this.jdbcTemplate = new JdbcTemplate(dataSource); 5} 6 7public List<Map<String, Object>> getList() { 8 return this.jdbcTemplate.queryForList("select * from mytable"); 9}
1private val jdbcTemplate = JdbcTemplate(dataSource) 2 3fun getList(): List<Map<String, Any>> { 4 return jdbcTemplate.queryForList("select * from mytable") 5}
반환된 list는 다음과 유사합니다:
1[{name=Bob, id=1}, {name=Mary, id=2}]
다음 예제는 특정 primary key에 대한 column을 update합니다:
1import javax.sql.DataSource; 2import org.springframework.jdbc.core.JdbcTemplate; 3 4public class ExecuteAnUpdate { 5 6 private JdbcTemplate jdbcTemplate; 7 8 public void setDataSource(DataSource dataSource) { 9 this.jdbcTemplate = new JdbcTemplate(dataSource); 10 } 11 12 public void setName(int id, String name) { 13 this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id); 14 } 15}
1import javax.sql.DataSource 2import org.springframework.jdbc.core.JdbcTemplate 3 4class ExecuteAnUpdate(dataSource: DataSource) { 5 6 private val jdbcTemplate = JdbcTemplate(dataSource) 7 8 fun setName(id: Int, name: String) { 9 jdbcTemplate.update("update mytable set name = ? where id = ?", name, id) 10 } 11}
앞의 예제에서, SQL statement에는 row parameter에 대한 placeholder가 있습니다. parameter value는 varargs로 전달하거나, 또는 객체 array로 전달할 수 있습니다. 따라서 primitive를 primitive wrapper 클래스으로 명시적으로 wrapping하거나 auto-boxing을 사용해야 합니다.
database에 의해 생성된 primary key를 retrieval하는 update() convenience 메서드가 지원됩니다. 이 지원은 JDBC 3.0 standard의 일부입니다. 자세한 내용은 specification의 13.6장을 참조하세요.
이 메서드는 첫 번째 argument로 PreparedStatementCreator를 받으며, 이는 필요한 insert statement를 지정하는 방법입니다. 다른 argument는 update가 성공적으로 반환될 때 생성된 key를 포함하는 KeyHolder입니다.
적절한 PreparedStatement를 생성하는 표준적인 단일 방법은 없습니다(이 때문에 메서드 시그니처가 현재와 같은 형태입니다). 다음 예제는 Oracle에서 작동하지만 다른 platform에서는 작동하지 않을 수 있습니다:
1final String INSERT_SQL = "insert into my_test (name) values(?)"; 2final String name = "Rob"; 3 4KeyHolder keyHolder = new GeneratedKeyHolder(); 5jdbcTemplate.update(connection -> { 6 PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" }); 7 ps.setString(1, name); 8 return ps; 9}, keyHolder); 10 11// keyHolder.getKey() now contains the generated key
1val INSERT_SQL = "insert into my_test (name) values(?)" 2val name = "Rob" 3 4val keyHolder = GeneratedKeyHolder() 5jdbcTemplate.update({ connection -> 6 connection.prepareStatement(INSERT_SQL, arrayOf("id")).apply { 7 setString(1, name) 8 } 9}, keyHolder) 10 11// keyHolder.getKey() now contains the generated key
Package Hierarchy
Controlling Database Connections