Spring의 JDBC추상화 프레임워크에 의해 제공되는 값-추가는 다음의 목록에 의해 가장 잘 보여진다(기울임꼴로 되어 있는 줄은 Spring의 JDBC추상화 프레임워크를 사용할때 애플리케이션 개발자에 의해 코딩될 필요가 있다는데 노트하라.).
connection 파라미터 정의하기
connection 열기
구문(statement) 명시하기
구문을 준비하고 수행하기
결과를 통해 반복(iterate)을 반복하기
각각의 반복에 대해 작업하기
예외 처리하기
트랜잭션 다루기
connection 닫기
Spring은 사용하기 어렵고 개발을 반복하는 복잡한 API와 같은 JDBC를 만드는 하위레벨의 상세한 사항 모두를 다룬다.
JDBC추상 프레임워크는 Spring에 의해 제공되는 4개( core, datasource, object, 그리고 support)의 패키지로 구성된다.
org.springframework.jdbc.core패키지는 JdbcTemplate를 포함하고 이것의 다양한 callback인터페이스, 거기다가 다양한 관련 클래스를 포함한다.
org.springframework.jdbc.datasource패키지는 쉬운 DataSource 접근을 위한 유틸리티 클래스를 포함하고 J2EE컨테이너밖에서 변경이 되지 않은 JDBC코드를 테스트하고 실행하기 위해 사용될수 있는 여러가지 간단한 DataSource구현을 포함한다. 유틸리티클래스는 필요하다면 JNDI로 부터 Connection을 얻고 Connection을 닫는 정적 메소드를 제공한다. 이것은 DataSourceTransactionManager를 사용하는 것처럼 쓰레드범위의 연결을 지원한다.
그 다음 org.springframework.jdbc.object패키지는 쓰레드에 안전하고 재사용가능한 객체처럼 RDBMS 쿼리, update 그리고 저장 프로시저를 표현하는 클래스를 포함한다. 이 접근법은 JDO에 의해 형상화 되었다. 쿼리에 의해 반환된 객체는 데이터베이스로 부터 “disconnected”된다. JDBC추상화의 높은 레벨은 org.springframework.jdbc.core패키지내에서 하위 레벨에 의존한다.
마지막으로 org.springframework.jdbc.support패키지는 SQLException번역 기능과 몇개의 유틸리티 클래스를 찾을수 있는 곳이다.
JDBC처리중에 던져진 예외는 org.springframework.dao패키지내에서 정의된 예외로 번역이 된다. 이것은 Spring JDBC추상 레이어를 사용하는 코드가 JDBC또는 RDBMS특성 에러 처리를 구현할 필요가 없다는 것을 의미한다. 모든 번역된 예외는 호출자에게 전파되기 위한 다른 예외를 허락하는 동안 당신이 복구할수 있는 예외를 잡는 옵션을 제공하고 체크되지 않는다.
JdbcTemplate은 JDBC Core패키지에서 핵심 클래스이다. 이것은 자원을 생성하고 해재함으로써 JDBC의 사용을 단순화시킨다. 이것은 연결을 닫는것을 잊어버리는것처럼 공통적으로 발생할수 있는 에러를 피하도록 도와준다. 이것은 statement생성및 수행, SQL을 생성하고 결과물을 반환하고 애플리케이션 코드를 벗어나는 핵심적인 JDBC절차를 수행한다. 이 클래스는 SQL쿼리, update문 또는 저장 프로시저 호출, ResultSet를 넘어서 순환을 모방하고 반환된 인자값을 보여주는 작업을 수행한다. 이것은 또한 JDBC예외를 잡고 일반적인 것으로 그것들을 번역하고 좀더 다양한 정보를 제공하도록 하고 org.springframework.dao패키지내에 정의된 예외 구조제공한다.
이 클래스를 사용하는 코드는 단지 명백하게 정의된 규칙을 제공하는 callback인터페이스만 구현할 필요가 있다. PreparedStatementCreator callback인터페이스는 SQL과 필요한 인자를 제공하는 클래스에 의해 제공되는 Connection으로 prepared statement를 생성한다. 호출 가능한 statement를 생성하는 것은 CallableStatementCreateor 인터페이스이다. RowCallbackHandler 인터페이스는 ResultSet으로 부터 각각의 row에서 값을 뽑아낸다.
이 클래스는 DataSource 참조또는 애플리케이션 컨텍스트내에서 준비되고 빈(bean)참조처럼 서비스하기 위해 직접적인 초기화를 통해 서비스구현내에서 사용될수 있다. 주의: DataSource는 애플리케이션 컨텍스트내에서 언제나 빈처럼 설정되어야 한다. 이 클래스는 callback인터페이스와 SQLExceptionTranslator인터페이스에 의해 인자화 되기 때문에 이것을 하위클래스화 할 필요가 없다.
마지막으로, 이 클래스에 의해 이슈화되는 모든 SQL은 템플릿 인스턴스의 전체 경로를 포함한 클래스명에 관련된 카테고리에서 'DEBUG' 레벨에서 로그화된다(대개 JdbcTemplate이지만, JdbcTemplate 클래스의 사용자정의 하위클래스가 사용된다면 다를것이다.).
NamedParameterJdbcTemplate 클래스는 명명 파라미터(오직 전통적인 위치고정자('?') 인자를 사용하여 JDBC구문을 프로그램하는 것과는 대립되는)를 사용하여 JDBC구문을 프로그램하기 위한 지원을 추가한다. NamedParameterJdbcTemplate 클래스는 평범한 JdbcTemplate를 포장하고 포장된 JdbcTemplate로 위임한다. 이 부분은 NamedParameterJdbcTemplate 클래스가 JdbcTemplate 자체와 다른 영역만을 언급할것이다.
// some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
value내 명명 파라미터 표기의 사용은 'sql' 변수로 할당되고 관련된 값은 'namedParameters' 변수(MapSqlParameterSource 타입인)로 플러그인 된다.
당신이 원한다면, 명명 파라미터(그리고 관련된 값들)를 (아마도 좀더 친숙한)Map-기반 스타일을 사용하여 NamedParameterJdbcTemplate 인스턴스로 전달할수 있다. (나머지 메소드는 NamedParameterJdbcOperations에 의해 드러나고 NamedParameterJdbcTemplate 클래스에 의해 구현된다)
// some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
Map namedParameters = new HashMap();
namedParameters.put("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
NamedParameterJdbcTemplate에 관련된 다른 좋은 기능은 SqlParameterSource(같은 패키지내 존재하는) 인터페이스이다. 이미 코드조각(MapSqlParameterSource 클래스)을 처리하는 것중 하나로 이 인터페이스의 구현물 예제를 보았다. SqlParameterSource 전체는 NamedParameterJdbcTemplate를 위한 명명 파라미터 값의 근원으로 제공하는 것이다. MapSqlParameterSource 클래스는 가장 간단한 구현물이고 java.util.Map에 대한 어댑터이고 자체적으로 명백하게 사용한다.
다른, 좀더 흥미로운, SqlParameterSource 인터페이스의 구현물은 BeanPropertySqlParameterSource 클래스이다. 이 클래스는 임의의 JavaBean과 같은 객체를 포장하고 명명 파라미터 값의 근원처럼 포장된 객체의 프라퍼티를 사용한다. 예제는 이것을 좀더 명확하게 해줄것이다.
// some JavaBean-like class... 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... }
// some JDBC-backed DAO class... public int countOfActors(Actor exampleActor) { // notice how the named parameters match the properties of the above 'Actor' class String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource()); SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return template.queryForInt(sql, namedParameters); }
NamedParameterJdbcTemplate 클래스가 전통적인 JdbcTemplate 템플릿을 포장한다는 것을 기억하라. 포장된 JdbcTemplate 인스턴스에 접근할 필요가 있다면(JdbcTemplate 클래스에만 존재하는 몇가지 기능에 접근하는), JdbcOperations 인터페이스를 통해 포장된 JdbcTemplate에 접근하는 getJdbcOperations() 메소드를 사용할수 있다.
NamedParameterJdbcTemplate 클래스는 쓰레드에 안전하고 기대되는 사용 패턴은 작업마다 새로운 NamedParameterJdbcTemplate 인스턴스를 인스턴스화할 뿐 아니라 DataSource(Spring의 IoC기술을 사용한다면 Spring IoC컨테이너를 통해) 마다 하나의 NamedParameterJdbcTemplate 인스턴스를 간단히 설정하고 필요한 DAO에 대해 같은 인스턴스를 공유한다.
![]() | Note |
---|---|
이 클래스에 의해 제공되는 기능이 Java 5를 사용할때만 사용가능하다는 것을 알라. |
SimpleJdbcTemplate 클래스는 Java 5언어의 기능적인 장점을 가지는 전통적인 Spring JdbcTemplate에 대한 래퍼이다. SimpleJdbcTemplate 클래스가 Java 5의 구문상 유연성과 같은 기능을 위한 선물이다. 하지만 Java 5에서 개발하는 사람이 JDK이전 버전에서 개발하는 것으로 돌아간다. 이러한 구문상 유연성과 같은 기능이 멋지다.
구문상 유연성 영역에서 SimpleJdbcTemplate 클래스의 값-추가는 'before and after' 예제로 가장 잘 보여진다. 다음의 코드 조각은 전통적인 Spring JdbcTemplate 을 사용하여 몇가지 데이터 접근 코드를 보여준다.
// classic JdbcTemplate-style... public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; RowMapper mapper = new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); actor.setId(rs.getLong(Long.valueOf(rs.getLong("id")))); actor.setFirstName(rs.getString("first_name")); actor.setLastName(rs.getString("last_name")); return actor; } }; // normally this would be dependency injected of course... JdbcTemplate jdbcTemplate = new JdbcTemplate(this.getDataSource()); // notice the cast, and the wrapping up of the 'id' argument // in an array, and the boxing of the 'id' argument as a reference type return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)}); }
이것은 SimpleJdbcTemplate를 사용할때만 같은 메소드이다. '좀더 명백한' 코드를 어떻게 하는지 알라.
// SimpleJdbcTemplate-style... public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper<Actor>() { // notice the return type with respect to Java 5 covariant return types public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); actor.setId(rs.getLong("id")); actor.setFirstName(rs.getString("first_name")); actor.setLastName(rs.getString("last_name")); return actor; } }; // again, normally this would be dependency injected of course... SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(this.getDataSource()); return simpleJdbcTemplate.queryForObject(sql, mapper, id); }
데이터베이스로부터 데이터작업을 수행하기 위해서 우리는 데이터베이스로 부터 Connection을 얻을 필요가 있다. Spring은 DataSource을 통해서 이것을 수행한다. DataSource는 JDBC스펙의 일부이고 생성된 connection공장처럼 볼수 있다. 이것은 컨테이너또는 프레임워크에게 높은 성능의 Connection pooling와 애플리케이션 코드로 부터 트랜잭션 관리 부분을 숨길수 있도록 한다. 개발자의 입장에서 당신은 데이터베이스에 연결하는 상세내역을 알 필요가 없다. 이것은 데이터베이스를 셋팅하는 관리자의 책임이다. 당신은 당신의 코드를 개발하고 테스트하는 동안 두가지 책임을 모두 수행해야 할지도 모르지만 어떻게 데이터소스가 설정이 되는지에 대해서 알필요는 없다.
Spring의 JDBC레이어를 사용할때 당신은 JNDI로 부터 데이터소스를 얻거나 Spring배포내에서 제공되어 있는 구현물로 자신만의 설정을 할수도 있다. 후자의 경우 웹 컨테이너밖에서 단위테스팅을 능숙하게 할수 있게 한다. 우리는 나중에 다루어질 여러개의 추가적인 구현물이 있지만 이 섹션에서 DriverManagerDataSource 을 사용할것이다. DriverManagerDataSource 는 당신이 JDBC Connection을 얻었을때 작업하기 위해서 사용되어진 것과 같은 방식으로 작동한다. 당신은 DriverManager가 드라이버클래스를 로드할수 있도록 JDBC드라이버의 패키지를 포함한 전체이름을 명시해야 한다. 그 다음 당신은 JDBC드라이버사이에 변경이 되는 url을 제공해야만 한다. 여기서 정확한 값을 위해서 당신 드라이버의 문서를 찾아보아야 한다. 마지막으로 당신은 데이터베이스 연결에 사용되는 사용자명과 비밀번호를 제공해야만 한다. 이것은 DriverManagerDataSource:을 설정하기 위한 방법을 보여주는 예제이다.
DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); dataSource.setUrl("jdbc:hsqldb:hsql://localhost:"); dataSource.setUsername("sa"); dataSource.setPassword("");
SQLExceptionTranslator은 SQLException과 Spring의 데이터접근 전략에 얽매이지 않는 org.springframework.dao.DataAccessException사이에 해석할수 있는 클래스에 의해 구현될수 있는 인터페이스이다.
구현은 좀더 정확성을 위해서 일반적(예를 들면, JDBC를 위해 SQLState코드를 사용하는)이거나 소유(예를 들면, Oracle에러코드를 사용하는)될수 있다.
SQLErrorCodeSQLExceptionTranslator는 초기설정에 의해서 사용이 되는 SQLExceptionTranslator의 구현이다. 이 구현은 업체코드를 명시하는데 사용한다. SQLState 구현보다 좀더 정확하지만 업체에 종속적이다. 에러코드 해석은 SQLErrorCodes이라고 불리는 자바빈 타입의 코드에 기초를 둔다. 이 클래스는 "sql-error-codes.xml"라는 이름의 설정파일의 내용에 기초를 두는 SQLErrorCodes를 생성하기 위한 공장같은 이름의 SQLErrorCodesFactory에 의해서 생성되고 활성화된다. 이 파일은 업체코드에 의해 활성화되고 DatabaseMetaData로 부터 얻어진 DatabaseProductName에 기초를 둔다.
SQLErrorCodeSQLExceptionTranslator는 다음의 일치규칙(matching rules)을 적용한다.
어느 하위 클래스에 의해서 구현되는 사용자정의해석(custom translation). 이 클래스는 이 규칙을 적용하지 않는 경우에 견고해지고 스스로 사용되는 것에 주의하라.
에러코드일치를 적용하라. 에러코드는 초기설정에 의해서 SQLErrorCodesFactory으로 부터 얻어진다. 이것은 클래스패스로부터 에러코드를 찾고 데이터베이스 메타데이터로부터 데이터베이스 이름으로 부터 키를 입력한다.
fallback해석자를 사용하라. SQLStateSQLExceptionTranslator는 초기설정의 fallback해석자이다.
SQLErrorCodeSQLExceptionTranslator는 다음과 같은 방법으로 확장할수 있다.
public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) { if (sqlex.getErrorCode() == -12345) { return new DeadlockLoserDataAccessException(task, sqlex); } return null; } }
이 예제에서 에러코드 '-12345'는 해석되었거나 초기설정 해석자구현에 의해서 해석되기 위해서 남겨진 다른 에러코드이다. 이 사용자정의 해석자(custom translator)를 사용하기 위해서 setExceptionTranslator 메소드를 사용하고 이 해석자가 필요한 데이터 접근 처리를 위해 JdbcTemplate를 사용하기 위한 JdbcTemplate 으로 값을 넘길필요가 있다. 여기에 사용자정의 해석자가 어떻게 사용되는지에 대한 예제가 있다.
// create a JdbcTemplate and set data source JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(dataSource); // create a custom translator and set the DataSource for the default translation lookup MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator(); tr.setDataSource(dataSource); jt.setExceptionTranslator(tr); // use the JdbcTemplate for this SqlUpdate SqlUpdate su = new SqlUpdate(); su.setJdbcTemplate(jt); su.setSql("update orders set shipping_charge = shipping_charge * 1.05"); su.compile(); su.update();
이 사용자정의 해석자는 sql-error-codes.xml내의 에러코드를 찾기위해 디폴트 해석자를 원하기 때문에 데이터소스를 전달했다.
SQL문을 실행하기 위해 필요한 작은 코드가 있다. 당신이 필요한 모든것은 DataSource 와 JdbcTemplate 이다. 당신이 그것을 가졌을때 당신은 JdbcTemplate 과 함께 제공되는 많은 편리한 메소드를 사용할수 있다. 여기에 작지만 새로운 테이블을 생성하는 모든 기능적인 클래스를 위해 필요한 짧은 예제를 보여준다.
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAStatement { private JdbcTemplate jt; private DataSource dataSource; public void doExecute() { jt = new JdbcTemplate(dataSource); jt.execute("create table mytable (id integer, name varchar(100))"); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
메소드를 수행하는 것에 추가적으로 여기엔 많은 수의 쿼리 메소드가 있다. 그 메소드의 몇몇은 하나의 값을 반환하는 쿼리를 위해 사용되는 경향이 있다. 아마도 당신은 하나의 레코드로 부터 카운트나 특정값을 가져오길 원할지도 모른다. 만약 그 경우라면 당신은 queryForInt(..), queryForLong 또는 queryForObject(..)를 사용할수 있다. 후자는 반환된 JDBC타입을 인자처럼 전달된 자바 클래스로 변환할 것이다. 만약 타입변환이 유효하지 않다면 InvalidDataAccessApiUsageException를 던질것이다. 여기에 int를 위한것과 String을 위한 두개의 쿼리 메소드를 포함하는 예제가 있다.
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class RunAQuery { private JdbcTemplate jt; private DataSource dataSource; public int getCount() { jt = new JdbcTemplate(dataSource); int count = jt.queryForInt("select count(*) from mytable"); return count; } public String getName() { jt = new JdbcTemplate(dataSource); String name = (String) jt.queryForObject("select name from mytable", String.class); return name; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
하나의 결과물을 위한 쿼리 메소드에 추가적으로 쿼리가 반환하는 각각의 레코드를 가지는 List를 반환하는 다양한 메소드가 있다. 가장 일반적인 하나는 각각의 레코드를 위한 칼럼값을 표현하는 Map 형태의 List를 반환하는 queryForList이다. 만약 우리가 모든 레코드의 리스트를 가져오는 메소드를 추가한다면 다음과 같을것이다.
public List getList() { jt = new JdbcTemplate(dataSource); List rows = jt.queryForList("select * from mytable"); return rows; }
반환되는 리스트는 이것처럼 보일것이다.
[{name=Bob, id=1}, {name=Mary, id=2}]
당신이 사용할수 있는 많은 update메소드가 있다. 나는 어떠한 기본키를 위한 칼럼을 수정하는 예제를 아래에서 보라. 이 예제에서 row 파라미터를 위한 위치고정자(place holders)를 가진 SQL문을 사용한다. 대부분의 쿼리및 update메소드는 이 기능을 가진다. 파라미터 값은 객체의 배열내에 전달된다.
import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAnUpdate { private JdbcTemplate jt; private DataSource dataSource; public void setName(int id, String name) { jt = new JdbcTemplate(dataSource); jt.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)}); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } }
DataSourceUtils 클래스는 필요하다면 JNDI로 부터 connection을 얻거나 connection을 닫기 위한 static메소드를 제공하는 편리하고 강력한 헬퍼 클래스이다. 이것은 쓰레드 범위의 connection을 위한 지원 예를 들면 DataSourceTransactionManager를 사용한다.
주의 : getDataSourceFromJndi메소드는 bean factory나 애플리케이션 컨텍스트를 사용하지 않는 애플리페이션을 목표로 한다. 이것은 factory내 당신의 빈즈나 JdbcTemplate인스턴스를 먼저 설정하는것을 선호한다. JndiObjectFactoryBean는 JNDI로 부터 DataSource를 꺼내고 다른 빈즈에게 DataSource bean참조를 주는데 사용될수 있다. 다른 DataSource로의 교체는 설정의 문제이다. 당신은 non-JNDI의 DataSource를 가진 FactoryBean의 정의를 교체할수 있다.
SmartDataSource 인터페이스는 관계형 데이터베이스를 위한 connection을 제공할수 있는 클래스에 의해 구현되는 것이다. DataSource 인터페이스를 확장하는것은 주어진 작업후에 닫혀야만하는 connection인지 아닌지 쿼리하기 위해 사용하도록 허용한다. 이것은 때때로 우리가 connection을 재사용하길 원한다는 것을 안다면 효율성을 위해 유용할수 있다.
Spring의 DataSource 구현물을 위한 abstract 기본 클래스는 "시시함(uninteresting)"을 처리한다. 이것은 당신이 자신의 DataSource 구현물을 쓴다면 확장할 클래스이다.
SingleConnectionDataSource클래스는 사용후에 닫히질 않는 connection을 포장한 SmartDataSource 의 구현물이다. 분명히 이것은 다중 쓰레드 성능은 아니다.
만약 퍼시스턴스 툴을 사용할때처럼, 라이언트코드가 풀링된 connection의 소비내 close를 호출한다면, suppressClose 을 true로 설정하라. 이것은 물리적 connection대신에 close-억제 프록시를 반환할것이다. 당신은 이것을 고유의 Oracle connection이나 다른 어떠한 것처럼 형변환할수 없다는것을 알라.
이것은 기본적으로 테스트 클래스이다. 예를 들면 이것은 간단한 JNDI환경과 함께 연결되어 애플리케이션 서버 외부에서 코드를 쉽게 테스팅 가능하도록 한다. DriverManagerDataSource와는 대조적으로 이것은 언제나 물리적 connection생성 초과를 피하고 같은 connection을 재사용한다.
DriverManagerDataSource 클래스는 빈 프라퍼티를 통해 명백한 예전 JDBC드라이버를 설정하고 언제나 새로운 connection을 반환하는 SmartDataSource 인터페이스의 구현물이다.
이것은 각각의 ApplicationContext내 DataSource bean이나 간단한 JNDI환경과의 결합처럼 테스트나 J2EE컨테이너 외부의 독립적인 환경을 위해 잠재적으로 유용하다. 풀 성격의 Connection.close()호출은 간단하게 connection을 닫을 것이다. 그래서 어떠한 DataSource-인식 퍼시스턴스코드도 작동할것이다. 어쨌든, commons-dbcp와 같은 자바빈 스타일의 connection pool을 사용하는 것은 테스트 환경에서조차 쉽다. 이것은 DriverManagerDataSource 에 비해 connection pool을 사용하는 것을 언제나 선호한다.
TransactionAwareDataSourceProxy는 Spring-관리 트랜잭션의 인지을 추가하기 위한 대상 DataSource를 포장하는 대상 DataSource를 위한 프록시이다. 이 점에서, 이것은 J2EE서버가 제공하는 전통적인 JNDI DataSource와 유사하다.
![]() | Note |
---|---|
호출해야만 하는 코드가 존재하고 표준 JDBC DataSource인터페이스 구현물을 전달하는 것을 제외하고 이 클래스를 사용하는 것은 결코 필요하거나 바람직하지 않다. 이 경우, 이 코드가 여전히 사용가능하지만 Spring관리 트랜잭션에 관계한다. JdbcTemplate 이나 DataSourceUtils와 같은 resource관리를 위한 더 높은 레벨의 추상화를 사용하여 당신 자신만의 새로운 코드를 작성하는 것이 대개 선호된다. |
(좀더 상세한 정보를 위해 TransactionAwareDataSourceProxy Javadoc을 보라.)
DataSourceTransactionManager 클래스는 하나의 JDBC데이터 소스를 위한 PlatformTransactionManager구현물이다. 이것은 명시된 데이터 소스의 JDBC connection을 최근에 수행된 쓰레드에 바인드한다. 잠재적으로 데이터 소스당 하나의 쓰레드 connection을 허용한다.
애플리케이션 코드는 J2EE의 표준적인 DataSource.getConnection 대신에 DataSourceUtils.getConnection(DataSource)을 통해 JDBC connection을 가져와야만 한다. 이것은 어쨌든 추천된다. 그리고 이것은 체크된 SQLException 대신에 체크되지 않은 org.springframework.dao 예외를 던진다. JdbcTemplate 와 같은 모든 프레임워크 클래스는 절대적으로 이 전략을 사용한다. 만약 이 트랜잭션 관리자를 사용하지 않는다면 룩업 전략은 공통된 것과 같이 정확하게 작동한다.
DataSourceTransactionManager 클래스는 사용자정의 격리 레벨, 적절한 JDBC 구문 쿼리 타임아웃처럼 적용되는 타임아웃을 지원한다. 후자를 지원하기 위해 애플리케이션 코드는 JdbcTemplate 나 각각의 생성된 구문를 위한 DataSourceUtils.applyTransactionTimeout 메소드 호출을 사용해야만 한다.
이 구현물은 하나의 자원일 경우 JTA를 지원하기 위해 컨테이너를 요구하지 않는것처럼 JtaTransactionManager 대신에 사용될수 있다. 두가지 사항 사이의 전환은 설정상의 문제이다. 만약 당신이 요구되는 connection룩업 패턴을 고집한다면 JTA는 사용자 지정 격리 레벨을 지원하지 않는다는 것에 주의하라.!
org.springframework.jdbc.object패키지는 좀더 객체 지향적인 방법으로 데이터베이스에 접근하는 것을 허락하는 클래스를 포함한다. 당신은 쿼리를 수행하고 관계적인 칼럼 데이터를 비지니스 객체의 프라퍼티로 맵핑하는 비지니스 객체를 포함하는 리스트처럼 결과를 얻을수 있다. 당신은 또한 저장 프로시저와 update, delete그리고 insert 구문을 실행할수 있다.
![]() | Note |
---|---|
밑에서 언급된(StoredProcedure 클래스의 예외를 가진) 다양한 RDBMS작업 클래스가 종종 JdbcTemplate 호출에 대체될수 있는 몇몇 Spring개발자들간에 view가 있다. 종종 이것은 직접 JdbcTemplate의 메소드를 간단히 호출하는 DAO메소드를 사용하는 것과 유사하다. 아무리 view 더라도 스트레스가 될것이다. RDBMS 작업 클래스를 사용하여 측정가능한 값을 얻는데 자유롭다면, 언급된 클래스를 사용하는것도 자유롭게 느낄것이다. |
SqlQuery는 SQL궈리를 캡슐화하는 쓰레드에 안전한 객체를 재사용가능하다. 하위 클래스는 ResultSet를 반복하는 동안 결과를 저장할수 있는 객체를 제공하기 위해 newResultReader()메소드를 구현해야만 한다. SqlQuery는 MappingSqlQuery 하위클래스가 Java클래스에 대한 맵핑 row를 위한 좀더 많은 편리한 구현물을 제공하기 때문에 드물게 직접적으로 사용된다. SqlQuery을 확장하는 다른 구현물은 MappingSqlQueryWithParameters 와 UpdatableSqlQuery이다.
MappingSqlQuery는 명확한 하위 클래스가 JDBC ResultSet의 각각의 레코드를 객체로 변환하기 위한 추상메소드인 mapRow(ResultSet, int)를 구현함으로써 재사용가능한 쿼리이다.
모든 다양한 SqlQuery 구현물인, MappingSqlQuery는 매우 종종 사용되고 이것은 사용하기 가장 쉬운 것중 하나이다.
customer테이블의 데이터를 Customer의 인스턴스로 맵핑하는 사용자정의 쿼리의 예제를 아래에서 보라.
private class CustomerMappingQuery extends MappingSqlQuery { public CustomerMappingQuery(DataSource ds) { super(ds, "SELECT id, name FROM customer WHERE id = ?"); super.declareParameter(new SqlParameter("id", Types.INTEGER)); compile(); } public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Customer cust = new Customer(); cust.setId((Integer) rs.getObject("id")); cust.setName(rs.getString("name")); return cust; } }
우리는 오직 파라미터로 DataSource를 가지는 사용자정의 쿼리를 위한 생성자를 제공한다. 이 생성자에서 우리는 DataSource 와 이 쿼리를 위해 레코드를 가져오기 위해 수행되어야 하는 SQL을 가진 수퍼클래스의 생성자를 호출한다. 이 SQL은 수행되는 동안 전달될 어떠한 파라미터를 위한 위치자를 포함한 PreparedStatement를 생성하기 위해 사용될 것이다. 각각의 파라미터는 SqlParameter내에 전달될 declareParameter메소드를 사용해서 선언되어야 한다. SqlParameter 는 이름과 java.sql.Types내에 정의될 JDBC타입을 가진다. 모든 파라미터가 compile() 메소드를 호출해서 정의된 후에 statement는 준비되고 나중에 수행된다.
이 사용자 정의 쿼리가 초기화되고 수행되는 코드를 보자.
public Customer getCustomer(Integer id) { CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); Object[] parms = new Object[1]; parms[0] = id; List customers = custQry.execute(parms); if (customers.size() > 0) { return (Customer) customers.get(0); } else { return null; } }
예제내 메소드는 오직 파마리터로써 전달되는 id와 함께 customer를 가져온다. CustomerMappingQuery 클래스의 인스턴스를 생성한 후에 우리는 전달될 모든 파라미터를 포함할 객체의 배열을 생성한다. 이 경우에 오직 한개의 파라미터가 있고 이것은 Integer로 전달된다. 지금 우리는 파라미터의 배열을 사용해서 쿼리를 수행할 준비가 되었고 우리의 쿼리를 통해 반환되는 각각의 레코드를 위한 Customer 객체를 포함하는 List를 얻게된다. 이 경우에 적합한 경우 하나의 항목이 될것이다.
SqlUpdate 클래스는 SQL update를 캡슐화한다. 쿼리처럼, update객체는 재사용가능하다. 모든 RdbmsOperation클래스 처럼, update는 파라미터를 가지고 SQL내 정의된다.
이 클래스는 쿼리 객체의 execute()메소드와 유사한 많은 update()메소드를 제공한다.
이 클래스는 명백하다. 비록 이것이 하위 클래스(예를 들면 사용자 정의 update메소드를 추가하는)가 될수 있지만 이것은 SQL을 셋팅하고 파라미터를 선언함으로써 쉽게 파라미터화 될수 있다.
import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.SqlUpdate; public class UpdateCreditRating extends SqlUpdate { public UpdateCreditRating(DataSource ds) { setDataSource(ds); setSql("update customer set credit_rating = ? where id = ?"); declareParameter(new SqlParameter(Types.NUMERIC)); declareParameter(new SqlParameter(Types.NUMERIC)); compile(); } /** * @param id for the Customer to be updated * @param rating the new value for credit rating * @return number of rows updated */ public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); } }
StoredProcedure클래스는 RDBMS 저장 프로시저의 객체 추상화를 위한 수퍼클래스이다. 이 클래스는 추상적이고 execute(..)메소드들은 protected상태이다. 좀더 단단하게 타이핑된 하위 클래스를 통해 다른것보다 좀더 사용이 제한적이다.
상속된 sql프라퍼티는 RDBMS의 저장프로시저의 이름이 될것이다. JDBC 3.0은 명명 파라미터를 소개한다. 비록 이 클래스에 의해 제공되는 다른 특징이지만 여전히 JDBC 3.0에서 필요하다.
이것은 Oracle데이터베이스에서 사용되는 sysdate()함수를 호출하는 프로그램 예제이다. 저장 프로시저 기능을 사용하기 위해 당신은 StoredProcedure를 확장한 클래스를 생성해야만 한다. 여기엔 입력 파라미터가 없지만 SqlOutParameter 클래스를 사용한 date처럼 선언된 출력 파라미터는 있다. execute() 메소드는 key로써 파라미터이름을 사용한 각각의 선언된 출력 파라미터를 위한 항목을 가지는 map을 반환한다.
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;
public class TestStoredProcedure {
public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}
void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");
MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map results = sproc.execute();
printMap(results);
}
private class MyStoredProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
return execute(new HashMap());
}
}
private static void printMap(Map results) {
for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
}
두개의 출력 파라미터(Oracle 커서의 경우)를 가지는 StoredProcedure의 예제를 아래에서 보라.
import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map execute() {
// again, this sproc has no input parameters, so an empty Map is supplied...
return super.execute(new HashMap());
}
}
TitlesAndGenresStoredProcedure 생성자에서 사용된 declareParameter(..) 메소드의 오버로드된 형태가 RowMapper 구현물 인스턴스에 전달되는 방법을 노트하라. 이것은 존재하는 기능을 재사용하기 위해 편리하고 강력한 방법이다(두개의 RowMapper구현물을 위한 코드는 아래에서 제공된다).
첫번째로, ResultSet을 제공되는 ResultSet내 각각의 row를 위한 Title도메인 객체로 간단히 맵핑하는 TitleMapper클래스이다.
import com.foo.sprocs.domain.Title; import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; public final class TitleMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); title.setId(rs.getLong("id")); title.setName(rs.getString("name")); return title; } }
두번째로, ResultSet을 제공되는 ResultSet내 각각의 row를 위한 Genre도메인 객체로 간단히 맵핑하는 GenreMapper클래스이다.
import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; import com.foo.domain.Genre; public final class GenreMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(rs.getString("name")); } }
저장 프로시저(이를테면, 저장 프로시저는 RDBMS의 정의내 한개 또는 그 이상의 입력 파라미터를 가지는 것처럼 명시된다.)에 파라미터를 전달할 필요가 있다면, 수퍼클래스(타입화되지 않은)의 execute(Map parameters)(protected 접근을 하는)에 위임할 강력하게 타입화된 execute(..) 메소드를 코딩할것이다.
import oracle.jdbc.driver.OracleTypes; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.object.StoredProcedure; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; public class TitlesAfterDateStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "TitlesAfterDate"; private static final String CUTOFF_DATE_PARAM = "cutoffDate"; public TitlesAfterDateStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declaraParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE); declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper())); compile(); } public Map execute(Date cutoffDate) { Map inputs = new HashMap(); inputs.put(CUTOFF_DATE_PARAM, cutoffDate); return super.execute(inputs); } }
SqlFunction RDBMS작업 클래스는 결과의 하나의 레코드를 반환하는 쿼리를 위한 SQL "함수" 랩퍼를 캡슐화한다. 디폴트 행위는 int를 반환하는 것이지만 추가적인 반환 타입 파라미터를 가진 메소드를 사용해서 오버라이드 할수 있다. 이것은 JdbcTemplate의 queryForXxx 메소드를 사용하는것이 유사하다. SqlFunction이 가진 장점은 JdbcTemplate을 생성할 필요가 없다는 것이다. 이것은 상태(scenes)뒤에서 행해진다.
이 클래스는"select user()" 나 "select sysdate from dual" 와 같은 쿼리를 사용해서 하나의 결과를 반환하는 SQL함수들을 호출하는 것을 사용하는 경향이 있다. 이것은 좀더 복잡한 저장 프로시저를 호출하거나 저장 프로시저나 저장 함수를 호출하기 위한 CallableStatement를 사용하는 경향은 아니다. 이러한 타입의 처리를 위해 StoredProcedure 나 SqlCall을 사용하라.
SqlFunction은 하위 클래스에 알반적으로 필요하지 않은 명확한 클래스이다. 이 패키지를 사용하는 코드는 이 타입의 객체를 생성하고 SQL과 파라미터를 선언하고 함수를 수행하기 위해 반복적으로 선호하는 run메소드를 호출 할수 있다. 이것은 테이블로 부터 레코드의 카운트를 가져오는 예제이다.
public int countRows() { SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable"); sf.compile(); return sf.run(); }