diff --git a/2019-08/spring-05/application-events-demo/README.md b/2019-08/spring-05/application-events-demo/README.md
new file mode 100644
index 00000000..63e68847
--- /dev/null
+++ b/2019-08/spring-05/application-events-demo/README.md
@@ -0,0 +1,2 @@
+# application-events-demo
+Пример работы с событиями
\ No newline at end of file
diff --git a/2019-08/spring-05/beans-lifecycle-demo/README.md b/2019-08/spring-05/beans-lifecycle-demo/README.md
new file mode 100644
index 00000000..2c841b7e
--- /dev/null
+++ b/2019-08/spring-05/beans-lifecycle-demo/README.md
@@ -0,0 +1,2 @@
+# beans-lifecycle-demo
+Пример жизненного цикла бинов
\ No newline at end of file
diff --git a/2019-08/spring-05/beans-scopes-demo/README.md b/2019-08/spring-05/beans-scopes-demo/README.md
new file mode 100644
index 00000000..09665122
--- /dev/null
+++ b/2019-08/spring-05/beans-scopes-demo/README.md
@@ -0,0 +1,2 @@
+# beans-scopes-demo
+Пример работы со областью действия (Scope) бинов
\ No newline at end of file
diff --git a/2019-08/spring-05/conditional-and-profiles-demo/README.md b/2019-08/spring-05/conditional-and-profiles-demo/README.md
new file mode 100644
index 00000000..f596dfc9
--- /dev/null
+++ b/2019-08/spring-05/conditional-and-profiles-demo/README.md
@@ -0,0 +1,2 @@
+# conditional-and-profiles-demo
+Пример работы с профилями и автоконфигурациями
\ No newline at end of file
diff --git a/2019-08/spring-05/test-configuration-demo/README.md b/2019-08/spring-05/test-configuration-demo/README.md
new file mode 100644
index 00000000..00dadea2
--- /dev/null
+++ b/2019-08/spring-05/test-configuration-demo/README.md
@@ -0,0 +1,2 @@
+# test-configuration-demo
+Пример конфигурирования тестов
\ No newline at end of file
diff --git a/2019-08/spring-07/mybatis-demo/.gitignore b/2019-08/spring-07/mybatis-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/README.md b/2019-08/spring-07/mybatis-demo/README.md
new file mode 100644
index 00000000..44635273
--- /dev/null
+++ b/2019-08/spring-07/mybatis-demo/README.md
@@ -0,0 +1,2 @@
+# mybatis-demo
+Пример работы с БД через MyBatis
\ No newline at end of file
diff --git a/2019-08/spring-07/mybatis-demo/pom.xml b/2019-08/spring-07/mybatis-demo/pom.xml
new file mode 100644
index 00000000..b7f66cba
--- /dev/null
+++ b/2019-08/spring-07/mybatis-demo/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.8.RELEASE
+
+
+ ru.otus.example
+ mybatis-demo
+ 0.0.1-SNAPSHOT
+ mybatis-demo
+ MyBatis demo
+
+
+ 11
+
+
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 2.0.1
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java
new file mode 100644
index 00000000..ab5f7513
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java
new file mode 100644
index 00000000..5c8f4728
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java
new file mode 100644
index 00000000..6aa8cff7
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java
new file mode 100644
index 00000000..8fa43ebd
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java
new file mode 100644
index 00000000..afb324fb
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java
new file mode 100644
index 00000000..634d1aba
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java
new file mode 100644
index 00000000..96c77681
--- /dev/null
+++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java
@@ -0,0 +1,15 @@
+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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java
new file mode 100644
index 00000000..989b681d
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java
new file mode 100644
index 00000000..82a8260b
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/main/resources/schema.sql b/2019-08/spring-07/mybatis-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java b/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
new file mode 100644
index 00000000..76d28681
--- /dev/null
+++ b/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
@@ -0,0 +1,87 @@
+package ru.otus.example.mybatisdemo.repositories;
+
+import lombok.val;
+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 static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Репозиторий на основе MyBatis для работы со студентами ")
+@SpringBootTest
+@Transactional
+public class OtusStudentRepositoryTest {
+
+ @Autowired
+ private OtusStudentRepository studentRepositoryMyBatis;
+
+ @DisplayName("должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void shouldReturnCorrectStudentsListWithAllInfo() {
+ val students = studentRepositoryMyBatis.findAllWithAllInfo();
+ assertThat(students).isNotNull().hasSize(10).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(10);
+ }
+
+ @DisplayName(" должен загружать информацию о нужном студенте")
+ @Test
+ void shouldFindExpectedStudentById(){
+ val actualStudent = studentRepositoryMyBatis.findById(1L);
+
+ assertThat(actualStudent).isNotNull();
+ assertThat(actualStudent.getName()).isEqualTo("student_01");
+ assertThat(actualStudent.getAvatar()).isNotNull()
+ .hasFieldOrPropertyWithValue("id", 1L)
+ .hasFieldOrPropertyWithValue("photoUrl", "photoUrl_01");
+ assertThat(actualStudent.getEmails()).isNotNull().hasSize(2);
+ assertThat(actualStudent.getCourses()).isNotNull().hasSize(3);
+ }
+
+ @DisplayName(" должен сохранить, а потом загрузить информацию о нужном студенте")
+ @Test
+ void shouldSaveAndLoadCorrectStudent() {
+ val expectedStudent = new OtusStudent();
+ expectedStudent.setName("Vasya");
+ expectedStudent.setAvatar(new Avatar(1L, "photoUrl_01"));
+ studentRepositoryMyBatis.insert(expectedStudent);
+ val actualStudent = studentRepositoryMyBatis.findById(11L);
+
+ assertThat(actualStudent).isNotNull().isEqualToComparingOnlyGivenFields(expectedStudent, "name");
+ }
+
+
+ @DisplayName(" должен обновлять имя студента в БД")
+ @Test
+ void shouldUpdateStudentName() {
+ val student = studentRepositoryMyBatis.findById(1L);
+ student.setName("Висусуалий");
+ studentRepositoryMyBatis.updateName(student);
+ val actualStudent = studentRepositoryMyBatis.findById(1L);
+
+ assertThat(actualStudent).isNotNull().hasFieldOrPropertyWithValue("name", student.getName());
+ }
+
+ @DisplayName("должен удалять студента из БД по id")
+ @Test
+ void shouldDeleteStudentFromDbById() {
+ val studentsCountBefore = studentRepositoryMyBatis.getStudentsCount();
+ studentRepositoryMyBatis.deleteById(1L);
+ val studentsCountAfter = studentRepositoryMyBatis.getStudentsCount();
+
+ assertThat(studentsCountBefore - studentsCountAfter).isEqualTo(1);
+ }
+
+}
diff --git a/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml b/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..037f7eae
--- /dev/null
+++ b/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml
@@ -0,0 +1,18 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ initialization-mode: always
+
+ jpa:
+ generate-ddl: false
+ #generate-ddl: true
+ hibernate:
+ ddl-auto: none
+ #ddl-auto: create-drop
+
+ show-sql: true
+
+
+logging:
+ level:
+ ru.otus.example.mybatisdemo.repositories: TRACE
\ No newline at end of file
diff --git a/2019-08/spring-07/mybatis-demo/src/test/resources/data.sql b/2019-08/spring-07/mybatis-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/orm-demo/.gitignore b/2019-08/spring-07/orm-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-08/spring-07/orm-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/2019-08/spring-07/orm-demo/README.md b/2019-08/spring-07/orm-demo/README.md
new file mode 100644
index 00000000..a1e907ee
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/README.md
@@ -0,0 +1,2 @@
+# orm-demo
+Пример работы с БД через ORM
\ No newline at end of file
diff --git a/2019-08/spring-07/orm-demo/pom.xml b/2019-08/spring-07/orm-demo/pom.xml
new file mode 100644
index 00000000..5b8662a4
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.8.RELEASE
+
+
+ ru.otus.example
+ orm-demo
+ 0.0.1-SNAPSHOT
+ orm-demo
+ Orm demo
+
+
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+
+ org.eclipse.persistence
+ org.eclipse.persistence.jpa
+ 2.7.4
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java
new file mode 100644
index 00000000..83624381
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java
@@ -0,0 +1,16 @@
+package ru.otus.example.ormdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+import ru.otus.example.ormdemo.config.EclipseLinkJpaConfiguration;
+
+@SpringBootApplication
+//@Import(EclipseLinkJpaConfiguration.class)
+public class OrmDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(OrmDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java
new file mode 100644
index 00000000..f078b295
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java
@@ -0,0 +1,38 @@
+package ru.otus.example.ormdemo.config;
+
+import org.eclipse.persistence.config.PersistenceUnitProperties;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
+import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
+import org.springframework.transaction.jta.JtaTransactionManager;
+
+import javax.sql.DataSource;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration {
+ @Autowired
+ protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties,
+ ObjectProvider jtaTransactionManager,
+ ObjectProvider transactionManagerCustomizers) {
+ super(dataSource, properties, jtaTransactionManager, transactionManagerCustomizers);
+ }
+
+ @Override
+ protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
+ return new EclipseLinkJpaVendorAdapter();
+ }
+
+ @Override
+ protected Map getVendorProperties() {
+ HashMap map = new HashMap<>();
+ map.put(PersistenceUnitProperties.WEAVING, "false");
+ return map;
+ }
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java
new file mode 100644
index 00000000..bd7f4852
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java
@@ -0,0 +1,42 @@
+package ru.otus.example.ormdemo.models;
+
+import lombok.*;
+import ru.otus.example.ormdemo.models.common.Avatar;
+import ru.otus.example.ormdemo.models.common.Course;
+import ru.otus.example.ormdemo.models.common.EMail;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity // Указывает, что данный класс является сущностью
+@Table(name = "otus_students") // Задает имя таблицы, на которую будет отображаться сущность
+public class OtusStudent {
+ @Id // Позволяет указать какое поле является идентификатором
+ @GeneratedValue(strategy = GenerationType.IDENTITY) // Стратегия генерации идентификаторов
+ private long id;
+
+ // Задает имя и некоторые свойства поля таблицы, на которое будет отображаться поле сущности
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ // Указывает на связь между таблицами "один к одному"
+ @OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL)
+ // Задает поле, по которому происходит объединение с таблицей для хранения связанной сущности
+ @JoinColumn(name = "avatar_id")
+ private Avatar avatar;
+
+ // Указывает на связь между таблицами "один ко многим"
+ @OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
+ @JoinColumn(name = "student_id")
+ private List emails;
+
+ // Указывает на связь между таблицами "многие ко многим"
+ @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY /*, cascade = CascadeType.ALL*/)
+ // Задает таблицу связей между таблицами для хранения родительской и связанной сущностью
+ @JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"),
+ inverseJoinColumns = @JoinColumn(name = "course_id"))
+ private List courses;
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java
new file mode 100644
index 00000000..73750fe0
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java
@@ -0,0 +1,44 @@
+package ru.otus.example.ormdemo.models;
+
+import lombok.*;
+import org.hibernate.annotations.BatchSize;
+import org.hibernate.annotations.Fetch;
+import org.hibernate.annotations.FetchMode;
+import ru.otus.example.ormdemo.models.common.Avatar;
+import ru.otus.example.ormdemo.models.common.Course;
+import ru.otus.example.ormdemo.models.common.EMail;
+
+import javax.persistence.*;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "otus_students")
+@NamedEntityGraph(name = "OtusStudentWithAvatarAndEmails",
+ attributeNodes = {@NamedAttributeNode(value = "avatar"),
+ @NamedAttributeNode(value = "emails")})
+public class OtusStudentV2 {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+
+ @OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL)
+ @JoinColumn(name = "avatar_id")
+ private Avatar avatar;
+
+ @OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL)
+ @JoinColumn(name = "student_id")
+ private List emails;
+
+ //@BatchSize(size = 5)
+ //@Fetch(FetchMode.SUBSELECT)
+ @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY)
+ @JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"),
+ inverseJoinColumns = @JoinColumn(name = "course_id"))
+ private List courses;
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java
new file mode 100644
index 00000000..2ed2b5f8
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java
@@ -0,0 +1,21 @@
+package ru.otus.example.ormdemo.models.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "avatars")
+public class Avatar {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+
+ @Column(name = "photo_url", nullable = false, unique = true)
+ private String photoUrl;
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java
new file mode 100644
index 00000000..6ce8b993
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java
@@ -0,0 +1,21 @@
+package ru.otus.example.ormdemo.models.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "courses")
+public class Course {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+
+ @Column(name = "name", nullable = false, unique = true)
+ private String name;
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java
new file mode 100644
index 00000000..1bc8cf74
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java
@@ -0,0 +1,22 @@
+package ru.otus.example.ormdemo.models.common;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.*;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Entity
+@Table(name = "emails")
+public class EMail {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private long id;
+
+ @Column(name = "email", nullable = false, unique = true)
+ private String email;
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java
new file mode 100644
index 00000000..dc6d0811
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java
@@ -0,0 +1,7 @@
+package ru.otus.example.ormdemo.repositories;
+
+import ru.otus.example.ormdemo.models.common.Course;
+
+public interface CourseRepositoryJpa {
+ Course save(Course course);
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java
new file mode 100644
index 00000000..d4ce0bd8
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java
@@ -0,0 +1,18 @@
+package ru.otus.example.ormdemo.repositories;
+
+import ru.otus.example.ormdemo.models.common.Course;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+public class CourseRepositoryJpaImpl implements CourseRepositoryJpa {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public Course save(Course course) {
+ em.persist(course);
+ return course;
+ }
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java
new file mode 100644
index 00000000..ba9eedda
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java
@@ -0,0 +1,14 @@
+package ru.otus.example.ormdemo.repositories;
+
+
+import ru.otus.example.ormdemo.models.OtusStudent;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface OtusStudentRepositoryJpa {
+ Optional findById(long id);
+ List findAll();
+ OtusStudent save(OtusStudent student);
+
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java
new file mode 100644
index 00000000..0f27448a
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java
@@ -0,0 +1,37 @@
+package ru.otus.example.ormdemo.repositories;
+
+import org.springframework.stereotype.Repository;
+import ru.otus.example.ormdemo.models.OtusStudent;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public class OtusStudentRepositoryJpaImpl implements OtusStudentRepositoryJpa {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public Optional findById(long id) {
+ return Optional.ofNullable(em.find(OtusStudent.class, id));
+ }
+
+ @Override
+ public List findAll() {
+ return em.createQuery("select s from OtusStudent s", OtusStudent.class).getResultList();
+ }
+
+ @Override
+ public OtusStudent save(OtusStudent student) {
+ if (student.getId() <= 0) {
+ em.persist(student);
+ //em.flush();
+ return student;
+ } else {
+ return em.merge(student);
+ }
+ }
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java
new file mode 100644
index 00000000..5893319f
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java
@@ -0,0 +1,11 @@
+package ru.otus.example.ormdemo.repositories;
+
+
+import ru.otus.example.ormdemo.models.OtusStudentV2;
+
+import java.util.List;
+
+public interface OtusStudentV2RepositoryJpa {
+ List findAllWithEntityGraph();
+ List findAllWithJoinFetch();
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java
new file mode 100644
index 00000000..df615f41
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java
@@ -0,0 +1,30 @@
+package ru.otus.example.ormdemo.repositories;
+
+import org.springframework.stereotype.Repository;
+import ru.otus.example.ormdemo.models.OtusStudentV2;
+
+import javax.persistence.EntityGraph;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+import java.util.List;
+
+@Repository
+public class OtusStudentV2RepositoryJpaImpl implements OtusStudentV2RepositoryJpa {
+
+ @PersistenceContext
+ private EntityManager em;
+
+ @Override
+ public List findAllWithEntityGraph() {
+ EntityGraph> entityGraph = em.getEntityGraph("OtusStudentWithAvatarAndEmails");
+ TypedQuery query = em.createQuery("select s from OtusStudentV2 s", OtusStudentV2.class);
+ query.setHint("javax.persistence.fetchgraph", entityGraph);
+ return query.getResultList();
+ }
+
+ @Override
+ public List findAllWithJoinFetch() {
+ return em.createQuery("select distinct s from OtusStudentV2 s join fetch s.avatar join fetch s.emails", OtusStudentV2.class).getResultList();
+ }
+}
diff --git a/2019-08/spring-07/orm-demo/src/main/resources/schema.sql b/2019-08/spring-07/orm-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2019-08/spring-07/orm-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/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java
new file mode 100644
index 00000000..6ce2aff9
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java
@@ -0,0 +1,85 @@
+package ru.otus.example.ormdemo.repositories;
+
+import lombok.val;
+import org.assertj.core.api.Assertions;
+import org.hibernate.Session;
+import org.hibernate.SessionFactory;
+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.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.context.annotation.Import;
+import ru.otus.example.ormdemo.models.OtusStudent;
+import ru.otus.example.ormdemo.models.common.Avatar;
+import ru.otus.example.ormdemo.models.common.Course;
+import ru.otus.example.ormdemo.models.common.EMail;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Репозиторий на основе Jpa для работы со студентами ")
+@DataJpaTest
+@Import({OtusStudentRepositoryJpaImpl.class, CourseRepositoryJpaImpl.class})
+class OtusStudentRepositoryJpaImplTest {
+
+ private static final int EXPECTED_QUERIES_COUNT = 31;
+
+ @Autowired
+ private OtusStudentRepositoryJpaImpl repositoryJpa;
+
+ @Autowired
+ private CourseRepositoryJpaImpl courseRepositoryJpa;
+
+ @Autowired
+ private TestEntityManager em;
+
+ @DisplayName(" должен загружать информацию о нужном студенте")
+ @Test
+ void shouldFindExpectedStudentById() {
+ val optionalActualStudent = repositoryJpa.findById(1L);
+ val expectedStudent = em.find(OtusStudent.class, 1L);
+ assertThat(optionalActualStudent).isPresent().get().isEqualToComparingFieldByFieldRecursively(expectedStudent);
+ }
+
+ @DisplayName("должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void shouldReturnCorrectStudentsListWithAllInfo() {
+ SessionFactory sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class);
+ sessionFactory.getStatistics().setStatisticsEnabled(true);
+
+
+ System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
+ val students = repositoryJpa.findAll();
+ assertThat(students).isNotNull().hasSize(10).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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
+ }
+
+ @DisplayName(" должен корректно сохранять всю информацию о студенте")
+ @Test
+ void shouldSaveAllStudentInfo() {
+ val avatar = new Avatar(0, "где-то там");
+ val email = new EMail(0, "any@mail.com");
+ val emails = Collections.singletonList(email);
+
+ val course = new Course(0, "Spring");
+ val courses = Collections.singletonList(course);
+ //courseRepositoryJpa.save(course);
+
+
+ val vasya = new OtusStudent(- 1, "Vasya", avatar, emails, courses);
+ repositoryJpa.save(vasya);
+ assertThat(vasya.getId()).isGreaterThan(0);
+
+ val actualStudent = em.find(OtusStudent.class, vasya.getId());
+ assertThat(actualStudent).isNotNull().matches(s -> !s.getName().equals(""))
+ .matches(s -> s.getCourses() != null && s.getCourses().size() > 0 && s.getCourses().get(0).getId() > 0)
+ .matches(s -> s.getAvatar() != null)
+ .matches(s -> s.getEmails() != null && s.getEmails().size() > 0);
+ }
+}
\ No newline at end of file
diff --git a/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java
new file mode 100644
index 00000000..c23ff494
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java
@@ -0,0 +1,67 @@
+package ru.otus.example.ormdemo.repositories;
+
+import org.hibernate.SessionFactory;
+import org.junit.jupiter.api.BeforeEach;
+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.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.context.annotation.Import;
+import ru.otus.example.ormdemo.models.OtusStudentV2;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Репозиторий v2 на основе Jpa для работы со студентами ")
+@DataJpaTest
+@Import({OtusStudentV2RepositoryJpaImpl.class})
+class OtusStudentV2RepositoryJpaImplTest {
+
+ private static final int EXPECTED_QUERIES_COUNT = 11;
+ //private static final int EXPECTED_QUERIES_COUNT = 2;
+ //private static final int EXPECTED_QUERIES_COUNT = 3;
+
+ @Autowired
+ private OtusStudentV2RepositoryJpaImpl repositoryJpa;
+
+ @Autowired
+ private TestEntityManager em;
+
+ private SessionFactory sessionFactory;
+
+ @BeforeEach
+ void setUp() {
+ sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class);
+ sessionFactory.getStatistics().setStatisticsEnabled(true);
+ sessionFactory.getStatistics().clear();
+ }
+
+ @DisplayName(" с помощью EntityGraph должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void usingEntityGraphShouldReturnCorrectStudentsListWithWithAllInfo() {
+ System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
+ List students = repositoryJpa.findAllWithEntityGraph();
+ assertThat(students).isNotNull().hasSize(10).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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
+
+ }
+
+ @DisplayName(" с помощью 'join fetch' должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void usingJoinFetchShouldReturnCorrectStudentsListWithWithAllInfo() {
+ System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
+ List students = repositoryJpa.findAllWithJoinFetch();
+ assertThat(students).isNotNull().hasSize(10).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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
+ }
+}
\ No newline at end of file
diff --git a/2019-08/spring-07/orm-demo/src/test/resources/application.yml b/2019-08/spring-07/orm-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..b98cd926
--- /dev/null
+++ b/2019-08/spring-07/orm-demo/src/test/resources/application.yml
@@ -0,0 +1,18 @@
+spring:
+ datasource:
+ url: jdbc:h2:mem:testdb
+ initialization-mode: always
+
+ jpa:
+ show-sql: false
+ generate-ddl: false
+ #generate-ddl: true
+ hibernate:
+ ddl-auto: none
+ #ddl-auto: create-drop
+
+ #show-sql: true
+
+logging:
+ level:
+ ROOT: ERROR
\ No newline at end of file
diff --git a/2019-08/spring-07/orm-demo/src/test/resources/data.sql b/2019-08/spring-07/orm-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2019-08/spring-07/orm-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/2019-08/spring-07/spring-jdbc-demo/.gitignore b/2019-08/spring-07/spring-jdbc-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/README.md b/2019-08/spring-07/spring-jdbc-demo/README.md
new file mode 100644
index 00000000..462216c3
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/README.md
@@ -0,0 +1,2 @@
+# spring-jdbc-demo
+Пример работы с БД через jdbc
\ No newline at end of file
diff --git a/2019-08/spring-07/spring-jdbc-demo/pom.xml b/2019-08/spring-07/spring-jdbc-demo/pom.xml
new file mode 100644
index 00000000..48052d83
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.8.RELEASE
+
+
+ ru.otus.example
+ spring-jdbc-demo
+ 0.0.1-SNAPSHOT
+ spring-jdbc-demo
+ Spring jdbc demo
+
+
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java
new file mode 100644
index 00000000..28ebfec4
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java
new file mode 100644
index 00000000..a1963ea4
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java
new file mode 100644
index 00000000..07b6e2c2
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java
new file mode 100644
index 00000000..16985ad5
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java
new file mode 100644
index 00000000..5ae9167c
--- /dev/null
+++ b/2019-08/spring-07/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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java
new file mode 100644
index 00000000..716ab880
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java
@@ -0,0 +1,9 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import ru.otus.example.springjdbcdemo.models.Course;
+
+import java.util.List;
+
+public interface CourseRepositoryJdbc {
+ List findAllUsed();
+}
diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java
new file mode 100644
index 00000000..aedb3ad5
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java
@@ -0,0 +1,36 @@
+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 CourseRepositoryJdbcImpl implements CourseRepositoryJdbc {
+
+ @Autowired
+ 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 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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java
new file mode 100644
index 00000000..0761373d
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java
@@ -0,0 +1,9 @@
+package ru.otus.example.springjdbcdemo.repositories;
+
+import ru.otus.example.springjdbcdemo.models.OtusStudent;
+
+import java.util.List;
+
+public interface OtusStudentRepositoryJdbc {
+ List findAllWithAllInfo();
+}
diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java
new file mode 100644
index 00000000..05208672
--- /dev/null
+++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java
@@ -0,0 +1,49 @@
+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.stream.Collectors;
+
+@Repository
+@RequiredArgsConstructor
+public class OtusStudentRepositoryJdbcImpl implements OtusStudentRepositoryJdbc {
+
+ private final CourseRepositoryJdbc 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, c -> c));
+ 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/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java
new file mode 100644
index 00000000..778a46b7
--- /dev/null
+++ b/2019-08/spring-07/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