DtoQuery
DtoQuery is where we use SQL and map it into plain beans. These plain beans are just ordinary beans with public constructor(s) and public getters/setters.
public static class CustomerDto {
Integer id;
String name;
... // getters & setters
}
// using positioned parameters
List<CustomerDto> beans =
database.findDto(CustomerDto.class, "select id, name from customer where name = ?")
.setParameter(1, "Rob")
.findList();
// using named parameters
List<CustomerDto> beans =
database.findDto(CustomerDto.class, "select id, name from customer where name = :name")
.setParameter("name", "Rob")
.findList();
Mapping
When mapping the SQL resultSet to the DTO beans the following is used.
- Firstly we look for a constructor with the same number of arguments as columns in the resultSet. If we have such a constructor we use it for mapping (assuming the correct types).
- Secondly see if there are more columns in the resultSet than our largest constructor. If so we the largest constructor to read the first columns and then look to use setter methods to map the remaining columns.
- Finally we use the default constructor and setter methods.
Note that we perform the mapping using MethodHandles rather than reflection. That does mean it expects the Constructor and Setter methods to have public access.
firstRow / maxRows
We can apply firstRows/maxRows to the sql query.
String sql = "select id, name from customer where name like ?";
List<CustomerDto> robs = server().findDto(CustomerDto.class, sql)
.setParameter(1, "Rob%")
.setMaxRows(10)
.findList();
The appropriate sql will be added to apply firstRows/maxRows for the database platform.
select id, name from o_customer where name like ?
limit 10
RelaxedMode
By default when performing the mapping, if we are not able to map a column to a property (setter method) then an exception is thrown.
Instead we can set relaxed mode on the query via query.setRelaxedMode()
and that
means it will effectively ignore/skip any column that it can't map.
This is expected to be useful when there is potentially a large existing query and there is a desire to only map some of the columns into bean properties.
Postgres ANY
When using Postgres we can use Postgres ANY
in DtoQuery using positioned parameters
or by using setArrayParameter()
.
The benefit of using Postgres ANY
is that we end up using a single SQL query regardless of the number
of parameters - a single JDBC PreparedStatement and the database has a single SQL query to parse. This results in
the database doing less hard parsing and better use of PreparedStatement cache, less query plans to cache, less memory consumed.
Example: Positioned parameter
When using index positioned parameters and binding a collection that will bind as a JDBC ARRAY.
List<Integer> ids = List.of(1, 2);
String sql = "select id, name from customer where id = any(?)"
List<CustomerDto> list = DB.findDto(CustomerDto.class, sql)
.setParameter(ids) // bind as JDBC ARRAY
.findList();
Example: Using setArrayParameter() for named parameters
When using named parameters by default Ebean expects use of an IN
clause and collections are "expanded".
For example, a collection of 3 values is expanded into 3 separate JDBC bind values ?,?,?
rather than JDBC binding a single ARRAY.
To bind collections as a Postgres ARRAY for an ANY
clause we need to use setArrayParameter()
.
List<Integer> ids = List.of(1, 2);
String sql = "select id, name from o_customer where id = any(:idList)"
List<CustomerDto> list2 = DB.findDto(CustomerDto.class, sql)
.setArrayParameter("idList", ids) // bind as JDBC ARRAY
.findList();
findEach
We use findEach when processing a large query where we do not wish to hold all the beans in a list but instead process them one at a time.
String sql = "select id, name from o_customer where id > :id order by id desc";
database.findDto(CustomerDto.class, sql)
.setParameter("id", 0)
.findEach(customer -> {
log.debug("got " + customer.getId() + " " + customer.getName());
...
});
findStream
findStream is similar to findEach where we process the result one at a time.
We should use a try with resources
block to ensure the underlying resources
are closed.
String sql = "select id, name from o_customer where id > ? order by id desc";
try (Stream<CustomerDto> customers =
database.findDto(CustomerDto.class, sql)
.setParameter(0)
.findStream()) {
// use the stream of customers ...
...
}
findIterate
findIterate is similar to findStream and findEach where we process the result one at a time.
We should use a try with resources
block to ensure the underlying resources
are closed.
String sql = "select id, name from o_customer where id > :id order by id desc";
try (QueryIterator<CustomerDto> customers =
database.findDto(CustomerDto.class, sql)
.setParameter(0)
.findIterate()) {
// iterate through the customers
...
}
Single column
If we are looking to select a single column we look to use SqlQuery.mapToScalar() for that.
String sql = "select mysequence.nextval";
Long nextVal = DB.sqlQuery(sql)
.mapToScalar(Long.class)
.findOne();