The org.springframework.jdbc.object package contains classes that allow you to access the database in a more object-oriented way. For example, you can run queries and get the results as a list containing business objects with relational column data mapped to the properties of the business object. You can also run stored procedures and execute update, delete, and insert statements.

Many Spring developers believe that the various classes of RDBMS operations described below (except for the StoredProcedure class), most often can be replaced direct calls to JdbcTemplate. It is often easier to write a DAO method that calls a method from the JdbcTemplate directly (as opposed to encapsulating the request in a full-fledged class).

However, if you find it significantly useful to use RDBMS operation classes, you should continue to use these classes.

Understanding SqlQuery

SqlQuery is a reusable, thread-safe class , which encapsulates the SQL query. Subclasses must implement the newRowMapper(..) method to provide an instance of a RowMapper that can create one object for each row resulting from traversing the ResultSet. which is created during query execution. The SqlQuery class is rarely used directly because the MappingSqlQuery subclass provides a much more convenient implementation for mapping strings to Java classes. Other implementations that extend SqlQuery are MappingSqlQueryWithParameters and UpdatableSqlQuery.

Using MappingSqlQuery

MappingSqlQuery is a reusable query in which concrete subclasses must implement the abstract mapRow(..) method to map each row of the provided ResultSet into an object of the specified type. The following example shows a custom query that maps data from the t_actor relation to an instance of the Actor class:

Java
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;
    }
}
Kotlin
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")
    )
}

The class extends MappingSqlQuery, parameterized by the Actor type. The constructor for this custom request takes DataSource as its only parameter. In this constructor, you can call the superclass constructor with DataSource and the SQL that must be executed to retrieve the rows for this query. This SQL is used to create a PreparedStatement, so it can contain placeholders for any parameters that will be passed at runtime. You must declare each parameter using the declareParameter method, passing it a SqlParameter. SqlParameter takes the name and JDBC type defined in java.sql.Types. Once all the parameters have been defined, you can call the compile() method to prepare and subsequently execute the statement. Once compiled, this class is thread safe, so as long as these instances are created when the DAO is initialized, they can be stored as instance variables and reused. The following example shows how to define such a class:

Java
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
    this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
    return actorMappingQuery.findObject(id);
}
Kotlin
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)

The method in the previous example gets the client with id passed as the only parameter. Since we only need to return one object, we call the findObject helper method with id as a parameter. If instead there was a query that returned a list of objects and took additional parameters, we would use one of the execute methods, which takes an array of parameter values passed as variable-length arguments. The following example shows this method:

Java
public List<Actor> searchForActors(int age, String namePattern) {
    List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
    return actors;
}
Kotlin
fun searchForActors(age: Int, namePattern: String) =
            actorSearchMappingQuery.execute(age, namePattern)

Using SqlUpdate

The SqlUpdate class encapsulates a SQL update. Like a query, the update object is reusable, and like all RdbmsOperation classes, the update can have parameters and is defined in SQL. This class provides a number of update(..) methods similar to the execute(..) methods of request objects. The SqlUpdate class is concrete. It can be subclassed - for example, to add a custom update method. However, you don't necessarily need to subclass the SqlUpdate class because it can be easily parameterized by specifying SQL and declaring parameters. The following example creates a custom update method named execute:

Java
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 для обновляемого Customer
     * @param rating новое значение кредитного рейтинга
     * @return количество обновленных строк
     */
    public int execute(int id, int rating) {
        return update(rating, id);
    }
}
Kotlin
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 для обновляемого Customer
     * @param rating новое значение кредитного рейтинга
     * @return количество обновленных строк
     */
    fun execute(id: Int, rating: Int): Int {
        return update(rating, id)
    }
}

Using StoredProcedure

The StoredProcedure class is an abstract superclass for RDBMS stored procedure object abstractions.

The inherited property sql is the name of the stored procedure in the RDBMS.

To define a parameter for the StoredProcedure class, you can use SqlParameter or one of its subclasses. You must set the parameter name and SQL type in the constructor, as shown in the following code snippet:

Java
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
Kotlin
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),

The SQL type is specified using the java.sql.Types constants .

The first line (with SqlParameter) declares the IN parameter. You can use IN parameters both for calls to stored procedures and for queries using SqlQuery and its subclasses.

The second line (with SqlOutParameter) declares out parameter that will be used in the stored procedure call. There is also a SqlInOutParameter for InOut parameters (parameters that provide a in value to a procedure and also return a value).

For in parameters, in addition to the name and SQL type, you can set the scale for numeric data or the type name for custom created database types. For out parameters, you can specify a RowMapper to handle the display of rows returned from the REF cursor. Another option is to set SqlReturnType, which allows you to define custom handling of return values.

The following simple DAO example uses StoredProcedure to call the (sysdate)() function), which comes with any Oracle database. To use stored procedure functionality, you must create a class that extends StoredProcedure. In this example, the StoredProcedure class is an inner class. However, if you need to reuse StoredProcedure, you can declare it as a top-level class. This example has no input parameters, but the output parameter is declared as a date type using the SqlOutParameter class. The execute() method runs the procedure and extracts the returned date from the resulting Map. The resulting Map has an entry for each declared output parameter (in this case only one), using the parameter name as the key. The following listing shows our custom StoredProcedure class:

Java
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 string "sysdate" has no input parameters, so it returns an empty Map...
            Map<String, Object> results = execute(new HashMap<String, Object>());
            Date sysdate = (Date) results.get("date");
            return sysdate;
        }
    }
}
Kotlin
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 string "sysdate" has no input parameters, so an empty Map is returned...
            val results = execute(mutableMapOf<String, Any>())
            return results["date"] as Date
        }
    }
}

The following StoredProcedure example has two output parameters (in this case REF cursors from Oracle ):

Java
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 stored procedure has no input parameters, so it returns an empty Map
        return super.execute(new HashMap<String, Object>());
    }
}
Kotlin
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 stored procedure has no input parameters, so it returns an empty Map
        return super.execute(HashMap<String, Any>())
    }
}

Note how the overloads of the declareParameter(..) method that were used in the TitlesAndGenresStoredProcedure constructor are passed to instances of the RowMapper implementation. This is a very convenient and efficient way to reuse existing functionality. The following two examples contain code for two RowMapper implementations.

The TitleMapper class maps a ResultSet with a domain object Title for each row in the provided ResultSet as follows:

Java
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;
    }
}
Kotlin
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"))
}

The GenreMapper class maps a ResultSet to a Genre domain object for each row in the provided ResultSet as follows:

Java
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"));
    }
}
Kotlin
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"))
    }
}

To To pass parameters to a stored procedure that contains one or more input parameters in its RDBMS definition, you can write a strongly typed execute(..) method that will be delegated to the untyped execute(Map) method in the superclass, as shown in the following example:

Java
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);
    }
}
Kotlin
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))
}