diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/pom.xml b/2025-07/spring-09-jdbc/jdbc-demo-exercise/pom.xml
new file mode 100644
index 00000000..16b94a8f
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+
+ jdbc-demo-exercise
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/Main.java b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..8916eb04
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,20 @@
+package ru.otus.spring;
+
+import org.h2.tools.Console;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import ru.otus.spring.dao.PersonDao;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ ApplicationContext context = SpringApplication.run(Main.class);
+
+ PersonDao dao = context.getBean(PersonDao.class);
+
+ Console.main(args);
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..9fa01df3
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,17 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+ int count();
+
+ void insert(Person person);
+
+ Person getById(long id);
+
+ List getAll();
+
+ void deleteById(long id);
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..63c535ee
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,41 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations) {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ return 0;
+ }
+
+ @Override
+ public void insert(Person person) {
+
+ }
+
+ @Override
+ public Person getById(long id) {
+ return null;
+ }
+
+ @Override
+ public List getAll() {
+ return null;
+ }
+
+ @Override
+ public void deleteById(long id) {
+
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..a78f21e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.domain;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Data
+public class Person {
+ private final long id;
+ private final String name;
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/application.yml
new file mode 100644
index 00000000..8f40ea4e
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #schema: schema.sql
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
+ h2:
+ console:
+ path: /h2-console
+ settings:
+ web-allow-others: true
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/data.sql
new file mode 100644
index 00000000..f96e0938
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'masha');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/schema.sql b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/schema.sql
new file mode 100644
index 00000000..583bbae0
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/main/resources/schema.sql
@@ -0,0 +1,6 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS
+(
+ ID BIGINT PRIMARY KEY,
+ NAME VARCHAR(255)
+);
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
new file mode 100644
index 00000000..c8d41990
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
@@ -0,0 +1,92 @@
+package ru.otus.spring.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Dao для работы с пёрсонами должно")
+@JdbcTest
+@Import(PersonDaoJdbc.class)
+class PersonDaoJdbcTest {
+
+ private static final int EXPECTED_PERSONS_COUNT = 1;
+ private static final int EXISTING_PERSON_ID = 1;
+ private static final String EXISTING_PERSON_NAME = "Ivan";
+
+ @Autowired
+ private PersonDaoJdbc personDao;
+
+ @DisplayName("возвращать ожидаемое количество пёрсонов в БД")
+ @Test
+ void shouldReturnExpectedPersonCount() {
+ int actualPersonsCount = personDao.count();
+ assertThat(actualPersonsCount).isEqualTo(EXPECTED_PERSONS_COUNT);
+ }
+
+ @DisplayName("добавлять пёрсона в БД")
+ @Test
+ void shouldInsertPerson() {
+ int countBeforeInsert = personDao.count();
+ assertThat(countBeforeInsert).isEqualTo(EXPECTED_PERSONS_COUNT);
+
+ Person expectedPerson = new Person(2, "Igor");
+ personDao.insert(expectedPerson);
+
+ // Ошибка! Сейчас так проверяем т.к. больше нет других способов,
+ // когда появится getById, будем использовать его
+ int countAfterInsert = personDao.count();
+ assertThat(countAfterInsert).isEqualTo(countBeforeInsert + 1);
+/*
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+*/
+ }
+
+ @DisplayName("возвращать ожидаемого пёрсона по его id")
+ @Test
+ void shouldReturnExpectedPersonById() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+ }
+
+ @DisplayName("удалять заданного пёрсона по его id")
+ @Test
+ void shouldCorrectDeletePersonById() {
+ // Ошибка! Сейчас так проверяем т.к. больше нет других способов,
+ // когда появится getById, тест будет выглядеть, как закомментированный блок ниже
+ int countBeforeDelete = personDao.count();
+ assertThat(countBeforeDelete).isEqualTo(EXPECTED_PERSONS_COUNT);
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ int countAfterDelete = personDao.count();
+ assertThat(countAfterDelete).isEqualTo(countBeforeDelete - 1);
+
+ /*
+ assertThatCode(() -> personDao.getById(EXISTING_PERSON_ID))
+ .doesNotThrowAnyException();
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ assertThatThrownBy(() -> personDao.getById(EXISTING_PERSON_ID))
+ .isInstanceOf(EmptyResultDataAccessException.class);
+ */
+ }
+
+ @DisplayName("возвращать ожидаемый список пёрсонов")
+ @Test
+ void shouldReturnExpectedPersonsList() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ List actualPersonList = personDao.getAll();
+ assertThat(actualPersonList)
+ .containsExactlyInAnyOrder(expectedPerson);
+ }
+}
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/application.yml
new file mode 100644
index 00000000..f7de231a
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ #schema-locations: schema.sql
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/data.sql
new file mode 100644
index 00000000..db9a08f8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-exercise/src/test/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'Ivan');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/pom.xml b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/pom.xml
new file mode 100644
index 00000000..fa78daa1
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+
+ jdbc-demo-solution-3
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/Main.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..7ec142b6
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,22 @@
+package ru.otus.spring;
+
+import org.h2.tools.Console;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import ru.otus.spring.dao.PersonDao;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ ApplicationContext context = SpringApplication.run(Main.class);
+
+ PersonDao dao = context.getBean(PersonDao.class);
+
+ System.out.println("All count " + dao.count());
+
+ Console.main(args);
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDao.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..f2c30f7c
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,18 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+
+ int count();
+
+ void insert(Person person);
+
+ Person getById(long id);
+
+ List getAll();
+
+ void deleteById(long id);
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..f682253b
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,44 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations) {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ Integer count = jdbc.queryForObject("select count(*) from persons", Integer.class);
+ return count == null ? 0 : count;
+ }
+
+ @Override
+ public void insert(Person person) {
+
+ }
+
+ @Override
+ public Person getById(long id) {
+ return null;
+ }
+
+ @Override
+ public List getAll() {
+ return null;
+ }
+
+ @Override
+ public void deleteById(long id) {
+
+ }
+
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/domain/Person.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..a78f21e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.domain;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Data
+public class Person {
+ private final long id;
+ private final String name;
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/application.yml
new file mode 100644
index 00000000..8f40ea4e
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #schema: schema.sql
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
+ h2:
+ console:
+ path: /h2-console
+ settings:
+ web-allow-others: true
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/data.sql
new file mode 100644
index 00000000..f96e0938
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'masha');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/schema.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/schema.sql
new file mode 100644
index 00000000..583bbae0
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/main/resources/schema.sql
@@ -0,0 +1,6 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS
+(
+ ID BIGINT PRIMARY KEY,
+ NAME VARCHAR(255)
+);
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
new file mode 100644
index 00000000..aa21e59b
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
@@ -0,0 +1,27 @@
+package ru.otus.spring.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Dao для работы с пёрсонами должно")
+@JdbcTest
+@Import(PersonDaoJdbc.class)
+class PersonDaoJdbcTest {
+
+ private static final int EXPECTED_PERSONS_COUNT = 1;
+
+ @Autowired
+ private PersonDaoJdbc personDao;
+
+ @DisplayName("возвращать ожидаемое количество пёрсонов в БД")
+ @Test
+ void shouldReturnExpectedPersonCount() {
+ int actualPersonsCount = personDao.count();
+ assertThat(actualPersonsCount).isEqualTo(EXPECTED_PERSONS_COUNT);
+ }
+}
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/application.yml
new file mode 100644
index 00000000..f7de231a
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ #schema-locations: schema.sql
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/data.sql
new file mode 100644
index 00000000..db9a08f8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-3/src/test/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'Ivan');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/pom.xml b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/pom.xml
new file mode 100644
index 00000000..7abe25e4
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+
+ jdbc-demo-solution-4
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/Main.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..2bc8e898
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,27 @@
+package ru.otus.spring;
+
+import org.h2.tools.Console;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import ru.otus.spring.dao.PersonDao;
+import ru.otus.spring.domain.Person;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ ApplicationContext context = SpringApplication.run(Main.class);
+
+ PersonDao dao = context.getBean(PersonDao.class);
+
+ System.out.println("All count " + dao.count());
+
+ dao.insert(new Person(2, "ivan"));
+
+ System.out.println("All count " + dao.count());
+
+ Console.main(args);
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDao.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..f2c30f7c
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,18 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+
+ int count();
+
+ void insert(Person person);
+
+ Person getById(long id);
+
+ List getAll();
+
+ void deleteById(long id);
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..67c51ae5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,45 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations) {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ Integer count = jdbc.queryForObject("select count(*) from persons", Integer.class);
+ return count == null ? 0 : count;
+ }
+
+ @Override
+ public void insert(Person person) {
+ jdbc.update("insert into persons (id, name) values (?, ?)", person.getId(), person.getName());
+ }
+
+ @Override
+ public Person getById(long id) {
+ return null;
+ }
+
+ @Override
+ public List getAll() {
+ return null;
+ }
+
+ @Override
+ public void deleteById(long id) {
+ jdbc.update("delete from persons where id = ?", id);
+ }
+
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/domain/Person.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..a78f21e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.domain;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Data
+public class Person {
+ private final long id;
+ private final String name;
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/application.yml
new file mode 100644
index 00000000..8f40ea4e
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #schema: schema.sql
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
+ h2:
+ console:
+ path: /h2-console
+ settings:
+ web-allow-others: true
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/data.sql
new file mode 100644
index 00000000..f96e0938
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'masha');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/schema.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/schema.sql
new file mode 100644
index 00000000..583bbae0
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/main/resources/schema.sql
@@ -0,0 +1,6 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS
+(
+ ID BIGINT PRIMARY KEY,
+ NAME VARCHAR(255)
+);
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
new file mode 100644
index 00000000..0e48f1c1
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
@@ -0,0 +1,73 @@
+package ru.otus.spring.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import ru.otus.spring.domain.Person;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Dao для работы с пёрсонами должно")
+@JdbcTest
+@Import(PersonDaoJdbc.class)
+class PersonDaoJdbcTest {
+
+ private static final int EXPECTED_PERSONS_COUNT = 1;
+ private static final int EXISTING_PERSON_ID = 1;
+ private static final String EXISTING_PERSON_NAME = "Ivan";
+
+ @Autowired
+ private PersonDaoJdbc personDao;
+
+ @DisplayName("возвращать ожидаемое количество пёрсонов в БД")
+ @Test
+ void shouldReturnExpectedPersonCount() {
+ int actualPersonsCount = personDao.count();
+ assertThat(actualPersonsCount).isEqualTo(EXPECTED_PERSONS_COUNT);
+ }
+
+ @DisplayName("добавлять пёрсона в БД")
+ @Test
+ void shouldInsertPerson() {
+ int countBeforeInsert = personDao.count();
+ assertThat(countBeforeInsert).isEqualTo(EXPECTED_PERSONS_COUNT);
+
+ Person expectedPerson = new Person(2, "Igor");
+ personDao.insert(expectedPerson);
+
+ // Ошибка! Сейчас так проверяем т.к. больше нет других способов,
+ // когда появится getById, будем использовать его
+ int countAfterInsert = personDao.count();
+ assertThat(countAfterInsert).isEqualTo(countBeforeInsert + 1);
+/*
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+*/
+ }
+
+ @DisplayName("удалять заданного пёрсона по его id")
+ @Test
+ void shouldCorrectDeletePersonById() {
+ // Ошибка! Сейчас так проверяем т.к. больше нет других способов,
+ // когда появится getById, тест будет выглядеть, как закомментированный блок ниже
+ int countBeforeDelete = personDao.count();
+ assertThat(countBeforeDelete).isEqualTo(EXPECTED_PERSONS_COUNT);
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ int countAfterDelete = personDao.count();
+ assertThat(countAfterDelete).isEqualTo(countBeforeDelete - 1);
+
+ /*
+ assertThatCode(() -> personDao.getById(EXISTING_PERSON_ID))
+ .doesNotThrowAnyException();
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ assertThatThrownBy(() -> personDao.getById(EXISTING_PERSON_ID))
+ .isInstanceOf(EmptyResultDataAccessException.class);
+ */
+ }
+}
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/application.yml
new file mode 100644
index 00000000..f7de231a
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ #schema-locations: schema.sql
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/data.sql
new file mode 100644
index 00000000..db9a08f8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-4/src/test/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'Ivan');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/pom.xml b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/pom.xml
new file mode 100644
index 00000000..227c0edd
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/pom.xml
@@ -0,0 +1,52 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+
+ jdbc-demo-solution-5
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/Main.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..4bc2aca8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,31 @@
+package ru.otus.spring;
+
+import org.h2.tools.Console;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import ru.otus.spring.dao.PersonDao;
+import ru.otus.spring.domain.Person;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ ApplicationContext context = SpringApplication.run(Main.class);
+
+ PersonDao dao = context.getBean(PersonDao.class);
+
+ System.out.println("All count " + dao.count());
+
+ dao.insert(new Person(2, "ivan"));
+
+ System.out.println("All count " + dao.count());
+
+ Person ivan = dao.getById(2);
+
+ System.out.println("Ivan id: " + ivan.getId() + " name: " + ivan.getName());
+
+ Console.main(args);
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDao.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..9fa01df3
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,17 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+ int count();
+
+ void insert(Person person);
+
+ Person getById(long id);
+
+ List getAll();
+
+ void deleteById(long id);
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..59d8e9df
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,57 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations) {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ Integer count = jdbc.queryForObject("select count(*) from persons", Integer.class);
+ return count == null ? 0 : count;
+ }
+
+ @Override
+ public void insert(Person person) {
+ jdbc.update("insert into persons (id, name) values (?, ?)", person.getId(), person.getName());
+ }
+
+ @Override
+ public Person getById(long id) {
+ return jdbc.queryForObject("select id, name from persons where id = ?", new PersonMapper(), id);
+ }
+
+ @Override
+ public List getAll() {
+ return jdbc.query("select id, name from persons", new PersonMapper());
+ }
+
+ @Override
+ public void deleteById(long id) {
+ jdbc.update("delete from persons where id = ?", id);
+ }
+
+
+ private static class PersonMapper implements RowMapper {
+
+ @Override
+ public Person mapRow(ResultSet resultSet, int i) throws SQLException {
+ long id = resultSet.getLong("id");
+ String name = resultSet.getString("name");
+ return new Person(id, name);
+ }
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/domain/Person.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..a78f21e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.domain;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Data
+public class Person {
+ private final long id;
+ private final String name;
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/application.yml
new file mode 100644
index 00000000..8f40ea4e
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/application.yml
@@ -0,0 +1,16 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #schema: schema.sql
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
+ h2:
+ console:
+ path: /h2-console
+ settings:
+ web-allow-others: true
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/data.sql
new file mode 100644
index 00000000..f96e0938
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'masha');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/schema.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/schema.sql
new file mode 100644
index 00000000..583bbae0
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/main/resources/schema.sql
@@ -0,0 +1,6 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS
+(
+ ID BIGINT PRIMARY KEY,
+ NAME VARCHAR(255)
+);
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
new file mode 100644
index 00000000..f04a9b1b
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
@@ -0,0 +1,71 @@
+package ru.otus.spring.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.dao.EmptyResultDataAccessException;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+@DisplayName("Dao для работы с пёрсонами должно")
+@JdbcTest
+@Import(PersonDaoJdbc.class)
+class PersonDaoJdbcTest {
+
+ private static final int EXPECTED_PERSONS_COUNT = 1;
+ private static final int EXISTING_PERSON_ID = 1;
+ private static final String EXISTING_PERSON_NAME = "Ivan";
+
+ @Autowired
+ private PersonDaoJdbc personDao;
+
+ @DisplayName("возвращать ожидаемое количество пёрсонов в БД")
+ @Test
+ void shouldReturnExpectedPersonCount() {
+ int actualPersonsCount = personDao.count();
+ assertThat(actualPersonsCount).isEqualTo(EXPECTED_PERSONS_COUNT);
+ }
+
+ @DisplayName("добавлять пёрсона в БД")
+ @Test
+ void shouldInsertPerson() {
+ Person expectedPerson = new Person(2, "Igor");
+ personDao.insert(expectedPerson);
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+ }
+
+ @DisplayName("возвращать ожидаемого пёрсона по его id")
+ @Test
+ void shouldReturnExpectedPersonById() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+ }
+
+ @DisplayName("удалять заданного пёрсона по его id")
+ @Test
+ void shouldCorrectDeletePersonById() {
+ assertThatCode(() -> personDao.getById(EXISTING_PERSON_ID))
+ .doesNotThrowAnyException();
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ assertThatThrownBy(() -> personDao.getById(EXISTING_PERSON_ID))
+ .isInstanceOf(EmptyResultDataAccessException.class);
+ }
+
+ @DisplayName("возвращать ожидаемый список пёрсонов")
+ @Test
+ void shouldReturnExpectedPersonsList() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ List actualPersonList = personDao.getAll();
+ assertThat(actualPersonList)
+ .containsExactlyInAnyOrder(expectedPerson);
+ }
+}
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/application.yml
new file mode 100644
index 00000000..f7de231a
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ #schema-locations: schema.sql
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/data.sql
new file mode 100644
index 00000000..db9a08f8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-5/src/test/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'Ivan');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/pom.xml b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/pom.xml
new file mode 100644
index 00000000..5949aef0
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+
+ jdbc-demo-solution-final
+ 1.0-SNAPSHOT
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/Main.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..05636811
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,33 @@
+package ru.otus.spring;
+
+import org.h2.tools.Console;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ApplicationContext;
+import ru.otus.spring.dao.PersonDao;
+import ru.otus.spring.domain.Person;
+
+@SpringBootApplication
+public class Main {
+
+ public static void main(String[] args) throws Exception {
+
+ ApplicationContext context = SpringApplication.run(Main.class);
+
+ PersonDao dao = context.getBean(PersonDao.class);
+
+ System.out.println("All count " + dao.count());
+
+ dao.insert(new Person(2, "ivan"));
+
+ System.out.println("All count " + dao.count());
+
+ Person ivan = dao.getById(2);
+
+ System.out.println("Ivan id: " + ivan.getId() + " name: " + ivan.getName());
+
+ System.out.println(dao.getAll());
+
+ Console.main(args);
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDao.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..f2c30f7c
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,18 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+
+ int count();
+
+ void insert(Person person);
+
+ Person getById(long id);
+
+ List getAll();
+
+ void deleteById(long id);
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..0ac9ad94
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,70 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+ private final NamedParameterJdbcOperations namedParameterJdbcOperations;
+
+ public PersonDaoJdbc(NamedParameterJdbcOperations namedParameterJdbcOperations) {
+ // Это просто оставили, чтобы не переписывать код
+ // В идеале всё должно быть на NamedParameterJdbcOperations
+ this.jdbc = namedParameterJdbcOperations.getJdbcOperations();
+ this.namedParameterJdbcOperations = namedParameterJdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ Integer count = jdbc.queryForObject("select count(*) from persons", Integer.class);
+ return count == null ? 0 : count;
+ }
+
+ @Override
+ public void insert(Person person) {
+ namedParameterJdbcOperations.update("insert into persons (id, name) values (:id, :name)",
+ Map.of("id", person.getId(), "name", person.getName()));
+ }
+
+ @Override
+ public Person getById(long id) {
+ Map params = Collections.singletonMap("id", id);
+ return namedParameterJdbcOperations.queryForObject(
+ "select id, name from persons where id = :id", params, new PersonMapper()
+ );
+ }
+
+ @Override
+ public List getAll() {
+ return jdbc.query("select id, name from persons", new PersonMapper());
+ }
+
+ @Override
+ public void deleteById(long id) {
+ Map params = Collections.singletonMap("id", id);
+ namedParameterJdbcOperations.update(
+ "delete from persons where id = :id", params
+ );
+ }
+
+ private static class PersonMapper implements RowMapper {
+
+ @Override
+ public Person mapRow(ResultSet resultSet, int i) throws SQLException {
+ long id = resultSet.getLong("id");
+ String name = resultSet.getString("name");
+ return new Person(id, name);
+ }
+ }
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/domain/Person.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..a78f21e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,11 @@
+package ru.otus.spring.domain;
+
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Data
+public class Person {
+ private final long id;
+ private final String name;
+}
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/application.yml
new file mode 100644
index 00000000..11e84086
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/application.yml
@@ -0,0 +1,17 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+# initialization-mode: always
+# schema: schema.sql
+# data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
+ h2:
+ console:
+ path: /h2-console
+ settings:
+ web-allow-others: true
+
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/data.sql
new file mode 100644
index 00000000..f96e0938
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'masha');
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/schema.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/schema.sql
new file mode 100644
index 00000000..bd6f5353
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/main/resources/schema.sql
@@ -0,0 +1,5 @@
+CREATE TABLE PERSONS
+(
+ ID BIGINT PRIMARY KEY,
+ NAME VARCHAR(255)
+);
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
new file mode 100644
index 00000000..d8881a0d
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/java/ru/otus/spring/dao/PersonDaoJdbcTest.java
@@ -0,0 +1,86 @@
+package ru.otus.spring.dao;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.test.context.transaction.AfterTransaction;
+import org.springframework.test.context.transaction.BeforeTransaction;
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.*;
+
+@DisplayName("Dao для работы с пёрсонами должно")
+@JdbcTest
+@Import(PersonDaoJdbc.class)
+//@Transactional(propagation = Propagation.NOT_SUPPORTED)
+class PersonDaoJdbcTest {
+
+ private static final int EXPECTED_PERSONS_COUNT = 1;
+ private static final int EXISTING_PERSON_ID = 1;
+ private static final String EXISTING_PERSON_NAME = "Ivan";
+
+ @Autowired
+ private PersonDaoJdbc personDao;
+
+ @BeforeTransaction
+ void beforeTransaction() {
+ System.out.println("beforeTransaction");
+ }
+
+ @AfterTransaction
+ void afterTransaction() {
+ System.out.println("afterTransaction");
+ }
+
+ @DisplayName("возвращать ожидаемое количество пёрсонов в БД")
+ @Test
+ void shouldReturnExpectedPersonCount() {
+ int actualPersonsCount = personDao.count();
+ assertThat(actualPersonsCount).isEqualTo(EXPECTED_PERSONS_COUNT);
+ }
+
+ //@Rollback(value = false)
+ //@Commit
+ @DisplayName("добавлять пёрсона в БД")
+ @Test
+ void shouldInsertPerson() {
+ Person expectedPerson = new Person(2, "Igor");
+ personDao.insert(expectedPerson);
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+ }
+
+ @DisplayName("возвращать ожидаемого пёрсона по его id")
+ @Test
+ void shouldReturnExpectedPersonById() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ Person actualPerson = personDao.getById(expectedPerson.getId());
+ assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
+ }
+
+ @DisplayName("удалять заданного пёрсона по его id")
+ @Test
+ void shouldCorrectDeletePersonById() {
+ assertThatCode(() -> personDao.getById(EXISTING_PERSON_ID))
+ .doesNotThrowAnyException();
+
+ personDao.deleteById(EXISTING_PERSON_ID);
+
+ assertThatThrownBy(() -> personDao.getById(EXISTING_PERSON_ID))
+ .isInstanceOf(EmptyResultDataAccessException.class);
+ }
+
+ @DisplayName("возвращать ожидаемый список пёрсонов")
+ @Test
+ void shouldReturnExpectedPersonsList() {
+ Person expectedPerson = new Person(EXISTING_PERSON_ID, EXISTING_PERSON_NAME);
+ List actualPersonList = personDao.getAll();
+ assertThat(actualPersonList)
+ .containsExactlyInAnyOrder(expectedPerson);
+ }
+}
\ No newline at end of file
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/application.yml b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/application.yml
new file mode 100644
index 00000000..2694a3e5
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/application.yml
@@ -0,0 +1,10 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ #initialization-mode: always
+ #data: data.sql
+ sql:
+ init:
+ mode: always
+ data-locations: data.sql
+ schema-locations: schema.sql
diff --git a/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/data.sql b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/data.sql
new file mode 100644
index 00000000..db9a08f8
--- /dev/null
+++ b/2025-07/spring-09-jdbc/jdbc-demo-solution-final/src/test/resources/data.sql
@@ -0,0 +1,2 @@
+insert into persons (id, `name`)
+values (1, 'Ivan');
diff --git a/2025-07/spring-09-jdbc/pom.xml b/2025-07/spring-09-jdbc/pom.xml
new file mode 100644
index 00000000..e6bcd817
--- /dev/null
+++ b/2025-07/spring-09-jdbc/pom.xml
@@ -0,0 +1,53 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.3
+
+
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+ pom
+
+
+ jdbc-demo-exercise
+ jdbc-demo-solution-3
+ jdbc-demo-solution-4
+ jdbc-demo-solution-5
+ jdbc-demo-solution-final
+
+
+
+ 17
+ 17
+ 2.2.220
+ 2.0
+
+
+
+
+
+
+
+ org.yaml
+ snakeyaml
+ ${snakeyaml.version}
+
+
+
+ com.h2database
+ h2
+ ${h2.version}
+
+
+
+
+