Логическое развитие идеи параметризованых запросов и prepared запросов, это инкапсуляция запросов в собственные классы. Каждый запрос обёртывается в класс, объекты которого каким-либо образом настраиваются во время исполнения и выполняют себя сами.
В этом примере используется схема данных из статьи запросы в Spring JDBC.
MappingSqlQuery
Класс MappingSqlQuery ориентирован на выполнение запросов, возвращающих данные и объединяет в себе функционал RowMapper и PreparedStatementCreator: сразу и параметры связывает сам, и результат в объект отображает сам.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class OrderMappingQuery extends MappingSqlQuery<Order> {
private static final String ORDER_QUERY
= "SELECT O.ID, CUSTOMER_ID, EMAIL "
+ "FROM ORDERS AS O, CUSTOMERS AS C "
+ "WHERE C.ID=O.CUSTOMER_ID AND O.ID=?";
public OrderMappingQuery(final JdbcTemplate jdbcTemplate) {
super(jdbcTemplate.getDataSource(), ORDER_QUERY);
super.declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
@Override
protected final Order mapRow(
final ResultSet resultSet,
final int rowNum)
throws SQLException {
Customer customer = new Customer();
customer.setId(resultSet.getInt("customer_id"));
customer.setEmail(resultSet.getString("email"));
Order order = new Order();
order.setId(resultSet.getInt("id"));
order.setCustomer(customer);
return order;
}
}
|
Реализация собственного MappingSqlQuery обычно состоит из двух частей. Вначале реализуют код, настраивающий MappingSqlQuery, то есть задают сам запрос, указывают какие у него параметры и т.д. Эту часть я вынес в конструктор.
Вторая часть — один в один реализация RowMapper, которая должна из JDBC ResultSet построить требуемый объект. Из-за того, что для настройки MappingSqlQuery требуется объект класса DataSource, его инициализация выполняется в методе @PostConstruct. Альтернативой этому было бы описание OrderMappingQuery как Spring bean и тогда Spring сам бы его создал, сам сконфигурировал с DataSource и сам подсунул бы в места использования.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Repository
public class OrderRepositoryJdbc implements OrderRepository {
@Inject
private JdbcTemplate jdbcTemplate;
private OrderMappingQuery orderQuery;
@PostConstruct
public final void init() {
orderQuery = new OrderMappingQuery(jdbcTemplate);
}
@Override
public final Order get(final Integer id) {
return orderQuery.findObject(id);
}
}
|
1
2
3
4
5
6
7
|
@Test
public void testGet() {
Order actual = testedObject.get(100);
assertThat(actual.getId(), is(100));
assertThat(actual.getCustomer().getId(), is(100));
assertThat(actual.getCustomer().getEmail(), is("TEST"));
}
|
SqlUpdate
Антонимом к MappingSqlQuery является SqlUpdate: всё то же самое, только не для получения данных, а для изменения данных.
1
2
3
4
5
6
7
8
9
|
public class UpdateSkuDescriptionQuery extends SqlUpdate {
public UpdateSkuDescriptionQuery(final JdbcTemplate jdbcTemplate) {
setDataSource(jdbcTemplate.getDataSource());
setSql("UPDATE skus SET description = ? WHERE id = ?");
declareParameter(new SqlParameter("description", Types.VARCHAR));
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
}
|
В собственной реализации точно так же требуется вначале настроить объект, задав запрос, параметры соединение и прочая. Но, так как предполагается, что запрос ничего не возвращает, реализация кода отображения результатов в объект не требуется. По той же причине, из-за необходимости передавать DataSource, я и в этот раз инициализирую объект UpdateSkuDescriptionQuery в @PostConstruct методе.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
@Repository
public class SkuRepositoryJdbc implements SkuRepository {
private static final String DESCRIPTION_QUERY
= "SELECT DESCRIPTION FROM SKUS WHERE ID = :id";
private UpdateSkuDescriptionQuery updateQuery;
@Inject
private NamedParameterJdbcTemplate jdbcTemplate;
@Inject
private JdbcTemplate simpleJdbcTemplate;
@PostConstruct
public final void init() {
updateQuery = new UpdateSkuDescriptionQuery(simpleJdbcTemplate);
}
@Override
public final void changeDescription(final Sku sku) {
updateQuery.update(sku.getDescription(), sku.getId());
}
@Override
public final String getDescription(final Integer id) {
return jdbcTemplate.queryForObject(
DESCRIPTION_QUERY,
Collections.singletonMap("id", id),
String.class);
}
}
|
1
2
3
4
5
6
7
8
9
10
|
@Test
public void testChange() {
Sku expected = new Sku();
expected.setId(100);
expected.setDescription("NEWBIE");
testedObject.changeDescription(expected);
assertThat(testedObject.getDescription(100), is("NEWBIE"));
}
|
SimpleJdbcInsert
Поскольку из базы можно получить её метаинформацию, в частности названия и типы столбцов в таблицах, то в некоторых случаях можно даже и сам текст запроса то и не писать, только операцию и параметры. SimpleJdbcInsert это как раз тот случай: настраиваем один раз таблицу и прочие параметры и сразу можно добавлять строки.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private SimpleJdbcInsert createCustomer;
@Inject
private JdbcTemplate jdbcTemplate;
@PostConstruct
public final void init() {
createCustomer = new SimpleJdbcInsert(jdbcTemplate)
.withTableName("customers")
.usingGeneratedKeyColumns("id");
}
@Override
public final void add(final Customer c) {
createCustomer.execute(new BeanPropertySqlParameterSource(c));
}
|
SimpleJdbcInsert принимает SqlParameterSource, что позволяет передавать в него параметры как в обычный запрос, используя Map или любую реализацию SqlParameterSource.
Код примера доступен на github.