Класи SimpleJdbcInsert та SimpleJdbcCall забезпечують спрощену конфігурацію, використовуючи метадані бази даних, які можна отримати через драйвер JDBC. Це означає, що доведеться менше займатися конфігуруванням, хоча можна перевизначати або вимкнути обробку метаданих, якщо ти волієш додавати подробиці до свого коду.

Вставка даних за допомогою SimpleJdbcInsert

Почнемо з розгляду класу SimpleJdbcInsert із мінімальною кількістю параметрів конфігурації. Потрібно створити екземпляр SimpleJdbcInsert у методі ініціалізації рівня доступу до даних. У цьому прикладі методом, що ініціалізує, є метод setDataSource. Не потрібно створювати підклас класу SimpleJdbcInsert. Натомість можна створити новий екземпляр і вказати ім'я таблиці за допомогою методу withTableName. Конфігураційні методи для цього класу дотримуються стилю fluid, який повертає екземпляр SimpleJdbcInsert, що дозволяє побудувати ланцюжок усіх конфігураційних методів. У наступному прикладі використовується лише один конфігураційний метод (приклади кількох методів будуть показані пізніше):

Java

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);
    }
    // ... додаткові методи
}
Kotlin

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. У наступному лістингу показано, як це робиться:

Java

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());
    }
    // ... додаткові методи
}
Kotlin

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, як показано в наступному прикладі:

Java

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());
    }
    //  ... додаткові методи
}
Kotlin

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:

Java

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());
    }
    // ... додаткові методи
}
Kotlin

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, що можна об'єднати в ланцюжок. У цьому прикладі показано, як його використовувати:

Java

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());
    }
    // ... додаткові методи
}
Kotlin

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 і повертається коду, що викликає.