Класи SimpleJdbcInsert
та SimpleJdbcCall
забезпечують спрощену конфігурацію, використовуючи
метадані бази даних, які можна отримати через драйвер JDBC. Це означає, що доведеться менше займатися
конфігуруванням, хоча можна перевизначати або вимкнути обробку метаданих, якщо ти волієш додавати подробиці до свого
коду.
Вставка даних за допомогою SimpleJdbcInsert
Почнемо з розгляду класу SimpleJdbcInsert
із мінімальною кількістю параметрів конфігурації. Потрібно створити екземпляр SimpleJdbcInsert
у методі
ініціалізації рівня доступу до даних. У цьому прикладі методом, що ініціалізує, є метод setDataSource
.
Не потрібно створювати підклас класу SimpleJdbcInsert
. Натомість можна створити новий екземпляр і
вказати ім'я таблиці за допомогою методу withTableName
. Конфігураційні методи для цього класу
дотримуються стилю fluid
, який повертає екземпляр SimpleJdbcInsert
, що дозволяє побудувати
ланцюжок усіх конфігураційних методів. У наступному прикладі використовується лише один конфігураційний метод
(приклади кількох методів будуть показані пізніше):
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... додаткові методи
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
fun add(actor: Actor) {
val parameters = mutableMapOf<String, Any>()
parameters["id"] = actor.id
parameters["first_name"] = actor.firstName
parameters["last_name"] = actor.lastName
insertActor.execute(parameters)
}
// ... додаткові методи
}
Тут метод execute
приймає за єдиний параметр звичайний
java.util.Map
. Важливо відзначити, що ключі, які використовуються для Map
, повинні
збігатися з іменами стовпців таблиці, як визначено в базі даних. Це відбувається тому, що метадані читаються для
створення фактичної інструкції вставки.
Отримання автоматично згенерованих ключів за допомогою SimpleJdbcInsert
У наступному прикладі використовується та сама вставка, що і в попередньому прикладі, але замість передачі
id
вона здійснює автоматичне отримання згенерованого ключа з подальшим встановленням для нового об'єкта
Actor
. При створенні SimpleJdbcInsert
, на додаток до вказання імені таблиці, ця вставка
встановлює ім'я згенерованого ключового стовпця за допомогою методу usingGeneratedKeyColumns
. У
наступному
лістингу показано, як це робиться:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... додаткові методи
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... додаткові методи
}
Основна відмінність у разі виконання вставки з використанням другого підходу полягає в тому, що ти не додаєш
id
до Map
, а викликаєш метод executeAndReturnKey
. Це повертає об'єкт java.lang.Number
,
за допомогою якого можна створити екземпляр числового типу, який використовується в класі предметної області. Тут не
можна покладатися на те що, що ці бази даних повернуть певний клас Java. java.lang.Number
— це основний
клас, який можна використовувати. Якщо є кілька стовпців, що автоматично генеруються, або генеровані значення не є
числовими, то можна використовувати KeyHolder
, який повертається методом executeAndReturnKeyHolder
.
Зазначення стовпців для SimpleJdbcInsert
Можна обмежити стовпці для вставки, вказавши
список імен стовпців за допомогою методу usingColumns
, як показано в наступному прикладі:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... додаткові методи
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... додаткові методи
}
Виконання вставки відбувається так само, якби ви вдалися до метаданих, щоб визначити, які стовпці використовувати.
Використання SqlParameterSource
для встановлення значень параметрів
Використання Map
для встановлення значень параметрів працює досить добре, але це не найзручніший у
використанні клас. Spring містить кілька реалізацій інтерфейсу SqlParameterSource
, які можна
використовувати замість нього. Перша з них — BeanPropertySqlParameterSource
, який є дуже зручним
класом, якщо у тебе є JavaBean-сумісний клас, що містить твої значення. Він використовує відповідний гетер для
отримання значень параметрів. У цьому прикладі показано, як використовувати
BeanPropertySqlParameterSource
:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... додаткові методи
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... додаткові методи
}
Іншим варіантом є MapSqlParameterSource
, який схожий на Map
, але надає зручніший
метод addValue
, що можна об'єднати в ланцюжок. У цьому прикладі показано, як його використовувати:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... додаткові методи
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... додаткові методи
}
Як бачиш, конфігурація та сама. Для використання цих альтернативних класів введення необхідно змінити лише код, що виконується.
Виклик збереженої процедури за допомогою SimpleJdbcCall
Клас SimpleJdbcCall
використовує метадані у базі даних для пошуку імен in
та
out
параметрів, тому не потрібно явно оголошувати їх. Можна оголосити параметри, якщо ти волієш
робити це, або якщо у тебе є параметри (такі як ARRAY або
STRUCT), які не мають
автоматичного відображення на клас Java. У першому прикладі показана проста процедура, яка повертає з бази даних
MySQL лише скалярні значення у форматі
VARCHAR
та DATE
. Процедура в прикладі зчитує
вказаний запис актора і повертає стовпці first_name
, last_name
та
birth_date
у вигляді параметрів out
. У наступному лістингу показано останній приклад:
CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;
Параметр in_id
містить id
актора, якого ти шукаєш. Параметри out
повертають дані, зчитані з таблиці.
Можна оголосити SimpleJdbcCall
способом, аналогічним до оголошення SimpleJdbcInsert
.
Необхідно створити екземпляр та налаштувати клас у методі ініціалізації твого рівня доступу до даних. Порівняно
з класом StoredProcedure
, не потрібно створювати підклас і оголошувати параметри, пошук яких
можливо здійснювати в метаданих базах даних. Наступний приклад конфігурації SimpleJdbcCall
використовує попередню процедуру, що зберігається (єдиним параметром конфігурації, крім DataSource
,
є ім'я процедури, що зберігається):
Java
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}
public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}
// ... додаткові методи
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val source = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(source)
return Actor(
id,
output["out_first_name"] as String,
output["out_last_name"] as String,
output["out_birth_date"] as Date)
}
// ... додаткові методи
}
Код, який ти пишеш для виконання виклику, включає створення SqlParameterSource
, що містить параметр
IN. Потрібно зіставити ім'я, вказане для вхідного значення, з ім'ям параметра, оголошеного в процедурі, що
зберігається. Регістр не обов'язково повинен збігатися, оскільки ти використовуєш метадані, щоб визначити, яким
чином на об'єкти бази даних потрібно посилатися в процедурі, що зберігається. Те, що зазначено в джерелі для
процедури, що зберігається, не обов'язково збігається з тим, як вона зберігається в базі даних. Деякі бази даних
перетворюють імена у всі верхні регістри, в той час як інші використовують нижній регістр або використовують
зазначений регістр.
Метод execute
приймає параметри IN і повертає Map
, що містить будь-які параметри
out
, ключовими з яких є імена, зазначені в процедурі, що зберігається. В даному випадку це out_first_name
,
out_last_name
та out_birth_date
.
Остання частина методу execute
створює екземпляр Actor
, який використовуватиметься для
повернення отриманих даних. Знову ж таки, важливо використовувати імена out
параметрів у тому
вигляді, в якому вони оголошені в процедурі, що зберігається. Крім того, регістр в іменах out
параметрів, що зберігаються в результуючій Map, збігається з регістром імен out
параметрів бази
даних, який може відрізнятися в різних базах даних. Щоб зробити код більш платформнонезалежним, потрібно виконувати
пошук
без урахування регістру або дати Spring команду використовувати LinkedCaseInsensitiveMap
. Щоб
здійснити останнє, можна створити власний JdbcTemplate
і встановити властивість setResultsMapCaseInsensitive
у true
. Потім можна передати цей налаштований екземпляр JdbcTemplate
до конструктора
твого SimpleJdbcCall
. У цьому прикладі показано цю конфігурацію:
Java
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}
// ... додаткові методи
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... додаткові методи
}
Виконавши цю дію, ти уникнеш конфліктів у регістрі, який використовується для імен твоїх параметрів,
що повертаються
.
Явне оголошення параметрів для використання в
SimpleJdbcCall
Раніше в цьому розділі ми описували, як параметри виводяться з метаданих, але ти можеш оголосити їх явно, якщо
захочеш. Зробити це можна, створивши та налаштувавши SimpleJdbcCall
за допомогою методу declareParameters
,
який приймає змінну кількість об'єктів SqlParameter
як вхідні дані. Докладніше про те, як визначити
SqlParameter
, див. у наступному розділі.
Явне оголошення необхідне, якщо база даних, що використовується, не є
базою даних, що підтримується Spring. Нині Spring підтримує пошук метаданих у викликах збережених
процедур для наступних баз даних: Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle та Sybase. Ми також
підтримуємо пошук метаданих у функціях, що зберігаються для MySQL, Microsoft SQL Server і Oracle.
Ти можеш явно оголосити один, кілька або всі параметри. Метадані параметрів, як і раніше, використовуються там,
де параметри не оголошені явно. Щоб обійти всю обробку пошуку метаданих для потенційних параметрів та
використовувати лише оголошені параметри, можна викликати метод
withoutProcedureColumnMetaDataAccess
як частину оголошення. Припустимо, що з функції бази даних
оголошено дві чи більше різних сигнатур виклику. У цьому випадку useInParameterNames
викликається,
щоб вказати список імен IN параметрів, які слід увімкнути для цієї сигнатури.
У наступному прикладі показано повністю оголошений виклик процедури та використано інформацію з попереднього
прикладу:
Java
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}
// ... додаткові методи
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
// ... додаткові методи
}
Виконання та кінцеві результати у цих двох прикладах однакові. У другому прикладі всі деталі вказані явно, а
не з використанням метаданих.
Спосіб визначення
SqlParameters
Для визначення параметра для класів SimpleJdbc
, а також для класів операцій РСУБД (розглянуто в
розділі "Моделювання операцій
JDBC як об'єктів Java ") можна використовувати SqlParameter
або один із його підкласів.
Для цього зазвичай вказують ім'я параметра та тип SQL у конструкторі. Тип SQL задається за допомогою
констант java.sql.Types
. Раніше в цьому розділі ми бачили оголошення, схожі на такі:
Java /div>
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),
Перший рядок з SqlParameter
оголошує IN параметр. Можна використовувати параметри IN як для
викликів збережених процедур, так і для запитів за допомогою SqlQuery
та його підкласів
(розглянуто в розділі "Основні
відомості про SqlQuery
").
Другий рядок (з SqlOutParameter
) повідомляє out
параметр для використання у
виклику збереженої процедури. Існує також SqlInOutParameter
для InOut
параметрів (параметри, які надають процедурі значення IN і які також повертають значення).
Для надання вхідних значень використовуються лише параметри,
оголошені як SqlParameter
та SqlInOutParameter
. Тут криється відмінність від
класу StoredProcedure
, який (з причин зворотної сумісності) дозволяє надавати вхідні
значення для параметрів, оголошених як SqlOutParameter
.
Для IN параметрів, крім імені та типу SQL, можна вказати для числових даних ім'я типу для кастомно
створених типів баз даних. Для out
параметрів можна вказати RowMapper
, щоб
проводити обробку відображення рядків, що повертаються з курсору REF
. Інший варіант –
вказати SqlReturnType
, який дає можливість визначити індивідуальну обробку значень, що
повертаються.
Виклик збереженої функції за допомогою SimpleJdbcCall
Викликати функцію, що зберігається, можна майже так само, як і процедуру, що зберігається, за винятком
того, що вказується ім'я функції, а не ім'я процедури. Метод withFunctionName
використовується як частина конфігурації, щоб вказати, що потрібно зробити виклик функції, після чого
генерується відповідний рядок для виклику функції. Для виконання функції використовується
спеціалізований виклик(executeFunction
), який повертає значення функції, що повертається, у
вигляді об'єкта зазначеного типу, що означає, що не потрібно отримувати повертаєме значення з
результуючої Map. Аналогічний допоміжний метод (з ім'ям executeObject
) також доступний для
процедур, що зберігаються, які мають тільки один out
параметр. Наступний приклад (для
MySQL) заснований на збереженій функції get_actor_name
, яка повертає повне ім'я актора:
CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;
Щоб викликати цю функцію, ми знову створюємо SimpleJdbcCall
у методі ініціалізації, як
показано в наступному прикладі:
Java
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}
public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}
// ... додаткові методи
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val source = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, source)
}
// ... додаткові методи
}
Метод executeFunction
повертає String
, що містить значення,
яке повертається з виклику функції.
Повернення ResultSet
або REF-курсора з об'єкта SimpleJdbcCall
Виклик процедури або функції, що зберігається, яка повертає результуючий набір, є трохи заплутаним
завданням. Деякі бази даних повертають результуючі набори під час обробки результатів JDBC, тоді як
інші вимагають явно зареєстрованого out
параметра певного типу. Обидва підходи вимагають
додаткової обробки для перебору результуючого набору та обробки повернутих рядків. За допомогою SimpleJdbcCall
можна використовувати метод returningResultSet
та оголосити реалізацію
RowMapper
, яка використовуватиметься для вказаного параметра. Якщо результуючий набір
повертається під час обробки результатів, то імена не будуть визначені. Тому результати, що
повертаються, повинні відповідати порядку, в якому оголошуються реалізації RowMapper
.
Вказане ім'я як і раніше використовується для зберігання обробленого списку результатів до результуючого
Map, який повертається з інструкції execute
.
У наступному прикладі (для MySQL) використовується процедура, яка не приймає жодних IN параметрів і
повертає всі рядки з таблиці t_actor
:
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
Щоб викликати цю процедуру, можна оголосити RowMapper
. Оскільки клас, з яким ти хочеш
зробити зіставлення, дотримується правил JavaBean, можна використовувати
BeanPropertyRowMapper
, який створюється шляхом передачі потрібного класу для відображення
методу newInstance
. У цьому прикладі показано, як це зробити:
Java
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadAllActors;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}
public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
}
// ... додаткові методи
}
Kotlin
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getActorsList(): List<Actor> {
val m = procReadAllActors.execute(mapOf<String, Any>())
return m["actors"] as List<Actor>
}
// ... додаткові методи
}
Виклик execute
передає порожній Map
, оскільки цей виклик не приймає жодних
параметрів. Потім список акторів витягується з результуючої Map і повертається коду, що викликає.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ