JDBC 操作を Java オブジェクトとしてモデル化する
org.springframework.jdbc.object
パッケージには、よりオブジェクト指向の方法でデータベースにアクセスできるクラスが含まれています。例として、クエリを実行して、ビジネスオブジェクトのプロパティにマッピングされたリレーショナル列データを含むビジネスオブジェクトを含むリストとして結果を取得できます。ストアドプロシージャを実行し、更新、削除、挿入ステートメントを実行することもできます。
多くの Spring 開発者は、以下で説明するさまざまな RDBMS 操作クラス( ただし、RDBMS 操作クラスを使用して測定可能な値を取得している場合は、これらのクラスを引き続き使用する必要があります。 |
SqlQuery
を理解する
SqlQuery
は、SQL クエリをカプセル化する再利用可能なスレッドセーフクラスです。サブクラスは、newRowMapper(..)
メソッドを実装して、クエリの実行中に作成された ResultSet
を反復処理して取得した行ごとに 1 つのオブジェクトを作成できる RowMapper
インスタンスを提供する必要があります。MappingSqlQuery
サブクラスは、行を Java クラスにマッピングするためのはるかに便利な実装を提供するため、SqlQuery
クラスが直接使用されることはほとんどありません。SqlQuery
を継承する他の実装は、MappingSqlQueryWithParameters
および UpdatableSqlQuery
です。
MappingSqlQuery
を使用する
MappingSqlQuery
は再利用可能なクエリで、具象サブクラスは抽象 mapRow(..)
メソッドを実装して、指定された ResultSet
の各行を指定された型のオブジェクトに変換する必要があります。次の例は、t_actor
リレーションのデータを Actor
クラスのインスタンスにマッピングするカスタムクエリを示しています。
Java
Kotlin
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;
}
}
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {
init {
declareParameter(SqlParameter("id", Types.INTEGER))
compile()
}
override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")
)
}
このクラスは、Actor
型でパラメーター化された MappingSqlQuery
を継承します。この顧客クエリのコンストラクターは、DataSource
を唯一のパラメーターとして受け取ります。このコンストラクターでは、DataSource
と、このクエリの行を取得するために実行する必要がある SQL を使用して、スーパークラスのコンストラクターを呼び出すことができます。この SQL は PreparedStatement
を作成するために使用されるため、実行中に渡されるパラメーターのプレースホルダーを含めることができます。SqlParameter
に渡す declareParameter
メソッドを使用して、各パラメーターを宣言する必要があります。SqlParameter
は、java.sql.Types
で定義されている名前と JDBC 型を受け取ります。すべてのパラメーターを定義した後、ステートメントを準備して後で実行できるように、compile()
メソッドを呼び出すことができます。このクラスはコンパイル後にスレッドセーフであるため、DAO の初期化時にこれらのインスタンスが作成される限り、インスタンス変数として保持して再利用できます。次の例は、そのようなクラスを定義する方法を示しています。
Java
Kotlin
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Actor getActor(Long id) {
return actorMappingQuery.findObject(id);
}
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getActor(id: Long) = actorMappingQuery.findObject(id)
前述の例のメソッドは、唯一のパラメーターとして渡された id
を使用してアクターを取得します。返されるオブジェクトを 1 つだけに限定したいため、パラメーターとして id
を使用して findObject
コンビニエンスメソッドを呼び出します。代わりに、オブジェクトのリストを返し、追加のパラメーターを取るクエリがある場合は、可変引数として渡されたパラメーター値の配列を取る execute
メソッドの 1 つを使用します。次の例は、このようなメソッドを示しています。
Java
Kotlin
public List<Actor> searchForActors(int age, String namePattern) {
return actorSearchMappingQuery.execute(age, namePattern);
}
fun searchForActors(age: Int, namePattern: String) =
actorSearchMappingQuery.execute(age, namePattern)
SqlUpdate
を使用する
SqlUpdate
クラスは、SQL 更新をカプセル化します。クエリと同様に、更新オブジェクトは再利用可能であり、すべての RdbmsOperation
クラスと同様に、更新にはパラメーターを含めることができ、SQL で定義されます。このクラスは、クエリオブジェクトの execute(..)
メソッドに類似した多くの update(..)
メソッドを提供します。SqlUpdate
クラスは具体的です。たとえば、カスタム更新メソッドを追加するために、サブクラス化できます。ただし、SqlUpdate
クラスをサブクラス化する必要はありません。これは、SQL の設定とパラメーターの宣言によって簡単にパラメーター化できるためです。次の例では、execute
という名前のカスタム更新メソッドを作成します。
Java
Kotlin
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);
}
}
import java.sql.Types
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object
.SqlUpdate
class UpdateCreditRating(ds: DataSource) : SqlUpdate() {
init {
setDataSource(ds)
sql = "update customer set credit_rating = ? where id = ?"
declareParameter(SqlParameter("creditRating", Types.NUMERIC))
declareParameter(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
*/
fun execute(id: Int, rating: Int): Int {
return update(rating, id)
}
}
StoredProcedure
を使用する
StoredProcedure
クラスは、RDBMS ストアードプロシージャーのオブジェクト抽象化のための abstract
スーパークラスです。
継承された sql
プロパティは、RDBMS のストアドプロシージャの名前です。
StoredProcedure
クラスのパラメーターを定義するには、SqlParameter
またはそのサブクラスの 1 つを使用できます。次のコードスニペットに示すように、コンストラクターでパラメーター名と SQL 型を指定する必要があります。
Java
Kotlin
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SQL 型は、java.sql.Types
定数を使用して指定されます。
最初の行(SqlParameter
を使用)は、IN パラメーターを宣言しています。IN パラメーターは、ストアドプロシージャコールと、SqlQuery
およびそのサブクラス(SqlQuery
を理解するでカバー)を使用したクエリの両方に使用できます。
2 行目(SqlOutParameter
を使用)は、ストアドプロシージャコールで使用される out
パラメーターを宣言しています。InOut
パラメーター(プロシージャに in
値を提供し、値を返すパラメーター)の SqlInOutParameter
もあります。
in
パラメーターの場合、名前と SQL 型に加えて、数値データのスケールまたはカスタムデータベース型の型名を指定できます。out
パラメーターの場合、RowMapper
を提供して、REF
カーソルから返された行のマッピングを処理できます。別のオプションは、戻り値のカスタマイズされた処理を定義できる SqlReturnType
を指定することです。
次の単純な DAO の例では、StoredProcedure
を使用して、Oracle データベースに付属している関数(sysdate()
)を呼び出します。ストアドプロシージャ機能を使用するには、StoredProcedure
を継承するクラスを作成する必要があります。この例では、StoredProcedure
クラスは内部クラスです。ただし、StoredProcedure
を再利用する必要がある場合は、最上位クラスとして宣言できます。この例には入力パラメーターはありませんが、SqlOutParameter
クラスを使用して出力パラメーターが日付型として宣言されています。execute()
メソッドはプロシージャを実行し、結果 Map
から返された日付を抽出します。結果 Map
には、パラメーター名をキーとして使用して、宣言された各出力パラメーター(この場合は 1 つのみ)のエントリがあります。次のリストは、カスタム StoredProcedure クラスを示しています。
Java
Kotlin
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;
}
}
}
import java.sql.Types
import java.util.Date
import java.util.Map
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure
class StoredProcedureDao(dataSource: DataSource) {
private val SQL = "sysdate"
private val getSysdate = GetSysdateProcedure(dataSource)
val sysdate: Date
get() = getSysdate.execute()
private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {
init {
setDataSource(dataSource)
isFunction = true
sql = SQL
declareParameter(SqlOutParameter("date", Types.DATE))
compile()
}
fun execute(): Date {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
val results = execute(mutableMapOf<String, Any>())
return results["date"] as Date
}
}
}
次の StoredProcedure
の例には、2 つの出力パラメーターがあります(この場合、Oracle REF カーソル)。
Java
Kotlin
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>());
}
}
import java.util.HashMap
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object
.StoredProcedure
class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "AllTitlesAndGenres"
}
init {
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
compile()
}
fun execute(): Map<String, Any> {
// again, this sproc has no input parameters, so an empty Map is supplied
return super.execute(HashMap<String, Any>())
}
}
TitlesAndGenresStoredProcedure
コンストラクターで使用されていた declareParameter(..)
メソッドのオーバーロードされたバリアントが RowMapper
実装インスタンスに渡される方法に注意してください。これは、既存の機能を再利用するための非常に便利で強力な方法です。次の 2 つの例は、2 つの RowMapper
実装のコードを提供します。
TitleMapper
クラスは、次のように、指定された ResultSet
の各行の ResultSet
を Title
ドメインオブジェクトにマップします。
Java
Kotlin
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;
}
}
import java.sql.ResultSet
import com.foo.domain.Title
import org.springframework.jdbc.core.RowMapper
class TitleMapper : RowMapper<Title> {
override fun mapRow(rs: ResultSet, rowNum: Int) =
Title(rs.getLong("id"), rs.getString("name"))
}
GenreMapper
クラスは、次のように、指定された ResultSet
の各行の ResultSet
を Genre
ドメインオブジェクトにマップします。
Java
Kotlin
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"));
}
}
import java.sql.ResultSet
import com.foo.domain.Genre
import org.springframework.jdbc.core.RowMapper
class GenreMapper : RowMapper<Genre> {
override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
return Genre(rs.getString("name"))
}
}
RDBMS の定義に 1 つ以上の入力パラメーターがあるストアドプロシージャにパラメーターを渡すには、次の例に示すように、スーパークラスの型なし execute(Map)
メソッドに委譲する厳密に型指定された execute(..)
メソッドをコーディングできます。
Java
Kotlin
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);
}
}
import java.sql.Types
import java.util.Date
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
class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "TitlesAfterDate"
private const val CUTOFF_DATE_PARAM = "cutoffDate"
}
init {
declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
compile()
}
fun execute(cutoffDate: Date) = super.execute(
mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
}