Mappers - mhewedy/spwrap GitHub Wiki
However spwrap
is very simple, yet it is very important to understand Mappers correctly.
Mapper is a class that implements one of type interfaces ResultSetMapper
and TypedOutputParamMapper
.
-
The first interface
ResultSetMapper
you will have to implement to return ResultSet from your stored procedure. -
The second interface
TypedOutputParamMapper
you will have to implement to return registered output parameters from your stored procedure.
These Mappers is used internally by spwrap
to provide the final result for you:
@Mapper(CustomerMapper.class)
@StoredProc("proc_list_all_customers_with_birhtdate_before")
List<Customer> listCustomersWithBirthDateBefore(@Param(java.sql.Types.DATE) birthDate);
In the above code snippet, suppose we have the stored procedure proc_list_all_customers_with_birhtdate_before
that takes 1 input parameter and return a result set as a result of executing a select query like:
select id, firstName, lastName from customers where ....
NOTE: All stored procedures by default need to return 2 additional output parameters for result code and message, however you can override such behaviour using Configurations read more on configurations wiki page
In this case, the CustomerMapper need to implement ResultSetMapper
and provide the mapping by implementing the map method:
public class CustomerMapper implements ResultSetMapper<Customer> {
@Override
public Customer map(Result<?> result) {
return new Customer(result.getInt(1), result.getString(2), result.getString(3));
}
}
NOTE: Mapping classes could be reused across your application, however I strongly recommend to have a set of General-usage Mapping classes and reuse them, such Mapping classes might be for example
IdMapping
class to return a single Long/Integer ID value of newly created recored.
We have seen an example of ResultSetMapper
object, however the TypedOutputParamMapper
is the same, it has additional method that you have to implement, List<Integer> getTypes()
these are the types of the registered output parameters (excluding the 2 additional result code and message output parameters).
see this example:
@StoredProc("get_customer_info")
Customer getCustomerInfo(@Param(Types.BIGINT)Long custId);
In this case because there's no @Mapping
annotation, the domain object Customer
will need to act as the mapping object. or you will get a CallException
saying method return type is not void however no mapping provided!
.
To avoid such Exception, your Customer
class need to implement TypedOutputParamMapper
:
public class Customer implements TypedOutputParamMapper<Customer> {
private Integer id;
private String firstName, lastName;
// mandatory when implementing TypedOutputParamMapper or ResultSetMapper
public Customer() {
}
public Customer(Integer id, String firstName, String lastName) {
super();
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public Integer id() {
return id;
}
public String firstName() {
return firstName;
}
public String lastName() {
return lastName;
}
public Customer map(Result<?> result) {
return new Customer(null, result.getString(1), result.getString(2));
}
public List<Integer> getTypes() {
return Arrays.asList(VARCHAR, VARCHAR);
}
@Override
public String toString() {
return "Customer [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
NOTE: The
Result
object that is passed to yourmap
method on eitherResultSetMapper
orTypedOutputParamMapper
is 1-based index, so the first output parameter index is 1 regardless of its order in the whole stored procedure parameters, remember the first output parameter index is 1, the second 2 and so on. and the same thing applies to the result set.
NOTE: remember always, if you choose to have all your stored procedures have 2 additional output parameters for result code and message (default behaviour), you will not either see these two output parameters in your code or deal with them, completely ignore them when you implement the
map
andgetTypes
methods.
Because both interfaces ResultSetMapper
or TypedOutputParamMapper
have the following method:
T map(Result<?> result);
and if you have one class that implement both interfaces, you still can distinguish between the call of each interface using result.isResultSet
or result.isCallableStatement
:
public Customer map(Result<?> result) {
if (result.isResultSet()) {
//handle the call that returns a result set
} else {
//handle the call that returns an output parameters
}
}
NOTE the map method of
ResultSetMapper
need to return a new Object each time it is invoked.
NOTE A no-args constructor should be exists on the Mapping class.
Result
class is just a wrapper around CallableStatement
or ResultSet
that its main purpose is to wrap the checked SQLException
into the runtime CallException
.
You can always access the underlying CallableStatement
or ResultSet
object using:
if (result.isResultSet()){
java.sql.ResultSet rs = ((ResultSet)result.wrappedObject());
// access data from rs
}
if (result.isCallableStatement()){
java.sql.CallableStatement cstmt = ((CallableStatement)result.wrappedObject());
// access data from cstmt
}
NOTE: when you access output parameters from
CallableStatement
, you will need to use the index of the output parameter in regard to the whole parameters (input and output), for example you have 3 IN and 3 out, so the first out param will be of index4
, not this is not the case if you access the parameter using theResult
wrapper, the first output parameter is of index1
.
As said before, the DAO method can point to the Mapper class into two ways
- the Mapper class is the same class that is returned from the DAO method.
@StoredProc("some_stored_proc")
MapperClass getSomeData();
- the Mapper class is annotating the DAO method using
@Mapper
annotation.
@Mapper(ReturnObject.class)
@StoredProc("some_stored_proc")
ReturnObject getSomeData();
And the precedence is always for the Mapper class that provided in the @Mapper
annotation. in other words, if you have 2 stored procedures that return customer information but in different order, for example first Stored procedure returns firstName
, birthDate
, age
, and the second Stored procedure returns just age
, lastName
and you have these 2 DAO methods to call the stored procedures:
Customer getFirstNameBirthDateAndAgeBy(@Param(BIGINT) Long custId);
Customer getAgeAndLastNameBy(@Param(BIGINT) Long custId);
In such case, the customer object cannot be used as a Mapper for both Stored procedure, so you can use it as a Mapper for one, and override it with @Mapper
annotation with a Custom mapper for the other like this:
Customer getFirstNameBirthDateAndAgeBy(@Param(BIGINT) Long custId);
@Mapper(CustomerCustomMapper1.class)
Customer getAgeAndLastNameBy(@Param(BIGINT) Long custId);
NOTE: the @Mapper annotation can take up to 2 Mapper classes one of each Type (
ResultSetMapper
andTypedOutputParamMapper
, in other words, any DAO method can return up to 1 result set and 1 list of output parameters. and if a DAO method returned both in one call, then the return type should beTuple
. Example:
@Mapper({CustomerResultSetMapper.class, OrderParamMapper.class})
Tuple<Customer, Order> getAllCustomersAndOrderby(@Param(Types.BIGINT) Long orderId);
Tuple<Customer, Order> t = getAllCustomersAndOrderby(100l);
List<Customer> customerList = t.list();
Order order = t.object();
NOTE: If your stored procedure returns a single output parameter with no result set, then you can use the
@Scalar
annotation and you will not need to provide a Mapper class yourself, the mapping will done for you. read more about Scalar annotation.