diff --git a/2022-11/spring-10-orm/demo-projects/.gitignore b/2022-11/spring-10-orm/demo-projects/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/.gitignore b/2022-11/spring-10-orm/demo-projects/mybatis-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/.gitignore
@@ -0,0 +1,29 @@
+HELP.md
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+### VS Code ###
+.vscode/
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/README.md b/2022-11/spring-10-orm/demo-projects/mybatis-demo/README.md
new file mode 100644
index 00000000..44635273
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/README.md
@@ -0,0 +1,2 @@
+# mybatis-demo
+Пример работы с БД через MyBatis
\ No newline at end of file
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/pom.xml b/2022-11/spring-10-orm/demo-projects/mybatis-demo/pom.xml
new file mode 100644
index 00000000..95f22b02
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/pom.xml
@@ -0,0 +1,61 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.6
+
+
+ ru.otus.example
+ mybatis-demo
+ 0.0.1-SNAPSHOT
+ mybatis-demo
+ MyBatis demo
+
+
+ 11
+ 11
+ 11
+ 2.2.2
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ ${mybatis.version}
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java
new file mode 100644
index 00000000..ab5f7513
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java
@@ -0,0 +1,14 @@
+package ru.otus.example.mybatisdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+@SpringBootApplication
+public class MyBatisDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(MyBatisDemoApplication.class, args);
+ }
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java
new file mode 100644
index 00000000..5c8f4728
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java
@@ -0,0 +1,13 @@
+package ru.otus.example.mybatisdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Avatar {
+ private long id;
+ private String photoUrl;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java
new file mode 100644
index 00000000..6aa8cff7
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java
@@ -0,0 +1,13 @@
+package ru.otus.example.mybatisdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Course {
+ private long id;
+ private String name;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java
new file mode 100644
index 00000000..8fa43ebd
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java
@@ -0,0 +1,13 @@
+package ru.otus.example.mybatisdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class EMail {
+ private long id;
+ private String email;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java
new file mode 100644
index 00000000..afb324fb
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java
@@ -0,0 +1,18 @@
+package ru.otus.example.mybatisdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OtusStudent {
+ private long id;
+ private String name;
+ private Avatar avatar;
+ private List emails;
+ private List courses;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java
new file mode 100644
index 00000000..634d1aba
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java
@@ -0,0 +1,18 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Result;
+import org.apache.ibatis.annotations.Results;
+import org.apache.ibatis.annotations.Select;
+import ru.otus.example.mybatisdemo.models.Avatar;
+
+@Mapper
+public interface AvatarRepository {
+ @Select("select * from avatars where id = #{id}")
+ @Results(value = {
+ @Result(property = "id", column = "id"),
+ @Result(property = "photoUrl", column = "photo_url")
+ })
+ Avatar getAvatarById(long id);
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java
new file mode 100644
index 00000000..20271981
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java
@@ -0,0 +1,17 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import ru.otus.example.mybatisdemo.models.Course;
+
+import java.util.List;
+
+@Mapper
+public interface CourseRepository {
+
+ @Select("select * " +
+ "from student_courses sc left join courses c on sc.course_id = c.id " +
+ "where sc.student_id = #{studentId}")
+ List getCoursesByStudentId(long studentId);
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java
new file mode 100644
index 00000000..989b681d
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java
@@ -0,0 +1,14 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Select;
+import ru.otus.example.mybatisdemo.models.EMail;
+
+import java.util.List;
+
+@Mapper
+public interface EmailRepository {
+
+ @Select("select * from emails where student_id = #{studentId}")
+ List getEmailsByStudentId(long studentId);
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java
new file mode 100644
index 00000000..82a8260b
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java
@@ -0,0 +1,42 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import org.apache.ibatis.annotations.*;
+import org.apache.ibatis.mapping.FetchType;
+import ru.otus.example.mybatisdemo.models.Avatar;
+import ru.otus.example.mybatisdemo.models.OtusStudent;
+
+import java.util.List;
+
+@Mapper
+public interface OtusStudentRepository {
+
+ @Select("select * from otus_students")
+ @Results(id = "studentAllMap", value = {
+ @Result(property = "id", column = "id"),
+ @Result(property = "name", column = "name"),
+ @Result(property = "avatar", column = "avatar_id", javaType = Avatar.class,
+ one = @One(select = "ru.otus.example.mybatisdemo.repositories.AvatarRepository.getAvatarById", fetchType = FetchType.EAGER)),
+ @Result(property = "emails", column = "id", javaType = List.class,
+ many = @Many(select = "ru.otus.example.mybatisdemo.repositories.EmailRepository.getEmailsByStudentId", fetchType = FetchType.EAGER)),
+ @Result(property = "courses", column = "id", javaType = List.class,
+ many = @Many(select = "ru.otus.example.mybatisdemo.repositories.CourseRepository.getCoursesByStudentId", fetchType = FetchType.EAGER))
+ })
+ List findAllWithAllInfo();
+
+ @Select("select * from otus_students where id = #{id}")
+ @ResultMap("studentAllMap")
+ OtusStudent findById(long id);
+
+ @Select("select count(*) as students_count from otus_students")
+ long getStudentsCount();
+
+ @Insert("insert into otus_students(name, avatar_id) values (#{name}, #{avatar.id})")
+ void insert(OtusStudent student);
+
+ @Update("update otus_students set name = #{name} where id = #{id}")
+ void updateName(OtusStudent student);
+
+ @Delete("delete from otus_students where id = #{id}")
+ void deleteById(long id);
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/resources/schema.sql b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/main/resources/schema.sql
@@ -0,0 +1,31 @@
+create table avatars(
+ id bigserial,
+ photo_url varchar(8000),
+ primary key (id)
+);
+
+create table courses(
+ id bigserial,
+ name varchar(255),
+ primary key (id)
+);
+
+create table otus_students(
+ id bigserial,
+ name varchar(255),
+ avatar_id bigint references avatars (id),
+ primary key (id)
+);
+
+create table emails(
+ id bigserial,
+ student_id bigint references otus_students(id) on delete cascade,
+ email varchar(255),
+ primary key (id)
+);
+
+create table student_courses(
+ student_id bigint references otus_students(id) on delete cascade,
+ course_id bigint references courses(id),
+ primary key (student_id, course_id)
+);
\ No newline at end of file
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
new file mode 100644
index 00000000..d9a24bce
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
@@ -0,0 +1,110 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import lombok.val;
+import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.transaction.annotation.Transactional;
+import ru.otus.example.mybatisdemo.models.Avatar;
+import ru.otus.example.mybatisdemo.models.OtusStudent;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Репозиторий на основе MyBatis для работы со студентами ")
+@SpringBootTest
+@Transactional
+public class OtusStudentRepositoryTest {
+
+ private static final String FIELD_ID = "id";
+ private static final String FIELD_PHOTO_URL = "photoUrl";
+ private static final String FIELD_NAME = "name";
+
+ private static final long FIRST_STUDENT_ID = 1L;
+ private static final long FIRST_AVATAR_ID = 1L;
+ private static final String FIRST_STUDENT_NAME = "student_01";
+ private static final String FIRST_AVATAR_URL = "photoUrl_01";
+ private static final String STUDENT_NEW_NAME = "Висусуалий";
+
+ private static final int EXPECTED_NUMBER_OF_STUDENTS = 10;
+ private static final long INSERTED_STUDENT_ID = 11L;
+ private static final int EXPECTED_EMAILS_COUNT = 2;
+ private static final int EXPECTED_COURSES_COUNT = 3;
+
+ @Autowired
+ private OtusStudentRepository studentRepositoryMyBatis;
+
+ @DisplayName("должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void shouldReturnCorrectStudentsListWithAllInfo() {
+ val students = studentRepositoryMyBatis.findAllWithAllInfo();
+ assertThat(students).isNotNull().hasSize(EXPECTED_NUMBER_OF_STUDENTS)
+ .allMatch(s -> !s.getName().equals(""))
+ .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
+ .allMatch(s -> s.getAvatar() != null)
+ .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
+ }
+
+ @DisplayName("должен загружать число студентов в БД")
+ @Test
+ void shouldReturnCorrectStudentsCount() {
+ long studentsCount = studentRepositoryMyBatis.getStudentsCount();
+ assertThat(studentsCount).isEqualTo(EXPECTED_NUMBER_OF_STUDENTS);
+ }
+
+ @DisplayName(" должен загружать информацию о нужном студенте")
+ @Test
+ void shouldFindExpectedStudentById(){
+ val actualStudent = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
+
+ assertThat(actualStudent).isNotNull();
+ assertThat(actualStudent.getName()).isEqualTo(FIRST_STUDENT_NAME);
+ assertThat(actualStudent.getAvatar()).isNotNull()
+ .hasFieldOrPropertyWithValue(FIELD_ID, FIRST_STUDENT_ID)
+ .hasFieldOrPropertyWithValue(FIELD_PHOTO_URL, FIRST_AVATAR_URL);
+ assertThat(actualStudent.getEmails()).isNotNull().hasSize(EXPECTED_EMAILS_COUNT);
+ assertThat(actualStudent.getCourses()).isNotNull().hasSize(EXPECTED_COURSES_COUNT);
+ }
+
+ @DisplayName(" должен сохранить, а потом загрузить информацию о нужном студенте")
+ @Test
+ void shouldSaveAndLoadCorrectStudent() {
+ val expectedStudent = new OtusStudent(0, STUDENT_NEW_NAME,
+ new Avatar(FIRST_AVATAR_ID, FIRST_AVATAR_URL), List.of(), List.of());
+ studentRepositoryMyBatis.insert(expectedStudent);
+ val actualStudent = studentRepositoryMyBatis.findById(INSERTED_STUDENT_ID);
+
+ assertThat(actualStudent)
+ .isNotNull()
+ .usingRecursiveComparison(
+ RecursiveComparisonConfiguration.builder()
+ .withIgnoredFields(FIELD_ID).build())
+ .isEqualTo(expectedStudent);
+ }
+
+
+ @DisplayName(" должен обновлять имя студента в БД")
+ @Test
+ void shouldUpdateStudentName() {
+ val student = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
+ student.setName(STUDENT_NEW_NAME);
+ studentRepositoryMyBatis.updateName(student);
+ val actualStudent = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
+
+ assertThat(actualStudent).isNotNull().hasFieldOrPropertyWithValue(FIELD_NAME, student.getName());
+ }
+
+ @DisplayName("должен удалять студента из БД по id")
+ @Test
+ void shouldDeleteStudentFromDbById() {
+ val studentsCountBefore = studentRepositoryMyBatis.getStudentsCount();
+ studentRepositoryMyBatis.deleteById(FIRST_STUDENT_ID);
+ val studentsCountAfter = studentRepositoryMyBatis.getStudentsCount();
+
+ assertThat(studentsCountBefore - studentsCountAfter).isEqualTo(1);
+ }
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/application.yml b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..dc237b00
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ initialization-mode: always
+
+logging:
+ level:
+ ru.otus.example.mybatisdemo.repositories: TRACE
\ No newline at end of file
diff --git a/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/data.sql b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/mybatis-demo/src/test/resources/data.sql
@@ -0,0 +1,29 @@
+insert into avatars(photo_url)
+values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'),
+ ('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10');
+
+insert into courses(name)
+values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'),
+ ('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11');
+
+insert into otus_students(name, avatar_id)
+values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5),
+ ('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10);
+
+
+insert into emails(email, student_id)
+values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4),
+ ('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10);
+
+
+insert into student_courses(student_id, course_id)
+values (1, 1), (1, 2), (1, 3),
+ (2, 2), (2, 4), (2, 5),
+ (3, 3), (3, 6), (3, 7),
+ (4, 4), (4, 8), (4, 9),
+ (5, 5), (5, 10), (5, 1),
+ (6, 6), (6, 2), (6, 3),
+ (7, 7), (7, 4), (7, 5),
+ (8, 8), (8, 6), (8, 7),
+ (9, 9), (9, 8), (9, 10),
+ (10, 10), (10, 1), (10, 2);
diff --git a/2022-11/spring-10-orm/demo-projects/pom.xml b/2022-11/spring-10-orm/demo-projects/pom.xml
new file mode 100644
index 00000000..95c77325
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/pom.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ ru.otus
+ demo-projects
+ 1.0
+
+ pom
+
+
+ spring-jdbc-demo
+ spring-jpa-ineritance-demo
+ mybatis-demo
+
+
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/.gitignore b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/.gitignore
@@ -0,0 +1,29 @@
+HELP.md
+/target/
+!.mvn/wrapper/maven-wrapper.jar
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+/build/
+
+### VS Code ###
+.vscode/
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/README.md b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/README.md
new file mode 100644
index 00000000..462216c3
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/README.md
@@ -0,0 +1,2 @@
+# spring-jdbc-demo
+Пример работы с БД через jdbc
\ No newline at end of file
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/pom.xml b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/pom.xml
new file mode 100644
index 00000000..3d092849
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.6
+
+
+ ru.otus.example
+ spring-jdbc-demo
+ 0.0.1-SNAPSHOT
+ spring-jdbc-demo
+ Spring jdbc demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jdbc
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java
new file mode 100644
index 00000000..28ebfec4
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java
@@ -0,0 +1,13 @@
+package ru.otus.example.springjdbcdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringJdbcDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringJdbcDemoApplication.class, args);
+ }
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java
new file mode 100644
index 00000000..a1963ea4
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java
@@ -0,0 +1,13 @@
+package ru.otus.example.springjdbcdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Avatar {
+ private long id;
+ private String photoUrl;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java
new file mode 100644
index 00000000..07b6e2c2
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java
@@ -0,0 +1,13 @@
+package ru.otus.example.springjdbcdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class Course {
+ private long id;
+ private String name;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java
new file mode 100644
index 00000000..16985ad5
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java
@@ -0,0 +1,13 @@
+package ru.otus.example.springjdbcdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class EMail {
+ private long id;
+ private String email;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java
new file mode 100644
index 00000000..5ae9167c
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java
@@ -0,0 +1,18 @@
+package ru.otus.example.springjdbcdemo.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class OtusStudent {
+ private long id;
+ private String name;
+ private Avatar avatar;
+ private List emails;
+ private List courses;
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepository.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepository.java
new file mode 100644
index 00000000..e594ee5c
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepository.java
@@ -0,0 +1,9 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import ru.otus.example.springjdbcdemo.models.Course;
+
+import java.util.List;
+
+public interface CourseRepository {
+ List findAllUsed();
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java
new file mode 100644
index 00000000..10526ed5
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java
@@ -0,0 +1,35 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Repository;
+import ru.otus.example.springjdbcdemo.models.Course;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+@Repository
+@RequiredArgsConstructor
+public class CourseRepositoryJdbc implements CourseRepository {
+
+ private final JdbcOperations op;
+
+ @Override
+ public List findAllUsed() {
+ return op.query("select c.id, c.name " +
+ "from courses c inner join student_courses sc on c.id = sc.course_id " +
+ "group by c.id, c.name " +
+ "order by c.name", new CourseRowMapper());
+ }
+
+ private static class CourseRowMapper implements RowMapper {
+ @Override
+ public Course mapRow(ResultSet rs, int i) throws SQLException {
+ return new Course(rs.getLong(1), rs.getString(2));
+ }
+ }
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepository.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepository.java
new file mode 100644
index 00000000..0012af3d
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepository.java
@@ -0,0 +1,9 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import ru.otus.example.springjdbcdemo.models.OtusStudent;
+
+import java.util.List;
+
+public interface OtusStudentRepository {
+ List findAllWithAllInfo();
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java
new file mode 100644
index 00000000..5618b797
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java
@@ -0,0 +1,52 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.example.springjdbcdemo.models.Course;
+import ru.otus.example.springjdbcdemo.models.OtusStudent;
+import ru.otus.example.springjdbcdemo.repositories.ext.OtusStudentResultSetExtractor;
+import ru.otus.example.springjdbcdemo.repositories.ext.StudentCourseRelation;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+@Repository
+@RequiredArgsConstructor
+public class OtusStudentRepositoryJdbc implements OtusStudentRepository {
+
+ private final CourseRepository courseRepository;
+ private final JdbcOperations op;
+
+ @Override
+ public List findAllWithAllInfo() {
+ List courses = courseRepository.findAllUsed();
+ List relations = getAllRelations();
+ Map students =
+ op.query("select os.id, os.name, a.id avatar_id, a.photo_url, e.id email_id, e.email " +
+ "from (otus_students os left join avatars a on " +
+ "os.avatar_id = a.id) left join emails e on os.id = e.student_id",
+ new OtusStudentResultSetExtractor());
+
+ mergeStudentsInfo(students, courses, relations);
+ return new ArrayList<>(Objects.requireNonNull(students).values());
+ }
+
+ private List getAllRelations() {
+ return op.query("select student_id, course_id from student_courses sc order by student_id, course_id",
+ (rs, i) -> new StudentCourseRelation(rs.getLong(1), rs.getLong(2)));
+ }
+
+ private void mergeStudentsInfo(Map students, List courses,
+ List relations) {
+ Map coursesMap = courses.stream().collect(Collectors.toMap(Course::getId, Function.identity()));
+ relations.forEach(r -> {
+ if (students.containsKey(r.getStudentId()) && coursesMap.containsKey(r.getCourseId())) {
+ students.get(r.getStudentId()).getCourses().add(coursesMap.get(r.getCourseId()));
+ }
+ });
+ }
+
+
+}
diff --git a/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java
new file mode 100644
index 00000000..778a46b7
--- /dev/null
+++ b/2022-11/spring-10-orm/demo-projects/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java
@@ -0,0 +1,37 @@
+package ru.otus.example.springjdbcdemo.repositories.ext;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import ru.otus.example.springjdbcdemo.models.Avatar;
+import ru.otus.example.springjdbcdemo.models.EMail;
+import ru.otus.example.springjdbcdemo.models.OtusStudent;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class OtusStudentResultSetExtractor implements
+ ResultSetExtractor