org.springframework.jdbc.object 패키지에는 보다 객체 지향적인 방식으로 데이터베이스에 액세스할 수 있는 클래스가 포함되어 있습니다. 예를 들어, 쿼리를 실행하고 결과를 비즈니스 객체의 속성에 매핑된 관계형 열 데이터와 함께 비즈니스 객체가 포함된 목록으로 다시 가져올 수 있습니다. 저장된 프로시저를 실행하고 업데이트, 삭제 및 삽입 문을 실행할 수도 있습니다.
[Note]
많은 Spring 개발자들은 아래에 설명된 다양한 RDBMS 작업 클래스(StoredProcedure클래스 제외)가 종종 직접적인JdbcTemplate호출로 대체될 수 있다고 믿습니다.JdbcTemplate에서 직접 메서드를 호출하는 DAO 메서드를 작성하는 것이 더 간단한 경우가 많습니다(쿼리를 완전한 클래스로 캡슐화하는 것과 반대).그러나 RDBMS 작업 클래스를 사용하여 측정 가능한 가치를 얻고 있다면 이러한 클래스를 계속 사용해야 합니다.
SqlQuerySqlQuery는 SQL 쿼리를 캡슐화하는 재사용 가능하고 스레드로부터 안전한 클래스입니다. 하위 클래스는 쿼리 실행 중에 생성된 ResultSet을 반복하여 얻은 행당 하나의 객체를 생성할 수 있는 RowMapper 인스턴스를 제공하기 위해 newRowMapper(..) 메서드를 구현해야 합니다. MappingSqlQuery 하위 클래스는 행을 Java 클래스에 매핑하는 데 훨씬 더 편리한 구현을 제공하기 때문에 SqlQuery 클래스는 직접적으로 사용되는 경우가 거의 없습니다. SqlQuery를 확장하는(extend) 다른 구현으로는 MappingSqlQueryWithParameters 및 UpdatableSqlQuery가 있습니다.
MappingSqlQueryMappingSqlQuery는 제공된 ResultSet의 각 행을 지정된 type의 객체로 변환하기 위해 구체적인 하위 클래스가 추상 mapRow(..) 메서드를 구현해야 하는 재사용 가능한 쿼리입니다. 다음 예에서는 t_actor relation의 데이터를 Actor 클래스의 인스턴스에 매핑하는 사용자 지정 쿼리를 보여줍니다.
public class ActorMappingQuery extends MappingSqlQuery<Actor> {
public ActorMappingQuery(DataSource ds) {
super(ds, "select id, first_name, last_name from t_actor where id = ?");
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
@Override
protected Actor mapRow(ResultSet rs, int rowNumber) 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;
}
}
클래스는 Actor 타입으로 매개변수화된 MappingSqlQuery를 확장합니다. 이 고객 쿼리의 생성자는 DataSource를 유일한 매개변수로 사용합니다. 이 생성자에서는 이 쿼리에 대한 행을 검색하기 위해 실행해야 하는 DataSource 및 SQL을 사용하여 슈퍼클래스의 생성자를 호출할 수 있습니다. 이 SQL은 PreparedStatement를 생성하는 데 사용되므로 실행 중에 전달될 매개변수에 대한 자리 표시자가 포함될 수 있습니다. SqlParameter를 전달하는 declarParameter 메서드를 사용하여 각 매개 변수를 선언해야 합니다. SqlParameter는 이름과 java.sql.Types에 정의된 JDBC 타입을 사용합니다. 모든 매개변수를 정의한 후 명령문을 준비하고 나중에 실행할 수 있도록 compile() 메서드를 호출할 수 있습니다. 이 클래스는 컴파일된 후 스레드로부터 안전하므로 DAO가 초기화될 때 이러한 인스턴스가 생성되는 한, 인스턴스 변수로 유지되고 재사용될 수 있습니다. 다음 예제에서는 이러한 클래스를 정의하는 방법을 보여줍니다.
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}
이전 예제의 메소드는 유일한 매개변수로 전달된 id를 사용하여 고객을 검색합니다. 하나의 객체만 반환되기를 원하므로 id를 매개변수로 사용하여 findObject 편의 메서드를 호출합니다. 대신 개체 목록을 반환하고 추가 매개변수를 가져오는 쿼리가 있는 경우 가변 인수(varargs)로 전달된 매개변수 값의 배열을 가져오는 execute 메서드 중 하나를 사용합니다. 다음 예에서는 이러한 방법을 보여줍니다.
public List<Actor> searchForActors(int age, String namePattern) {
return actorSearchMappingQuery.execute(age, namePattern);
}
SqlUpdateSqlUpdate 클래스는 SQL 업데이트를 캡슐화합니다. 쿼리와 마찬가지로 업데이트 개체는 재사용이 가능하며 모든 RdbmsOperation 클래스와 마찬가지로 업데이트에는 매개 변수가 있을 수 있으며 SQL로 정의됩니다. 이 클래스는 쿼리 객체의 execute(..) 메소드와 유사한 여러 update(..) 메소드를 제공합니다. SqlUpdate 클래스는 구체(concrete)입니다. 예를 들어 사용자 정의 업데이트 방법을 추가하기 위해 하위 클래스로 분류할 수 있습니다. 그러나 SqlUpdate 클래스는 SQL을 설정하고 매개 변수를 선언하여 쉽게 매개 변수화할 수 있으므로 하위 클래스로 분류할 필요가 없습니다. 다음 예에서는 execute이라는 사용자 지정 업데이트 메서드를 만듭니다.
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("creditRating", Types.NUMERIC));
declareParameter(new SqlParameter("id", 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 execute(int id, int rating) {
return update(rating, id);
}
}
StoredProcedureStoredProcedure 클래스는 RDBMS 저장된 프로시저의 객체 추상화를 위한 abstract 슈퍼클래스입니다.
상속된 sql 속성(property)은 RDBMS에 있는 저장된 프로시저의 이름입니다.
StoredProcedure 클래스에 대한 매개 변수를 정의하려면 SqlParameter 또는 해당 하위 클래스 중 하나를 사용할 수 있습니다. 다음 코드 조각에 표시된 대로 생성자에서 매개변수 이름과 SQL 타입을 지정해야 합니다.
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SQL 타입은 java.sql.Types 상수를 사용하여 지정됩니다.
첫 번째 줄(SqlParameter 포함)에서는 IN 매개 변수를 선언합니다. 저장된 프로시저 호출과 SqlQuery 및 해당 하위 클래스를 사용하는 쿼리 모두에 IN 매개 변수를 사용할 수 있습니다(SqlQuery 이해에서 다룹니다).
두 번째 줄(SqlOutParameter 포함)은 저장 프로시저 호출에 사용할 out 매개 변수를 선언합니다. InOut 매개변수(프로시저에 in 값을 제공하고 값도 반환하는 매개변수)에 대한 SqlInOutParameter도 있습니다.
in 매개변수의 경우 이름 및 SQL 타입 외에도 숫자 데이터의 소수점 이하 자릿수 또는 사용자 정의 데이터베이스 타입의 타입 이름을 지정할 수 있습니다. out 매개변수의 경우 REF 커서에서 반환된 행 매핑을 처리하기 위해 RowMapper를 제공할 수 있습니다. 또 다른 옵션은 반환 값의 사용자 정의 처리를 정의할 수 있는 SqlReturnType을 지정하는 것입니다.
간단한 DAO의 다음 예는 Oracle 데이터베이스와 함께 제공되는 함수(sysdate())를 호출하기 위해 StoredProcedure를 사용합니다. 저장된 프로시저 기능을 사용하려면 StoredProcedure를 확장하는 클래스를 만들어야 합니다. 이 예에서 StoredProcedure 클래스는 내부 클래스입니다. 그러나 StoredProcedure를 재사용해야 하는 경우 이를 최상위 클래스로 선언할 수 있습니다. 이 예제에는 입력 매개 변수가 없지만 출력 매개 변수는 SqlOutParameter 클래스를 사용하여 날짜 형식으로 선언됩니다. execute() 메소드는 프로시저를 실행하고 결과 Map에서 반환된 날짜를 추출합니다. 결과 Map에는 매개변수 이름을 키로 사용하여 선언된 각 출력 매개변수(이 경우 하나만)에 대한 항목이 있습니다. 다음 목록은 사용자 정의 StoredProcedure 클래스를 보여줍니다.
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class StoredProcedureDao {
private GetSysdateProcedure getSysdate;
@Autowired
public void init(DataSource dataSource) {
this.getSysdate = new GetSysdateProcedure(dataSource);
}
public Date getSysdate() {
return getSysdate.execute();
}
private class GetSysdateProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public GetSysdateProcedure(DataSource dataSource) {
setDataSource(dataSource);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Date execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}
}
StoredProcedure의 다음 예에는 두 개의 출력 매개변수(이 경우 Oracle REF 커서)가 있습니다.
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
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<String, Object> execute() {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(new HashMap<String, Object>());
}
}
TitlesAndGenresStoredProcedure 생성자에서 사용된 declareParameter(..) 메소드의 오버로드된 변형이 어떻게 RowMapper 구현 인스턴스에 전달되는지 확인하세요. 이는 기존 기능을 재사용하는 매우 편리하고 강력한 방법입니다. 다음 두 예제는 두 개의 RowMapper 구현에 대한 코드를 제공합니다.
TitleMapper 클래스는 다음과 같이 제공된 ResultSet의 각 행에 대해 ResultSet를 Title 도메인 객체에 매핑합니다.
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;
public final class TitleMapper implements RowMapper<Title> {
public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}
GenreMapper 클래스는 다음과 같이 제공된 ResultSet의 각 행에 대해 ResultSet를 Genre 도메인 객체에 매핑합니다.
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;
public final class GenreMapper implements RowMapper<Genre> {
public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
RDBMS의 정의에 하나 이상의 입력 매개변수가 있는 저장된 프로시저에 매개변수를 전달하려면 슈퍼클래스의 타입이 없는 execution(Map) 메소드에 위임하는 강력한 유형의 execute(..) 메소드를 코딩하면 됩니다. 다음 예는 다음을 보여줍니다.
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;
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);
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}
public Map<String, Object> execute(Date cutoffDate) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}