Loading...
Spring Framework Reference Documentation 7.0.2의 Modeling JDBC Operations as Java Objects의 한국어 번역본입니다.
아래의 경우에 피드백에서 신고해주신다면 반영하겠습니다.
감사합니다 :)
org.springframework.jdbc.object package에는 데이터베이스에 더 객체 지향적인 방식으로
접근할 수 있게 해 주는 클래스들이 들어 있습니다. 예를 들어, 쿼리를 실행하고
그 결과를 관계형 컬럼 데이터가 비즈니스 객체의 프로퍼티에 매핑된 비즈니스 객체를
포함하는 리스트로 돌려받을 수 있습니다. 또한 저장 프로시저를 실행하고
update, delete, insert 문을 실행할 수도 있습니다.
많은 Spring 개발자들은 아래에 설명된 다양한 RDBMS operation 클래스들<br>(
StoredProcedure클래스는 예외)<br>이 종종 단순한JdbcTemplate호출로 대체될 수 있다고 믿습니다.<br>종종 쿼리를 완전한 클래스로 캡슐화하는 것보다 DAO 메서드를 작성해서<br>그 안에서 직접JdbcTemplate의 메서드를 호출하는 편이 더 단순합니다.<br>그러나 RDBMS operation 클래스들을 사용함으로써 측정 가능한 가치를 얻고 있다면,<br>계속해서 이 클래스들을 사용해야 합니다.
SqlQuerySqlQuery는 SQL 쿼리를 캡슐화하는 재사용 가능하고 thread-safe한 클래스입니다. 서브클래스는
쿼리 실행 중에 생성된 ResultSet을 순회하면서 얻은 각 row마다 하나의 객체를
생성할 수 있는 RowMapper 인스턴스를 제공하기 위해 newRowMapper(..) 메서드를
구현해야 합니다.
SqlQuery 클래스는 거의 직접 사용되지 않습니다. row를 Java 클래스에
매핑하기 위한 훨씬 더 편리한 구현을 제공하는 서브클래스인 MappingSqlQuery를
사용하기 때문입니다. SqlQuery를 확장한 다른 구현으로는
MappingSqlQueryWithParameters와 UpdatableSqlQuery가 있습니다.
MappingSqlQueryMappingSqlQuery는 재사용 가능한 쿼리로, 구체적인 서브클래스가 제공된 ResultSet의
각 row를 지정된 타입의 객체로 변환하기 위해 mapRow(..) abstract 메서드를
구현해야 합니다. 다음 예제는 t_actor relation의 데이터를 Actor 클래스의 인스턴스에
매핑하는 커스텀 쿼리를 보여 줍니다:
1public class ActorMappingQuery extends MappingSqlQuery<Actor> { 2 3 public ActorMappingQuery(DataSource ds) { 4 super(ds, "select id, first_name, last_name from t_actor where id = ?"); 5 declareParameter(new SqlParameter("id", Types.INTEGER)); 6 compile(); 7 } 8 9 @Override 10 protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException { 11 Actor actor = new Actor(); 12 actor.setId(rs.getLong("id")); 13 actor.setFirstName(rs.getString("first_name")); 14 actor.setLastName(rs.getString("last_name")); 15 return actor; 16 } 17}
1class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") { 2 3 init { 4 declareParameter(SqlParameter("id", Types.INTEGER)) 5 compile() 6 } 7 8 override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor( 9 rs.getLong("id"), 10 rs.getString("first_name"), 11 rs.getString("last_name") 12 ) 13}
이 클래스는 Actor 타입으로 parameterized된 MappingSqlQuery를 확장합니다. 이
커스텀 쿼리의 생성자는 유일한 파라미터로 DataSource를 받습니다. 이
생성자 안에서, 슈퍼클래스의 생성자를 DataSource와 이 쿼리의 row를
가져오기 위해 실행해야 할 SQL과 함께 호출할 수 있습니다.
이 SQL은
PreparedStatement를 생성하는 데 사용되므로, 실행 시 전달할 파라미터를 위한
placeholder를 포함할 수 있습니다. declareParameter 메서드를 사용해
SqlParameter를 전달함으로써 각 파라미터를 선언해야 합니다. SqlParameter는
이름과 java.sql.Types에 정의된 JDBC 타입을 받습니다. 모든 파라미터를 정의한 후에는
문을 준비하고 나중에 실행할 수 있도록 compile() 메서드를 호출할 수 있습니다.
이 클래스는 컴파일된 이후에는 thread-safe하므로, DAO가 초기화될 때 이 인스턴스들을 생성하기만 하면 인스턴스 변수로 유지하면서 재사용할 수 있습니다. 다음 예제는 이러한 클래스를 정의하는 방법을 보여 줍니다:
1private ActorMappingQuery actorMappingQuery; 2 3@Autowired 4public void setDataSource(DataSource dataSource) { 5 this.actorMappingQuery = new ActorMappingQuery(dataSource); 6} 7 8public Actor getActor(Long id) { 9 return actorMappingQuery.findObject(id); 10}
1private val actorMappingQuery = ActorMappingQuery(dataSource) 2 3fun getActor(id: Long) = actorMappingQuery.findObject(id)
앞선 예제의 메서드는 유일한 파라미터로 전달된 id를 가진 actor를 조회합니다.
하나의 객체만 반환되기를 원하므로, 파라미터로 id를 전달하면서 findObject
편의 메서드를 호출합니다.
만약 객체 리스트를 반환하고 추가 파라미터를 받는
쿼리였다면, varargs로 전달된 파라미터 값 배열을 받는 execute 메서드 중
하나를 사용했을 것입니다. 다음 예제는 이러한 메서드를 보여 줍니다:
1public List<Actor> searchForActors(int age, String namePattern) { 2 return actorSearchMappingQuery.execute(age, namePattern); 3}
1fun searchForActors(age: Int, namePattern: String) = 2 actorSearchMappingQuery.execute(age, namePattern)
SqlUpdateSqlUpdate 클래스는 SQL update를 캡슐화합니다. 쿼리와 마찬가지로, update 객체는
재사용 가능하며, 모든 RdbmsOperation 클래스와 마찬가지로 파라미터를 가질 수 있고
SQL로 정의됩니다. 이 클래스는 쿼리 객체의 execute(..) 메서드에 상응하는
여러 update(..) 메서드를 제공합니다.
SqlUpdate 클래스는 구체 클래스입니다.
예를 들어, 커스텀 update 메서드를 추가하기 위해 서브클래싱할 수 있습니다.
그러나 SQL을 설정하고 파라미터를 선언하는 방식으로 손쉽게 파라미터화할 수 있으므로
SqlUpdate 클래스를 반드시 서브클래싱해야 하는 것은 아닙니다.
다음 예제는 execute라는 이름의 커스텀 update 메서드를 생성합니다:
1import java.sql.Types; 2import javax.sql.DataSource; 3import org.springframework.jdbc.core.SqlParameter; 4import org.springframework.jdbc.object.SqlUpdate; 5 6public class UpdateCreditRating extends SqlUpdate { 7 8 public UpdateCreditRating(DataSource ds) { 9 setDataSource(ds); 10 setSql("update customer set credit_rating = ? where id = ?"); 11 declareParameter(new SqlParameter("creditRating", Types.NUMERIC)); 12 declareParameter(new SqlParameter("id", Types.NUMERIC)); 13 compile(); 14 } 15 16 /** 17 * @param id for the Customer to be updated 18 * @param rating the new value for credit rating 19 * @return number of rows updated 20 */ 21 public int execute(int id, int rating) { 22 return update(rating, id); 23 } 24}
1import java.sql.Types 2import javax.sql.DataSource 3import org.springframework.jdbc.core.SqlParameter 4import org.springframework.jdbc.object.SqlUpdate 5 6class UpdateCreditRating(ds: DataSource) : SqlUpdate() { 7 8 init { 9 setDataSource(ds) 10 sql = "update customer set credit_rating = ? where id = ?" 11 declareParameter(SqlParameter("creditRating", Types.NUMERIC)) 12 declareParameter(SqlParameter("id", Types.NUMERIC)) 13 compile() 14 } 15 16 /** 17 * @param id for the Customer to be updated 18 * @param rating the new value for credit rating 19 * @return number of rows updated 20 */ 21 fun execute(id: Int, rating: Int): Int { 22 return update(rating, id) 23 } 24}
StoredProcedureStoredProcedure 클래스는 RDBMS stored procedure의 객체 추상을 위한
abstract 슈퍼클래스입니다.
상속된 sql 프로퍼티는 RDBMS에서 stored procedure의 이름입니다.
StoredProcedure 클래스의 파라미터를 정의하려면 SqlParameter나 그 서브클래스 중
하나를 사용할 수 있습니다. 다음 코드 스니펫에서 보듯이, 생성자에서
파라미터 이름과 SQL 타입을 지정해야 합니다:
1new SqlParameter("in_id", Types.NUMERIC), 2new SqlOutParameter("out_first_name", Types.VARCHAR),
1SqlParameter("in_id", Types.NUMERIC), 2SqlOutParameter("out_first_name", Types.VARCHAR),
SQL 타입은 java.sql.Types 상수를 사용해 지정합니다.
첫 번째 line(SqlParameter가 있는)은 IN 파라미터를 선언합니다. IN 파라미터는
stored procedure 호출과 SqlQuery 및 그 서브클래스( Understanding SqlQuery에서
다룹니다)를 사용하는 쿼리 모두에 사용할 수 있습니다.
두 번째 line(SqlOutParameter가 있는)은 stored procedure 호출에서 사용할
out 파라미터를 선언합니다. InOut 파라미터( procedure에 in 값을 제공하면서
또한 값을 반환하는 파라미터)를 위한 SqlInOutParameter도 있습니다.
in 파라미터의 경우, 이름과 SQL 타입에 더해 numeric 데이터에 대한 scale이나
커스텀 데이터베이스 타입에 대한 타입 이름을 지정할 수 있습니다. out 파라미터의
경우, REF cursor에서 반환된 row의 매핑을 처리할 RowMapper를 제공할 수 있습니다.
또 다른 option은 반환 값의 customized handling을 정의할 수 있게 해 주는
SqlReturnType을 지정하는 것입니다.
다음 simple DAO 예제는 Oracle 데이터베이스에 기본으로 포함된 function(sysdate())을
호출하기 위해 StoredProcedure를 사용합니다. stored procedure 기능을 사용하려면
StoredProcedure를 확장하는 클래스를 생성해야 합니다. 이 예제에서 StoredProcedure
클래스는 inner 클래스입니다. 그러나 StoredProcedure를 재사용해야 한다면
top-level 클래스로 선언할 수 있습니다.
이 예제에는 input 파라미터가 없지만,
SqlOutParameter 클래스를 사용해 date 타입으로 선언된 output 파라미터가 있습니다.
execute() 메서드는 프로시저를 실행하고 결과 Map에서 반환된 date를 추출합니다.
결과 Map에는 각 선언된 output 파라미터(이 경우 하나뿐)에 대해 파라미터 이름을
key로 사용하는 entry가 있습니다. 다음 listing은 커스텀 StoredProcedure 클래스를
보여 줍니다:
1import java.sql.Types; 2import java.util.Date; 3import java.util.HashMap; 4import java.util.Map; 5import javax.sql.DataSource; 6import org.springframework.beans.factory.annotation.Autowired; 7import org.springframework.jdbc.core.SqlOutParameter; 8import org.springframework.jdbc.object.StoredProcedure; 9 10public class StoredProcedureDao { 11 12 private GetSysdateProcedure getSysdate; 13 14 @Autowired 15 public void init(DataSource dataSource) { 16 this.getSysdate = new GetSysdateProcedure(dataSource); 17 } 18 19 public Date getSysdate() { 20 return getSysdate.execute(); 21 } 22 23 private class GetSysdateProcedure extends StoredProcedure { 24 25 private static final String SQL = "sysdate"; 26 27 public GetSysdateProcedure(DataSource dataSource) { 28 setDataSource(dataSource); 29 setFunction(true); 30 setSql(SQL); 31 declareParameter(new SqlOutParameter("date", Types.DATE)); 32 compile(); 33 } 34 35 public Date execute() { 36 // the 'sysdate' sproc has no input parameters, so an empty Map is supplied... 37 Map<String, Object> results = execute(new HashMap<String, Object>()); 38 Date sysdate = (Date) results.get("date"); 39 return sysdate; 40 } 41 } 42 43}
1import java.sql.Types 2import java.util.Date 3import java.util.Map 4import javax.sql.DataSource 5import org.springframework.jdbc.core.SqlOutParameter 6import org.springframework.jdbc.object.StoredProcedure 7 8class StoredProcedureDao(dataSource: DataSource) { 9 10 private val SQL = "sysdate" 11 12 private val getSysdate = GetSysdateProcedure(dataSource) 13 14 val sysdate: Date 15 get() = getSysdate.execute() 16 17 private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() { 18 19 init { 20 setDataSource(dataSource) 21 isFunction = true 22 sql = SQL 23 declareParameter(SqlOutParameter("date", Types.DATE)) 24 compile() 25 } 26 27 fun execute(): Date { 28 // the 'sysdate' sproc has no input parameters, so an empty Map is supplied... 29 val results = execute(mutableMapOf<String, Any>()) 30 return results["date"] as Date 31 } 32 } 33}
다음 StoredProcedure 예제는 두 개의 output 파라미터(이 경우 Oracle REF cursor)를
가집니다:
1import java.util.HashMap; 2import java.util.Map; 3import javax.sql.DataSource; 4import oracle.jdbc.OracleTypes; 5import org.springframework.jdbc.core.SqlOutParameter; 6import org.springframework.jdbc.object.StoredProcedure; 7 8public class TitlesAndGenresStoredProcedure extends StoredProcedure { 9 10 private static final String SPROC_NAME = "AllTitlesAndGenres"; 11 12 public TitlesAndGenresStoredProcedure(DataSource dataSource) { 13 super(dataSource, SPROC_NAME); 14 declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper())); 15 declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper())); 16 compile(); 17 } 18 19 public Map<String, Object> execute() { 20 // again, this sproc has no input parameters, so an empty Map is supplied 21 return super.execute(new HashMap<String, Object>()); 22 } 23}
1import java.util.HashMap 2import javax.sql.DataSource 3import oracle.jdbc.OracleTypes 4import org.springframework.jdbc.core.SqlOutParameter 5import org.springframework.jdbc.object.StoredProcedure 6 7class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) { 8 9 companion object { 10 private const val SPROC_NAME = "AllTitlesAndGenres" 11 } 12 13 init { 14 declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper())) 15 declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper())) 16 compile() 17 } 18 19 fun execute(): Map<String, Any> { 20 // again, this sproc has no input parameters, so an empty Map is supplied 21 return super.execute(HashMap<String, Any>()) 22 } 23}
TitlesAndGenresStoredProcedure 생성자에서 사용된 declareParameter(..) 메서드의
overloaded variant가 RowMapper 구현 인스턴스를 전달받는다는 점에 주목하십시오.
이는 기존 기능을 재사용하는 매우 편리하고 강력한 방법입니다. 다음 두 예제는
두 개의 RowMapper 구현에 대한 코드를 제공합니다.
TitleMapper 클래스는 제공된 ResultSet의 각 row에 대해 ResultSet을 Title
도메인 객체로 매핑합니다:
1import java.sql.ResultSet; 2import java.sql.SQLException; 3import com.foo.domain.Title; 4import org.springframework.jdbc.core.RowMapper; 5 6public final class TitleMapper implements RowMapper<Title> { 7 8 public Title mapRow(ResultSet rs, int rowNum) throws SQLException { 9 Title title = new Title(); 10 title.setId(rs.getLong("id")); 11 title.setName(rs.getString("name")); 12 return title; 13 } 14}
1import java.sql.ResultSet 2import com.foo.domain.Title 3import org.springframework.jdbc.core.RowMapper 4 5class TitleMapper : RowMapper<Title> { 6 7 override fun mapRow(rs: ResultSet, rowNum: Int) = 8 Title(rs.getLong("id"), rs.getString("name")) 9}
GenreMapper 클래스는 제공된 ResultSet의 각 row에 대해 ResultSet을 Genre
도메인 객체로 매핑합니다:
1import java.sql.ResultSet; 2import java.sql.SQLException; 3import com.foo.domain.Genre; 4import org.springframework.jdbc.core.RowMapper; 5 6public final class GenreMapper implements RowMapper<Genre> { 7 8 public Genre mapRow(ResultSet rs, int rowNum) throws SQLException { 9 return new Genre(rs.getString("name")); 10 } 11}
1import java.sql.ResultSet 2import com.foo.domain.Genre 3import org.springframework.jdbc.core.RowMapper 4 5class GenreMapper : RowMapper<Genre> { 6 7 override fun mapRow(rs: ResultSet, rowNum: Int): Genre { 8 return Genre(rs.getString("name")) 9 } 10}
RDBMS 정의에서 하나 이상의 input 파라미터를 가진 stored procedure에 파라미터를
전달하려면, 다음 예제에서 보듯이 슈퍼클래스의 untyped execute(Map) 메서드에
위임하는 강한 타입의 execute(..) 메서드를 작성할 수 있습니다:
1import java.sql.Types; 2import java.util.Date; 3import java.util.HashMap; 4import java.util.Map; 5import javax.sql.DataSource; 6import oracle.jdbc.OracleTypes; 7import org.springframework.jdbc.core.SqlOutParameter; 8import org.springframework.jdbc.core.SqlParameter; 9import org.springframework.jdbc.object.StoredProcedure; 10 11public class TitlesAfterDateStoredProcedure extends StoredProcedure { 12 13 private static final String SPROC_NAME = "TitlesAfterDate"; 14 private static final String CUTOFF_DATE_PARAM = "cutoffDate"; 15 16 public TitlesAfterDateStoredProcedure(DataSource dataSource) { 17 super(dataSource, SPROC_NAME); 18 declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE)); 19 declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper())); 20 compile(); 21 } 22 23 public Map<String, Object> execute(Date cutoffDate) { 24 Map<String, Object> inputs = new HashMap<String, Object>(); 25 inputs.put(CUTOFF_DATE_PARAM, cutoffDate); 26 return super.execute(inputs); 27 } 28}
1import java.sql.Types 2import java.util.Date 3import javax.sql.DataSource 4import oracle.jdbc.OracleTypes 5import org.springframework.jdbc.core.SqlOutParameter 6import org.springframework.jdbc.core.SqlParameter 7import org.springframework.jdbc.object.StoredProcedure 8 9class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) { 10 11 companion object { 12 private const val SPROC_NAME = "TitlesAfterDate" 13 private const val CUTOFF_DATE_PARAM = "cutoffDate" 14 } 15 16 init { 17 declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE)) 18 declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper())) 19 compile() 20 } 21 22 fun execute(cutoffDate: Date) = super.execute( 23 mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
Simplifying JDBC Operations with the SimpleJdbc Classes
Common Problems with Parameter and Data Value Handling