diff --git a/2019-11/spring-05/advanced-config-class-work/.gitignore b/2019-11/spring-05/advanced-config-class-work/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/.gitignore b/2019-11/spring-05/advanced-config-class-work/application-events-demo/.gitignore
new file mode 100644
index 00000000..789ddc9e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/.gitignore
@@ -0,0 +1,32 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#other
+*.bat
+*/.idea
+*.iml
+*/target
+
+.idea
+*.iml
+target
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/README.md b/2019-11/spring-05/advanced-config-class-work/application-events-demo/README.md
new file mode 100644
index 00000000..63e68847
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/README.md
@@ -0,0 +1,2 @@
+# application-events-demo
+Пример работы с событиями
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/pom.xml b/2019-11/spring-05/advanced-config-class-work/application-events-demo/pom.xml
new file mode 100644
index 00000000..b5e569e6
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.7.RELEASE
+
+
+
+ ru.otus.example
+ application-events-demo
+ 0.0.1-SNAPSHOT
+ application-events-demo
+ Application events demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ 2.0.1.RELEASE
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+ junit
+ junit
+
+
+
+
+
+ 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-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/ApplicationEventsDemoApplication.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/ApplicationEventsDemoApplication.java
new file mode 100644
index 00000000..5b8d7ef3
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/ApplicationEventsDemoApplication.java
@@ -0,0 +1,26 @@
+package ru.otus.example.applicationeventsdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+import org.springframework.context.event.ApplicationEventMulticaster;
+import org.springframework.context.event.SimpleApplicationEventMulticaster;
+import org.springframework.core.task.SimpleAsyncTaskExecutor;
+
+@SpringBootApplication
+public class ApplicationEventsDemoApplication {
+
+ //@Bean @Primary
+ //@Bean(name = "applicationEventMulticaster")
+ public ApplicationEventMulticaster applicationEventMulticaster() {
+ SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster();
+ eventMulticaster.setTaskExecutor(new SimpleAsyncTaskExecutor());
+ return eventMulticaster;
+ }
+
+ public static void main(String[] args) {
+ SpringApplication.run(ApplicationEventsDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/EventsPublisher.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/EventsPublisher.java
new file mode 100644
index 00000000..2274570a
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/EventsPublisher.java
@@ -0,0 +1,5 @@
+package ru.otus.example.applicationeventsdemo.events;
+
+public interface EventsPublisher {
+ void publish();
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEvent.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEvent.java
new file mode 100644
index 00000000..22bab552
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEvent.java
@@ -0,0 +1,15 @@
+package ru.otus.example.applicationeventsdemo.events;
+
+import lombok.Getter;
+import org.springframework.context.ApplicationEvent;
+
+public class HalfAGlassOfWaterEvent extends ApplicationEvent {
+
+ @Getter
+ private final String payload;
+
+ public HalfAGlassOfWaterEvent(Object source) {
+ super(source);
+ payload = "Осталось половина стакана воды!!!";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEventPublisher.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEventPublisher.java
new file mode 100644
index 00000000..a1186fcc
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/HalfAGlassOfWaterEventPublisher.java
@@ -0,0 +1,17 @@
+package ru.otus.example.applicationeventsdemo.events;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class HalfAGlassOfWaterEventPublisher implements EventsPublisher {
+
+ private final ApplicationEventPublisher publisher;
+
+ @Override
+ public void publish() {
+ publisher.publishEvent(new HalfAGlassOfWaterEvent(this));
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/NegativeRespondent.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/NegativeRespondent.java
new file mode 100644
index 00000000..ce6115c9
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/NegativeRespondent.java
@@ -0,0 +1,18 @@
+package ru.otus.example.applicationeventsdemo.events;
+
+import lombok.SneakyThrows;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class NegativeRespondent implements ApplicationListener {
+
+ @SneakyThrows
+ @Override
+ public void onApplicationEvent(HalfAGlassOfWaterEvent halfAGlassOfWaterEvent) {
+ Thread.sleep(100);
+ System.out.println("Негативно настроенный слушатель");
+ System.out.println(String.format("- %s", halfAGlassOfWaterEvent.getPayload()));
+ System.out.println("- Какой ужас. Теперь он наполовину пуст!!!\n\n");
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/PositiveRespondent.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/PositiveRespondent.java
new file mode 100644
index 00000000..d9071026
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/events/PositiveRespondent.java
@@ -0,0 +1,18 @@
+package ru.otus.example.applicationeventsdemo.events;
+
+import lombok.SneakyThrows;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class PositiveRespondent implements ApplicationListener {
+
+ @SneakyThrows
+ @Override
+ public void onApplicationEvent(HalfAGlassOfWaterEvent halfAGlassOfWaterEvent) {
+ Thread.sleep(100);
+ System.out.println("Позитивно настроенный слушатель");
+ System.out.println(String.format("- %s", halfAGlassOfWaterEvent.getPayload()));
+ System.out.println("- Ничего. Главное, что он наполовину полон!!!\n\n");
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommands.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommands.java
new file mode 100644
index 00000000..482e9855
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommands.java
@@ -0,0 +1,35 @@
+package ru.otus.example.applicationeventsdemo.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.Availability;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import org.springframework.shell.standard.ShellMethodAvailability;
+import org.springframework.shell.standard.ShellOption;
+import ru.otus.example.applicationeventsdemo.events.EventsPublisher;
+
+@ShellComponent
+@RequiredArgsConstructor
+public class ApplicationEventsCommands {
+
+ private final EventsPublisher eventsPublisher;
+
+ private String userName;
+
+ @ShellMethod(value = "Login command", key = {"l", "login"})
+ public String login(@ShellOption(defaultValue = "stvort") String userName) {
+ this.userName = userName;
+ return String.format("Добро пожаловать: %s", userName);
+ }
+
+ @ShellMethod(value = "Publish event command", key = {"p", "pub", "publish"})
+ @ShellMethodAvailability(value = "isPublishEventCommandAvailable")
+ public String publishEvent() {
+ eventsPublisher.publish();
+ return "Событие опубликовано";
+ }
+
+ private Availability isPublishEventCommandAvailable() {
+ return userName == null? Availability.unavailable("Сначала залогиньтесь"): Availability.available();
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/resources/application.yml
new file mode 100644
index 00000000..f78a9f36
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/main/resources/application.yml
@@ -0,0 +1,3 @@
+logging:
+ level:
+ root: ERROR
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommandsTest.java b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommandsTest.java
new file mode 100644
index 00000000..ee3c8d0f
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/java/ru/otus/example/applicationeventsdemo/shell/ApplicationEventsCommandsTest.java
@@ -0,0 +1,67 @@
+package ru.otus.example.applicationeventsdemo.shell;
+
+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.boot.test.mock.mockito.MockBean;
+import org.springframework.shell.Availability;
+import org.springframework.shell.CommandNotCurrentlyAvailable;
+import org.springframework.shell.Shell;
+import org.springframework.test.annotation.DirtiesContext;
+import ru.otus.example.applicationeventsdemo.events.EventsPublisher;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@DisplayName("Тест команд shell ")
+@SpringBootTest
+class ApplicationEventsCommandsTest {
+
+ @MockBean
+ private EventsPublisher eventsPublisher;
+
+ @Autowired
+ private Shell shell;
+
+ private static final String GREETING_PATTERN = "Добро пожаловать: %s";
+ private static final String DEFAULT_LOGIN = "stvort";
+ private static final String CUSTOM_LOGIN = "Вася";
+ private static final String COMMAND_LOGIN = "login";
+ private static final String COMMAND_LOGIN_SHORT = "l";
+ private static final String COMMAND_PUBLISH = "publish";
+ private static final String COMMAND_PUBLISH_EXPECTED_RESULT = "Событие опубликовано";
+ private static final String COMMAND_LOGIN_PATTERN = "%s %s";
+
+ @DisplayName(" должен возвращать приветствие для всех форм команды логина")
+ @Test
+ void shouldReturnExpectedGreetingAfterLoginCommandEvaluated() {
+ String res = (String) shell.evaluate(() -> COMMAND_LOGIN);
+ assertThat(res).isEqualTo(String.format(GREETING_PATTERN, DEFAULT_LOGIN));
+
+ res = (String) shell.evaluate(() -> COMMAND_LOGIN_SHORT);
+ assertThat(res).isEqualTo(String.format(GREETING_PATTERN, DEFAULT_LOGIN));
+
+ res = (String) shell.evaluate(() -> String.format(COMMAND_LOGIN_PATTERN, COMMAND_LOGIN_SHORT, CUSTOM_LOGIN));
+ assertThat(res).isEqualTo(String.format(GREETING_PATTERN, CUSTOM_LOGIN));
+ }
+
+ @DisplayName(" должен возвращать CommandNotCurrentlyAvailable если при попытке выполнения команды publish пользователь выполнил вход")
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @Test
+ void shouldReturnCommandNotCurrentlyAvailableObjectWhenUserDoesNotLoginAfterPublishCommandEvaluated() {
+ Object res = shell.evaluate(() -> COMMAND_PUBLISH);
+ assertThat(res).isInstanceOf(CommandNotCurrentlyAvailable.class);
+ }
+
+ @DisplayName(" должен возвращать статус выполнения команды publish и вызвать соответствующий метод сервиса есл икоманда выполнена после входа")
+ @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD)
+ @Test
+ void shouldReturnExpectedMessageAndFirePublishMethodAfterPublishCommandEvaluated() {
+ shell.evaluate(() -> COMMAND_LOGIN);
+ String res = (String) shell.evaluate(() -> COMMAND_PUBLISH);
+ assertThat(res).isEqualTo(COMMAND_PUBLISH_EXPECTED_RESULT);
+ verify(eventsPublisher, times(1)).publish();
+ }
+}
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..b4921681
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/application-events-demo/src/test/resources/application.yml
@@ -0,0 +1,4 @@
+spring:
+ shell:
+ interactive:
+ enabled: false
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/.gitignore b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/.gitignore
new file mode 100644
index 00000000..789ddc9e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/.gitignore
@@ -0,0 +1,32 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#other
+*.bat
+*/.idea
+*.iml
+*/target
+
+.idea
+*.iml
+target
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/README.md b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/README.md
new file mode 100644
index 00000000..2c841b7e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/README.md
@@ -0,0 +1,2 @@
+# beans-lifecycle-demo
+Пример жизненного цикла бинов
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/pom.xml b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/pom.xml
new file mode 100644
index 00000000..a9c46a30
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ ru.otus.example
+ beans-lifecycle-demo
+ 0.0.1-SNAPSHOT
+ beans-lifecycle-demo
+ Beans lifecycle demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ 2.0.1.RELEASE
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/BeansLifecycleDemoApplication.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/BeansLifecycleDemoApplication.java
new file mode 100644
index 00000000..d3da1418
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/BeansLifecycleDemoApplication.java
@@ -0,0 +1,13 @@
+package ru.otus.example.beanslifecycledemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BeansLifecycleDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BeansLifecycleDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/FriendPhoneNumber.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/FriendPhoneNumber.java
new file mode 100644
index 00000000..913d7db0
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/FriendPhoneNumber.java
@@ -0,0 +1,8 @@
+package ru.otus.example.beanslifecycledemo.domain;
+
+public class FriendPhoneNumber extends PhoneNumber {
+ @Override
+ public String getOwnerName() {
+ return "Друг";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/GirlfiendPhoneNumber.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/GirlfiendPhoneNumber.java
new file mode 100644
index 00000000..54e4e173
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/GirlfiendPhoneNumber.java
@@ -0,0 +1,11 @@
+package ru.otus.example.beanslifecycledemo.domain;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class GirlfiendPhoneNumber extends PhoneNumber {
+ @Override
+ public String getOwnerName() {
+ return "Подруга";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/Phone.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/Phone.java
new file mode 100644
index 00000000..b61d7fd7
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/Phone.java
@@ -0,0 +1,16 @@
+package ru.otus.example.beanslifecycledemo.domain;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class Phone {
+ private String greeting = "Погнали к родителям";
+
+ private final PhoneNumber favoriteNumber;
+
+ public void callFavoriteNumber() {
+ System.out.println(favoriteNumber.getOwnerName() + " " + greeting);
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/PhoneNumber.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/PhoneNumber.java
new file mode 100644
index 00000000..215bd09f
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/domain/PhoneNumber.java
@@ -0,0 +1,7 @@
+package ru.otus.example.beanslifecycledemo.domain;
+
+public abstract class PhoneNumber {
+ public String getOwnerName() {
+ return "Спорт-лото";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanFactoryPostProcessor.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanFactoryPostProcessor.java
new file mode 100644
index 00000000..5f82e6e5
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanFactoryPostProcessor.java
@@ -0,0 +1,32 @@
+package ru.otus.example.beanslifecycledemo.lifecycle;
+
+import org.springframework.beans.BeanMetadataAttribute;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.annotation.ScannedGenericBeanDefinition;
+import ru.otus.example.beanslifecycledemo.domain.FriendPhoneNumber;
+import ru.otus.example.beanslifecycledemo.domain.GirlfiendPhoneNumber;
+
+public class CustomBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
+ @Override
+ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
+
+ if (beanFactory.containsBean("customLifeCycleBean")) {
+ System.out.println("Шаг #1: BeanFactoryPostProcessor.postProcessBeanFactory\n");
+ }
+
+/*
+ for (String beanName : beanFactory.getBeanDefinitionNames()) {
+ BeanDefinition d = beanFactory.getBeanDefinition(beanName);
+
+ if (GirlfiendPhoneNumber.class.getName().equalsIgnoreCase(d.getBeanClassName())) {
+ d.setBeanClassName(FriendPhoneNumber.class.getName());
+ ((ScannedGenericBeanDefinition) d).addMetadataAttribute(new BeanMetadataAttribute("className", FriendPhoneNumber.class.getName()));
+ d.setAutowireCandidate(true);
+ }
+ }
+*/
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanPostProcessor.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanPostProcessor.java
new file mode 100644
index 00000000..9b9c5579
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomBeanPostProcessor.java
@@ -0,0 +1,40 @@
+package ru.otus.example.beanslifecycledemo.lifecycle;
+
+import lombok.SneakyThrows;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import ru.otus.example.beanslifecycledemo.domain.Phone;
+
+import java.lang.reflect.Field;
+
+public class CustomBeanPostProcessor implements BeanPostProcessor {
+ @Override
+ public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
+ if (bean.getClass().equals(CustomLifeCycleBean.class)) {
+ System.out.println("Шаг #5: BeanPostProcessor.postProcessBeforeInitialization\n");
+ }
+ return bean;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
+ if (bean.getClass().equals(CustomLifeCycleBean.class)) {
+ System.out.println("Шаг #9: BeanPostProcessor.postProcessAfterInitialization\n");
+ }
+
+ if (bean.getClass().isAssignableFrom(Phone.class)) {
+ //updateGreeting(bean);
+ }
+ return bean;
+ }
+
+ @SneakyThrows
+ private void updateGreeting(Object bean) {
+ Class aClass = Phone.class;
+ Field greetingField = aClass.getDeclaredField("greeting");
+
+ greetingField.setAccessible(true);
+ greetingField.setAccessible(true);
+ greetingField.set(bean, "Ай-да в гараж. Стихи читать!");
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomLifeCycleBean.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomLifeCycleBean.java
new file mode 100644
index 00000000..b94d3e99
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/CustomLifeCycleBean.java
@@ -0,0 +1,55 @@
+package ru.otus.example.beanslifecycledemo.lifecycle;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.*;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+public class CustomLifeCycleBean implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware, ApplicationContextAware {
+
+ @Override
+ public void setBeanName(String s) {
+ System.out.println("Шаг #2: BeanNameAware\n");
+ }
+
+ @Override
+ public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
+ System.out.println("Шаг #3: BeanFactoryAware\n");
+ }
+
+ @Override
+ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+ System.out.println("Шаг #4: ApplicationContextAware\n");
+ }
+
+ @PostConstruct
+ public void postConstruct() {
+ System.out.println("Шаг #6: @PostConstruct\n");
+ }
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ System.out.println("Шаг #7: InitializingBean.afterPropertiesSet\n");
+ }
+
+ public void customInitMethod() {
+ System.out.println("Шаг #8: CustomLifeCycleBean.customInitMethod\n");
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ System.out.println("Шаг #10: @PreDestroy\n");
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ System.out.println("Шаг #11: DisposableBean.destroy\n");
+ }
+
+ public void customDestroyMethod() {
+ System.out.println("Шаг #12: CustomLifeCycleBean.customDestroyMethod\n");
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/LifeCycleConfig.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/LifeCycleConfig.java
new file mode 100644
index 00000000..bd81fea0
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/lifecycle/LifeCycleConfig.java
@@ -0,0 +1,27 @@
+package ru.otus.example.beanslifecycledemo.lifecycle;
+
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class LifeCycleConfig {
+
+ @Bean
+ public BeanFactoryPostProcessor customBeanFactoryPostProcessor() {
+ return new CustomBeanFactoryPostProcessor();
+ }
+
+ @Bean
+ public BeanPostProcessor customBeanPostProcessor() {
+ return new CustomBeanPostProcessor();
+ }
+
+ @ConditionalOnProperty(name = "spring.shell.interactive.enabled", havingValue = "false")
+ @Bean(initMethod = "customInitMethod", destroyMethod = "customDestroyMethod")
+ public CustomLifeCycleBean customLifeCycleBean() {
+ return new CustomLifeCycleBean();
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/shell/LifecycleDemoCommands.java b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/shell/LifecycleDemoCommands.java
new file mode 100644
index 00000000..3987bc06
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/java/ru/otus/example/beanslifecycledemo/shell/LifecycleDemoCommands.java
@@ -0,0 +1,18 @@
+package ru.otus.example.beanslifecycledemo.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import ru.otus.example.beanslifecycledemo.domain.Phone;
+
+@RequiredArgsConstructor
+@ShellComponent
+public class LifecycleDemoCommands {
+
+ private final Phone phone;
+
+ @ShellMethod(value = "Call favorite number", key = {"call-favorite-number", "cfn"})
+ public void callFavoriteNumber() {
+ phone.callFavoriteNumber();
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/resources/application.yml
new file mode 100644
index 00000000..a50954b5
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-lifecycle-demo/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+logging:
+ level:
+ root: ERROR
+
+spring:
+ shell:
+ interactive:
+ enabled: false
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/.gitignore b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/.gitignore
new file mode 100644
index 00000000..789ddc9e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/.gitignore
@@ -0,0 +1,32 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#other
+*.bat
+*/.idea
+*.iml
+*/target
+
+.idea
+*.iml
+target
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/README.md b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/README.md
new file mode 100644
index 00000000..09665122
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/README.md
@@ -0,0 +1,2 @@
+# beans-scopes-demo
+Пример работы со областью действия (Scope) бинов
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/pom.xml b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/pom.xml
new file mode 100644
index 00000000..ea2d2db1
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ ru.otus.example
+ beans-scopes-demo
+ 0.0.1-SNAPSHOT
+ beans-scopes-demo
+ Beans scopes demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/BeansScopesDemoApplication.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/BeansScopesDemoApplication.java
new file mode 100644
index 00000000..a7ce4369
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/BeansScopesDemoApplication.java
@@ -0,0 +1,13 @@
+package ru.otus.example.beansscopesdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BeansScopesDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BeansScopesDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/controllers/GreetingController.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/controllers/GreetingController.java
new file mode 100644
index 00000000..1a309a90
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/controllers/GreetingController.java
@@ -0,0 +1,40 @@
+package ru.otus.example.beansscopesdemo.controllers;
+
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import ru.otus.example.beansscopesdemo.services.GreetingService;
+
+@Controller
+public class GreetingController {
+ private final GreetingService singletonGreetingService;
+ private final GreetingService prototypeGreetingService1;
+ private final GreetingService prototypeGreetingService2;
+ private final GreetingService sessionGreetingService;
+ private final GreetingService requestGreetingService;
+
+ public GreetingController(@Qualifier("SingletonGreetingService") GreetingService singletonGreetingService,
+ @Qualifier("PrototypeGreetingService")GreetingService prototypeGreetingService1,
+ @Qualifier("PrototypeGreetingService")GreetingService prototypeGreetingService2,
+ @Qualifier("SessionGreetingService")GreetingService sessionGreetingService,
+ @Qualifier("RequestGreetingService")GreetingService requestGreetingService
+ ) {
+ this.singletonGreetingService = singletonGreetingService;
+ this.prototypeGreetingService1 = prototypeGreetingService1;
+ this.prototypeGreetingService2 = prototypeGreetingService2;
+ this.sessionGreetingService = sessionGreetingService;
+ this.requestGreetingService = requestGreetingService;
+ }
+
+ @GetMapping("/")
+ public String greetingPage(Model model) {
+ boolean isFirstGreetingSuccess = prototypeGreetingService1.isFirstGreetingSuccess();
+ model.addAttribute("singletonGreeting", singletonGreetingService.greeting());
+ model.addAttribute("sessionGreeting", sessionGreetingService.greeting());
+ model.addAttribute("requestGreeting", requestGreetingService.greeting());
+ model.addAttribute("prototype1Greeting", prototypeGreetingService1.greeting());
+ model.addAttribute("prototype2Greeting", isFirstGreetingSuccess? prototypeGreetingService2.greeting(): "Пока жду");
+ return "index";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/AbstractGreetingServiceImpl.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/AbstractGreetingServiceImpl.java
new file mode 100644
index 00000000..3fff2235
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/AbstractGreetingServiceImpl.java
@@ -0,0 +1,35 @@
+package ru.otus.example.beansscopesdemo.services;
+
+
+import org.springframework.beans.factory.annotation.Value;
+
+public abstract class AbstractGreetingServiceImpl implements GreetingService {
+
+ @Value("${greetings.first-greeting}")
+ private String firstGreeting;
+
+ @Value("${greetings.re-greeting}")
+ private String reGreeting;
+
+ private boolean isFirstGreetingSuccess;
+
+ public AbstractGreetingServiceImpl() {
+ this.isFirstGreetingSuccess = false;
+ }
+
+ @Override
+ public boolean isFirstGreetingSuccess() {
+ return isFirstGreetingSuccess;
+ }
+
+ @Override
+ public String greeting() {
+ return currentGreeting();
+ }
+
+ private synchronized String currentGreeting() {
+ String greeting = isFirstGreetingSuccess ? reGreeting : firstGreeting;
+ isFirstGreetingSuccess = true;
+ return greeting;
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/GreetingService.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/GreetingService.java
new file mode 100644
index 00000000..1347ff39
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/GreetingService.java
@@ -0,0 +1,6 @@
+package ru.otus.example.beansscopesdemo.services;
+
+public interface GreetingService {
+ boolean isFirstGreetingSuccess();
+ String greeting();
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/PrototypeGreetingServiceImpl.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/PrototypeGreetingServiceImpl.java
new file mode 100644
index 00000000..0932a66c
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/PrototypeGreetingServiceImpl.java
@@ -0,0 +1,9 @@
+package ru.otus.example.beansscopesdemo.services;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+@Scope("prototype")
+@Service("PrototypeGreetingService")
+public class PrototypeGreetingServiceImpl extends AbstractGreetingServiceImpl {
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/RequestGreetingServiceImpl.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/RequestGreetingServiceImpl.java
new file mode 100644
index 00000000..943c73db
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/RequestGreetingServiceImpl.java
@@ -0,0 +1,11 @@
+package ru.otus.example.beansscopesdemo.services;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.WebApplicationContext;
+
+@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Service("RequestGreetingService")
+public class RequestGreetingServiceImpl extends AbstractGreetingServiceImpl {
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SessionGreetingServiceImpl.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SessionGreetingServiceImpl.java
new file mode 100644
index 00000000..efff908a
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SessionGreetingServiceImpl.java
@@ -0,0 +1,11 @@
+package ru.otus.example.beansscopesdemo.services;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.context.annotation.ScopedProxyMode;
+import org.springframework.stereotype.Service;
+import org.springframework.web.context.WebApplicationContext;
+
+@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
+@Service("SessionGreetingService")
+public class SessionGreetingServiceImpl extends AbstractGreetingServiceImpl {
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SingletonGreetingServiceImpl.java b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SingletonGreetingServiceImpl.java
new file mode 100644
index 00000000..fb8406a5
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/java/ru/otus/example/beansscopesdemo/services/SingletonGreetingServiceImpl.java
@@ -0,0 +1,9 @@
+package ru.otus.example.beansscopesdemo.services;
+
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+@Scope("singleton")
+@Service("SingletonGreetingService")
+public class SingletonGreetingServiceImpl extends AbstractGreetingServiceImpl {
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/application.yml
new file mode 100644
index 00000000..a18bec79
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+greetings:
+ first-greeting: Привет
+ re-greeting: Рад снова тебя видеть
+
+
+logging:
+ level:
+ root: ERROR
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/templates/index.html b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/templates/index.html
new file mode 100644
index 00000000..3719f228
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/beans-scopes-demo/src/main/resources/templates/index.html
@@ -0,0 +1,45 @@
+
+
+
+
+ Advanced configuration demo
+
+
+
+
+
Демонстрация @Scope
+
+ - SingletonGreetingService должен говорить сначала "Привет", а потом все время "Рад снова тебя видеть"
+ - PrototypeGreetingService1 должен говорить сначала "Привет", а потом все время "Рад снова тебя видеть"
+ - PrototypeGreetingService2 должен говорить сначала "Пока жду", после обновления страницы "Привет", а потом все время "Рад снова тебя видеть"
+ - SessionGreetingService должен вести себя как SingletonGreetingService, но только до перезапуска браузера
+ - RequestGreetingService должен каждый раз говорить "Привет"
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/.gitignore b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/.gitignore
new file mode 100644
index 00000000..789ddc9e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/.gitignore
@@ -0,0 +1,32 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#other
+*.bat
+*/.idea
+*.iml
+*/target
+
+.idea
+*.iml
+target
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/README.md b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/README.md
new file mode 100644
index 00000000..f596dfc9
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/README.md
@@ -0,0 +1,2 @@
+# conditional-and-profiles-demo
+Пример работы с профилями и автоконфигурациями
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/pom.xml b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/pom.xml
new file mode 100644
index 00000000..eac2b65a
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/pom.xml
@@ -0,0 +1,83 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ ru.otus.example
+ conditional-and-profiles-demo
+ 0.0.1-SNAPSHOT
+ conditional-and-profiles-demo
+ Conditional and profiles demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+ junit
+ junit
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+ org.springframework.shell
+ spring-shell-starter
+ 2.0.1.RELEASE
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/ConditionalAndProfilesDemoApplication.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/ConditionalAndProfilesDemoApplication.java
new file mode 100644
index 00000000..96f37fd9
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/ConditionalAndProfilesDemoApplication.java
@@ -0,0 +1,13 @@
+package ru.otus.example.conditionalandprofilesdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class ConditionalAndProfilesDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ConditionalAndProfilesDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Alexey.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Alexey.java
new file mode 100644
index 00000000..66091903
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Alexey.java
@@ -0,0 +1,14 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+@ConditionalOnProperty(name = "condition.alexey-exists", havingValue = "true")
+@Component
+public class Alexey extends Friend {
+ @Override
+ public String getName() {
+ return "Алексей";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Anna.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Anna.java
new file mode 100644
index 00000000..be26e16d
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Anna.java
@@ -0,0 +1,14 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+@ConditionalOnBean(Alexey.class)
+@Component
+public class Anna extends Friend {
+ @Override
+ public String getName() {
+ return "Аня";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Oleg.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Oleg.java
new file mode 100644
index 00000000..2a11f476
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Oleg.java
@@ -0,0 +1,14 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+@Profile("Oleg")
+@Component
+public class Oleg extends Friend {
+ @Override
+ public String getName() {
+ return "Олег";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Peter.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Peter.java
new file mode 100644
index 00000000..435891a8
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Peter.java
@@ -0,0 +1,15 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+@Profile("Peter")
+@Component
+public class Peter extends Friend {
+
+ @Override
+ public String getName() {
+ return "Петр";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yana.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yana.java
new file mode 100644
index 00000000..76b0fca3
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yana.java
@@ -0,0 +1,16 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.context.annotation.Conditional;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+import ru.otus.example.conditionalandprofilesdemo.model.conditions.YanaConditions;
+
+
+@Conditional(YanaConditions.class)
+@Component
+public class Yana extends Friend {
+ @Override
+ public String getName() {
+ return "Яна";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yanis.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yanis.java
new file mode 100644
index 00000000..6dd2c14d
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/Yanis.java
@@ -0,0 +1,14 @@
+package ru.otus.example.conditionalandprofilesdemo.model;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+@ConditionalOnProperty(name = "condition.yanis-exists", havingValue = "true")
+@Component
+public class Yanis extends Friend {
+ @Override
+ public String getName() {
+ return "Янис";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/base/Friend.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/base/Friend.java
new file mode 100644
index 00000000..2d2eb67d
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/base/Friend.java
@@ -0,0 +1,5 @@
+package ru.otus.example.conditionalandprofilesdemo.model.base;
+
+public abstract class Friend {
+ public abstract String getName();
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/conditions/YanaConditions.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/conditions/YanaConditions.java
new file mode 100644
index 00000000..cf130ebb
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/model/conditions/YanaConditions.java
@@ -0,0 +1,20 @@
+package ru.otus.example.conditionalandprofilesdemo.model.conditions;
+
+import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+
+public class YanaConditions extends AllNestedConditions {
+
+ public YanaConditions() {
+ super(ConfigurationPhase.PARSE_CONFIGURATION);
+ }
+
+
+ @ConditionalOnProperty(name = "condition.alexey-exists", havingValue = "false")
+ static class AlexeyDoesNotExistsCondition {
+ }
+
+ @ConditionalOnProperty(name = "condition.yanis-exists", havingValue = "true")
+ static class YanisExistsCondition {
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/shell/ConditionalAndProfilesDemoCommands.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/shell/ConditionalAndProfilesDemoCommands.java
new file mode 100644
index 00000000..a0cf3f0b
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/java/ru/otus/example/conditionalandprofilesdemo/shell/ConditionalAndProfilesDemoCommands.java
@@ -0,0 +1,21 @@
+package ru.otus.example.conditionalandprofilesdemo.shell;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.shell.standard.ShellComponent;
+import org.springframework.shell.standard.ShellMethod;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@ShellComponent
+@RequiredArgsConstructor
+public class ConditionalAndProfilesDemoCommands {
+
+ private final List partyMembers;
+
+ @ShellMethod(value = "Print party members", key = {"print-party-members", "ppm"})
+ public String printPartyMembers() {
+ return partyMembers.stream().map(Friend::getName).collect(Collectors.joining("\n"));
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/resources/application.yml
new file mode 100644
index 00000000..5045336c
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/main/resources/application.yml
@@ -0,0 +1,25 @@
+greetings:
+ first-greeting: Привет
+ re-greeting: Рад снова тебя видеть
+
+
+condition:
+ alexey-exists: true
+ yanis-exists: false
+
+spring:
+ profiles:
+ #active: Oleg
+ #active: Peter
+
+logging:
+ level:
+ root: ERROR
+
+
+---
+spring:
+ profiles: Peter
+
+condition:
+ yanis-exists: true
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/java/ru/otus/example/conditionalandprofilesdemo/test/ConditionalAndProfilesDemoApplicationTests.java b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/java/ru/otus/example/conditionalandprofilesdemo/test/ConditionalAndProfilesDemoApplicationTests.java
new file mode 100644
index 00000000..ccb1307b
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/java/ru/otus/example/conditionalandprofilesdemo/test/ConditionalAndProfilesDemoApplicationTests.java
@@ -0,0 +1,37 @@
+package ru.otus.example.conditionalandprofilesdemo.test;
+
+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.test.context.ActiveProfiles;
+import ru.otus.example.conditionalandprofilesdemo.model.base.Friend;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+//@ActiveProfiles({"Oleg"})
+//@ActiveProfiles({"Peter"})
+//@ActiveProfiles({"Oleg", "Peter"})
+@DisplayName("FriendsMap должна")
+@SpringBootTest
+class ConditionalAndProfilesDemoApplicationTests {
+
+ @Autowired
+ private Map friendsMap;
+
+
+ @DisplayName(" содержать Олега")
+ @Test
+ void shouldContainOleg() {
+ assertThat(friendsMap).containsKey("oleg");
+ }
+
+ @DisplayName(" содержать Петра")
+ @Test
+ void shouldContainPetr() {
+ assertThat(friendsMap).containsKey("peter");
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/resources/application.yml b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..d1ae4f6e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/conditional-and-profiles-demo/src/test/resources/application.yml
@@ -0,0 +1,7 @@
+spring:
+ shell:
+ interactive:
+ enabled: false
+logging:
+ level:
+ root: ERROR
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/pom.xml b/2019-11/spring-05/advanced-config-class-work/pom.xml
new file mode 100644
index 00000000..0d03f5c7
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ ru.otus
+ advanced-config-class-work
+ 1.0
+
+ pom
+
+
+ beans-scopes-demo
+ beans-lifecycle-demo
+ application-events-demo
+ conditional-and-profiles-demo
+ test-configuration-demo
+
+
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/.gitignore b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/.gitignore
new file mode 100644
index 00000000..789ddc9e
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/.gitignore
@@ -0,0 +1,32 @@
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+#other
+*.bat
+*/.idea
+*.iml
+*/target
+
+.idea
+*.iml
+target
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/README.md b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/README.md
new file mode 100644
index 00000000..00dadea2
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/README.md
@@ -0,0 +1,2 @@
+# test-configuration-demo
+Пример конфигурирования тестов
\ No newline at end of file
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/pom.xml b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/pom.xml
new file mode 100644
index 00000000..d6677365
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/pom.xml
@@ -0,0 +1,76 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ ru.otus.example
+ test-configuration-demo
+ 0.0.1-SNAPSHOT
+ test-configuration-demo
+ Test configuration demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+
+
+ junit
+ junit
+
+
+
+
+
+ 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-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/TestConfigurationDemoApplication.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/TestConfigurationDemoApplication.java
new file mode 100644
index 00000000..e5dd8341
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/TestConfigurationDemoApplication.java
@@ -0,0 +1,15 @@
+package ru.otus.example.testconfigurationdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@ComponentScan("ru.otus.example.testconfigurationdemo.family")
+@SpringBootApplication
+public class TestConfigurationDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TestConfigurationDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/FamilyMember.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/FamilyMember.java
new file mode 100644
index 00000000..1685a4cc
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/FamilyMember.java
@@ -0,0 +1,5 @@
+package ru.otus.example.testconfigurationdemo.family;
+
+public abstract class FamilyMember {
+ public abstract String getName();
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/childrens/Son.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/childrens/Son.java
new file mode 100644
index 00000000..f3da3289
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/childrens/Son.java
@@ -0,0 +1,12 @@
+package ru.otus.example.testconfigurationdemo.family.childrens;
+
+import org.springframework.stereotype.Component;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+@Component
+public class Son extends FamilyMember {
+ @Override
+ public String getName() {
+ return "Сын";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Father.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Father.java
new file mode 100644
index 00000000..d531bda5
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Father.java
@@ -0,0 +1,10 @@
+package ru.otus.example.testconfigurationdemo.family.parents;
+
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+public class Father extends FamilyMember {
+ @Override
+ public String getName() {
+ return "Папа";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Mother.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Mother.java
new file mode 100644
index 00000000..d9b97b75
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/parents/Mother.java
@@ -0,0 +1,12 @@
+package ru.otus.example.testconfigurationdemo.family.parents;
+
+import org.springframework.stereotype.Component;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+@Component
+public class Mother extends FamilyMember {
+ @Override
+ public String getName() {
+ return "Мама";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/pets/Dog.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/pets/Dog.java
new file mode 100644
index 00000000..3e436c12
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/main/java/ru/otus/example/testconfigurationdemo/family/pets/Dog.java
@@ -0,0 +1,13 @@
+package ru.otus.example.testconfigurationdemo.family.pets;
+
+import org.springframework.stereotype.Component;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+@Component
+public class Dog extends FamilyMember {
+
+ @Override
+ public String getName() {
+ return "Собака";
+ }
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedConfigurationDemoTest.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedConfigurationDemoTest.java
new file mode 100644
index 00000000..ec5baa93
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedConfigurationDemoTest.java
@@ -0,0 +1,37 @@
+package ru.otus.example.testconfigurationdemo.demo1;
+
+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.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+import ru.otus.example.testconfigurationdemo.family.pets.Dog;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("В NestedConfigurationDemoTest семья должна ")
+@SpringBootTest
+public class NestedConfigurationDemoTest {
+
+ @Configuration
+ static class NestedConfiguration {
+ @Bean
+ FamilyMember dog() {
+ return new Dog();
+ }
+ }
+
+ @Autowired
+ private Map family;
+
+ @DisplayName(" содержать только собаку ")
+ @Test
+ void shouldContainOnlyDog() {
+ assertThat(family).containsOnlyKeys("dog");
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedTestConfigurationDemoTest.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedTestConfigurationDemoTest.java
new file mode 100644
index 00000000..df8ee16f
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/NestedTestConfigurationDemoTest.java
@@ -0,0 +1,37 @@
+package ru.otus.example.testconfigurationdemo.demo1;
+
+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.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+import ru.otus.example.testconfigurationdemo.family.parents.Father;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("В NestedTestConfigurationDemoTest семья должна ")
+@SpringBootTest
+public class NestedTestConfigurationDemoTest {
+
+ @TestConfiguration
+ static class NestedTestConfiguration {
+ @Bean
+ FamilyMember father() {
+ return new Father();
+ }
+ }
+
+ @Autowired
+ private Map family;
+
+ @DisplayName(" содержать маму, папу, сына и собаку ")
+ @Test
+ void shouldContainAllFamilyWithFather() {
+ assertThat(family).containsOnlyKeys("mother", "father", "son", "dog");
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/PlainSpringBootTestDemoTest.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/PlainSpringBootTestDemoTest.java
new file mode 100644
index 00000000..07c32f59
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo1/PlainSpringBootTestDemoTest.java
@@ -0,0 +1,26 @@
+package ru.otus.example.testconfigurationdemo.demo1;
+
+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 ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("В PlainSpringBootTestDemoTest семья должна ")
+@SpringBootTest
+public class PlainSpringBootTestDemoTest {
+
+ @Autowired
+ private Map family;
+
+ @DisplayName(" содержать маму, сына и собаку ")
+ @Test
+ void shouldContainAllFamilyExceptFather() {
+ assertThat(family).containsOnlyKeys("mother", "son", "dog");
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/SpringBootTestWithExternalLimitationDemoTest.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/SpringBootTestWithExternalLimitationDemoTest.java
new file mode 100644
index 00000000..98241b1b
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/SpringBootTestWithExternalLimitationDemoTest.java
@@ -0,0 +1,26 @@
+package ru.otus.example.testconfigurationdemo.demo2;
+
+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 ru.otus.example.testconfigurationdemo.family.FamilyMember;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("В SpringBootTestWithExternalLimitationDemoTest семья должна ")
+@SpringBootTest
+public class SpringBootTestWithExternalLimitationDemoTest {
+
+ @Autowired
+ private Map family;
+
+ @DisplayName(" содержать маму, папу и сына")
+ @Test
+ void shouldContainAllFamilyExceptFather() {
+ assertThat(family).containsOnlyKeys("mother", "father", "son");
+ }
+
+}
diff --git a/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/TestSpringBootConfiguration.java b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/TestSpringBootConfiguration.java
new file mode 100644
index 00000000..b4106d4b
--- /dev/null
+++ b/2019-11/spring-05/advanced-config-class-work/test-configuration-demo/src/test/java/ru/otus/example/testconfigurationdemo/demo2/TestSpringBootConfiguration.java
@@ -0,0 +1,17 @@
+package ru.otus.example.testconfigurationdemo.demo2;
+
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import ru.otus.example.testconfigurationdemo.family.FamilyMember;
+import ru.otus.example.testconfigurationdemo.family.parents.Father;
+
+@ComponentScan({"ru.otus.example.testconfigurationdemo.family.parents",
+ "ru.otus.example.testconfigurationdemo.family.childrens"})
+@SpringBootConfiguration
+public class TestSpringBootConfiguration {
+ @Bean
+ FamilyMember father() {
+ return new Father();
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/pom.xml
new file mode 100644
index 00000000..1538c35c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-exercise
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..8916eb04
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/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/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..4ee1a3b3
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,4 @@
+package ru.otus.spring.dao;
+
+public interface PersonDao {
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..b38dd2a5
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,8 @@
+package ru.otus.spring.dao;
+
+import org.springframework.stereotype.Repository;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-exercise/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/pom.xml
new file mode 100644
index 00000000..dad2e34c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-solution-step-1
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..8916eb04
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/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/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..4ee1a3b3
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,4 @@
+package ru.otus.spring.dao;
+
+public interface PersonDao {
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..dc1933f1
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,17 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations)
+ {
+ this.jdbc = jdbcOperations;
+ }
+
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-1/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/pom.xml
new file mode 100644
index 00000000..d5004851
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-solution-step-2
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..6c08834b
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/Main.java
@@ -0,0 +1,23 @@
+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());
+
+ Console.main(args);
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..9f5581fd
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,6 @@
+package ru.otus.spring.dao;
+
+public interface PersonDao {
+
+ int count();
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..f069a7a2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,23 @@
+package ru.otus.spring.dao;
+
+import org.springframework.jdbc.core.JdbcOperations;
+import org.springframework.stereotype.Repository;
+import ru.otus.spring.domain.Person;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations)
+ {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ return jdbc.queryForObject("select count(*) from persons", Integer.class);
+ }
+
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-2/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/pom.xml
new file mode 100644
index 00000000..83d1a443
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-solution-step-3
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..2bc8e898
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/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/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..73109a4a
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,12 @@
+package ru.otus.spring.dao;
+
+import ru.otus.spring.domain.Person;
+
+import java.util.List;
+
+public interface PersonDao {
+
+ int count();
+
+ void insert(Person person);
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..35b648f2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,33 @@
+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;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations)
+ {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ return jdbc.queryForObject("select count(*) from persons", Integer.class);
+ }
+
+ @Override
+ public void insert(Person person) {
+ jdbc.update("insert into persons (id, `name`) values (?, ?)", person.getId(), person.getName());
+ }
+
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-3/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/pom.xml
new file mode 100644
index 00000000..624d36b1
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-solution-step-4
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..4bc2aca8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/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/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..a2b1379a
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDao.java
@@ -0,0 +1,16 @@
+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(int id);
+
+ List getAll();
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..a89d7c0b
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
@@ -0,0 +1,56 @@
+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;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@Repository
+public class PersonDaoJdbc implements PersonDao {
+
+ private final JdbcOperations jdbc;
+
+ public PersonDaoJdbc(JdbcOperations jdbcOperations)
+ {
+ this.jdbc = jdbcOperations;
+ }
+
+ @Override
+ public int count() {
+ return jdbc.queryForObject("select count(*) from persons", Integer.class);
+ }
+
+ @Override
+ public void insert(Person person) {
+ jdbc.update("insert into persons (id, `name`) values (?, ?)", person.getId(), person.getName());
+ }
+
+ @Override
+ public Person getById(int id) {
+ return jdbc.queryForObject("select * from persons where id = ?", new Object[] {id}, new PersonMapper());
+ }
+
+ @Override
+ public List getAll() {
+ return jdbc.query("select * from persons", new PersonMapper());
+ }
+
+
+ private static class PersonMapper implements RowMapper {
+
+ @Override
+ public Person mapRow(ResultSet resultSet, int i) throws SQLException {
+ int id = resultSet.getInt("id");
+ String name = resultSet.getString("name");
+ return new Person(id, name);
+ }
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-4/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/.gitignore b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/pom.xml b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/pom.xml
new file mode 100644
index 00000000..6987d731
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-demo-solution-step-final
+ 1.0-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ com.h2database
+ h2
+ 1.4.200
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/Main.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/Main.java
new file mode 100644
index 00000000..05636811
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-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/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/dao/PersonDao.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/dao/PersonDao.java
new file mode 100644
index 00000000..9843d440
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-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(int id);
+
+ List getAll();
+
+ void deleteById(int id);
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/dao/PersonDaoJdbc.java
new file mode 100644
index 00000000..348e4df5
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-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;
+
+@SuppressWarnings({"SqlNoDataSourceInspection", "ConstantConditions", "SqlDialectInspection"})
+@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() {
+ return jdbc.queryForObject("select count(*) from persons", Integer.class);
+ }
+
+ @Override
+ public void insert(Person person) {
+ jdbc.update("insert into persons (id, `name`) values (?, ?)", person.getId(), person.getName());
+ }
+
+ @Override
+ public Person getById(int id) {
+ Map params = Collections.singletonMap("id", id);
+ return namedParameterJdbcOperations.queryForObject(
+ "select * from persons where id = :id", params, new PersonMapper()
+ );
+ }
+
+ @Override
+ public List getAll() {
+ return jdbc.query("select * from persons", new PersonMapper());
+ }
+
+ @Override
+ public void deleteById(int 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 {
+ int id = resultSet.getInt("id");
+ String name = resultSet.getString("name");
+ return new Person(id, name);
+ }
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/domain/Person.java b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/domain/Person.java
new file mode 100644
index 00000000..e34ae29c
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/java/ru/otus/spring/domain/Person.java
@@ -0,0 +1,20 @@
+package ru.otus.spring.domain;
+
+public class Person {
+
+ private final int id;
+ private final String name;
+
+ public Person(int id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+}
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/data.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/data.sql
new file mode 100644
index 00000000..240a5b4e
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/data.sql
@@ -0,0 +1 @@
+insert into persons (id, `name`) values (1, 'masha');
diff --git a/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/schema.sql b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/schema.sql
new file mode 100644
index 00000000..b38e5bc8
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/jdbc-demo-solution-step-final/src/main/resources/schema.sql
@@ -0,0 +1,2 @@
+DROP TABLE IF EXISTS PERSONS;
+CREATE TABLE PERSONS(ID INT PRIMARY KEY, NAME VARCHAR(255));
diff --git a/2019-11/spring-07/jdbc-class-work/pom.xml b/2019-11/spring-07/jdbc-class-work/pom.xml
new file mode 100644
index 00000000..4756a248
--- /dev/null
+++ b/2019-11/spring-07/jdbc-class-work/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ ru.otus
+ jdbc-class-work
+ 1.0
+
+ pom
+
+
+ jdbc-demo-exercise
+ jdbc-demo-solution-step-1
+ jdbc-demo-solution-step-2
+ jdbc-demo-solution-step-3
+ jdbc-demo-solution-step-4
+ jdbc-demo-solution-step-final
+
+
diff --git a/2019-11/spring-08/orm-class-work/.gitignore b/2019-11/spring-08/orm-class-work/.gitignore
new file mode 100644
index 00000000..e62c33c2
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+*.iml
+
+target/
diff --git a/examples/jpql-demo/.gitignore b/2019-11/spring-08/orm-class-work/mybatis-demo/.gitignore
similarity index 100%
rename from examples/jpql-demo/.gitignore
rename to 2019-11/spring-08/orm-class-work/mybatis-demo/.gitignore
diff --git a/2019-11/spring-08/orm-class-work/mybatis-demo/README.md b/2019-11/spring-08/orm-class-work/mybatis-demo/README.md
new file mode 100644
index 00000000..44635273
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/mybatis-demo/README.md
@@ -0,0 +1,2 @@
+# mybatis-demo
+Пример работы с БД через MyBatis
\ No newline at end of file
diff --git a/2019-11/spring-08/orm-class-work/mybatis-demo/pom.xml b/2019-11/spring-08/orm-class-work/mybatis-demo/pom.xml
new file mode 100644
index 00000000..1fa13a35
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/mybatis-demo/pom.xml
@@ -0,0 +1,73 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ ru.otus.example
+ mybatis-demo
+ 0.0.1-SNAPSHOT
+ mybatis-demo
+ MyBatis demo
+
+
+ 11
+ 11
+ 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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java
new file mode 100644
index 00000000..ab5f7513
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java
new file mode 100644
index 00000000..5c8f4728
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java
new file mode 100644
index 00000000..6aa8cff7
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java
new file mode 100644
index 00000000..8fa43ebd
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java
new file mode 100644
index 00000000..afb324fb
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java
new file mode 100644
index 00000000..634d1aba
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java
new file mode 100644
index 00000000..96c77681
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java
new file mode 100644
index 00000000..989b681d
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java
new file mode 100644
index 00000000..82a8260b
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/main/resources/schema.sql b/2019-11/spring-08/orm-class-work/mybatis-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java b/2019-11/spring-08/orm-class-work/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
new file mode 100644
index 00000000..210924e6
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java
@@ -0,0 +1,103 @@
+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 {
+
+ 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();
+ expectedStudent.setName(STUDENT_NEW_NAME);
+ expectedStudent.setAvatar(new Avatar(FIRST_AVATAR_ID, FIRST_AVATAR_URL));
+ studentRepositoryMyBatis.insert(expectedStudent);
+ val actualStudent = studentRepositoryMyBatis.findById(INSERTED_STUDENT_ID);
+
+ assertThat(actualStudent).isNotNull().isEqualToComparingOnlyGivenFields(expectedStudent, FIELD_NAME);
+ }
+
+
+ @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/2019-11/spring-08/orm-class-work/mybatis-demo/src/test/resources/application.yml b/2019-11/spring-08/orm-class-work/mybatis-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..037f7eae
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/mybatis-demo/src/test/resources/data.sql b/2019-11/spring-08/orm-class-work/mybatis-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/.gitignore b/2019-11/spring-08/orm-class-work/orm-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/README.md b/2019-11/spring-08/orm-class-work/orm-demo/README.md
new file mode 100644
index 00000000..a1e907ee
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/README.md
@@ -0,0 +1,2 @@
+# orm-demo
+Пример работы с БД через ORM
\ No newline at end of file
diff --git a/2019-11/spring-08/orm-class-work/orm-demo/pom.xml b/2019-11/spring-08/orm-class-work/orm-demo/pom.xml
new file mode 100644
index 00000000..213779ff
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/pom.xml
@@ -0,0 +1,72 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ ru.otus.example
+ orm-demo
+ 0.0.1-SNAPSHOT
+ orm-demo
+ Orm demo
+
+
+ 11
+ 11
+ 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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java
new file mode 100644
index 00000000..8d4b413c
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java
@@ -0,0 +1,13 @@
+package ru.otus.example.ormdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class OrmDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(OrmDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java
new file mode 100644
index 00000000..bd7f4852
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java
new file mode 100644
index 00000000..e7fa06e5
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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;
+
+ //@Fetch(FetchMode.SUBSELECT)
+ //@BatchSize(size = 5)
+ @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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java
new file mode 100644
index 00000000..2ed2b5f8
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java
new file mode 100644
index 00000000..6ce8b993
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java
new file mode 100644
index 00000000..1bc8cf74
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java
new file mode 100644
index 00000000..dc6d0811
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java
new file mode 100644
index 00000000..d4ce0bd8
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java
new file mode 100644
index 00000000..ba9eedda
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java
new file mode 100644
index 00000000..5d4760cc
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java
@@ -0,0 +1,36 @@
+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);
+ return student;
+ } else {
+ return em.merge(student);
+ }
+ }
+}
diff --git a/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java
new file mode 100644
index 00000000..5893319f
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java b/2019-11/spring-08/orm-class-work/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java
new file mode 100644
index 00000000..df615f41
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/main/resources/schema.sql b/2019-11/spring-08/orm-class-work/orm-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2019-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java
new file mode 100644
index 00000000..7c9ca9f3
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java
@@ -0,0 +1,94 @@
+package ru.otus.example.ormdemo.repositories;
+
+import lombok.val;
+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_NUMBER_OF_STUDENTS = 10;
+ private static final long FIRST_STUDENT_ID = 1L;
+
+ private static final int EXPECTED_QUERIES_COUNT = 31;
+
+ private static final String STUDENT_AVATAR_URL = "где-то там";
+ private static final String STUDENT_EMAIL = "any@mail.com";
+ private static final String COURSE_NAME = "Spring";
+ private static final String STUDENT_NAME = "Вася";
+
+ @Autowired
+ private OtusStudentRepositoryJpaImpl repositoryJpa;
+
+ @Autowired
+ private CourseRepositoryJpaImpl courseRepositoryJpa;
+
+ @Autowired
+ private TestEntityManager em;
+
+ @DisplayName(" должен загружать информацию о нужном студенте")
+ @Test
+ void shouldFindExpectedStudentById() {
+ val optionalActualStudent = repositoryJpa.findById(FIRST_STUDENT_ID);
+ val expectedStudent = em.find(OtusStudent.class, FIRST_STUDENT_ID);
+ assertThat(optionalActualStudent).isPresent().get()
+ .isEqualToComparingFieldByField(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(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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
+ }
+
+ @DisplayName(" должен корректно сохранять всю информацию о студенте")
+ @Test
+ void shouldSaveAllStudentInfo() {
+ val avatar = new Avatar(0, STUDENT_AVATAR_URL);
+ val email = new EMail(0, STUDENT_EMAIL);
+ val emails = Collections.singletonList(email);
+
+ val course = new Course(0, COURSE_NAME);
+ val courses = Collections.singletonList(course);
+ //courseRepositoryJpa.save(course);
+
+
+ val vasya = new OtusStudent(0, STUDENT_NAME, 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-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java b/2019-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java
new file mode 100644
index 00000000..041384b3
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java
@@ -0,0 +1,74 @@
+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_NUMBER_OF_STUDENTS = 10;
+
+ private static final int EXPECTED_QUERIES_COUNT = 11; // EntityGraph/join fetch
+ //private static final int EXPECTED_QUERIES_COUNT = 2; // EntityGraph/join fetch + @Fetch(FetchMode.SUBSELECT)
+ //private static final int EXPECTED_QUERIES_COUNT = 3; // EntityGraph/join fetch + @BatchSize(size = 5)
+
+ @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(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);
+ 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(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);
+ 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-11/spring-08/orm-class-work/orm-demo/src/test/resources/application.yml b/2019-11/spring-08/orm-class-work/orm-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..d2b811a4
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/orm-demo/src/test/resources/application.yml
@@ -0,0 +1,17 @@
+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:
+ ROOT: ERROR
\ No newline at end of file
diff --git a/2019-11/spring-08/orm-class-work/orm-demo/src/test/resources/data.sql b/2019-11/spring-08/orm-class-work/orm-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/pom.xml b/2019-11/spring-08/orm-class-work/pom.xml
new file mode 100644
index 00000000..f398c0f4
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/pom.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+
+ ru.otus
+ orm-class-work
+ 1.0
+
+ pom
+
+
+ spring-jdbc-demo
+ orm-demo
+ replace-orm-provider-demo
+ mybatis-demo
+
+
diff --git a/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/.gitignore b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-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-11/spring-08/orm-class-work/replace-orm-provider-demo/README.md b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/README.md
new file mode 100644
index 00000000..f5eceb6d
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/README.md
@@ -0,0 +1,2 @@
+# replace-orm-provider-demo
+Пример замены ORM провайдера
\ No newline at end of file
diff --git a/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/pom.xml b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/pom.xml
new file mode 100644
index 00000000..b783b3fc
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ ru.otus.example
+ replace-orm-provider-demo
+ 0.0.1-SNAPSHOT
+ replace-orm-provider-demo
+ Replace ORM provider demo
+
+
+ 11
+ 11
+ 11
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.hibernate
+ hibernate-entitymanager
+
+
+ org.hibernate
+ hibernate-core
+
+
+
+
+
+ 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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/ReplaceOrmProviderDemoApplication.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/ReplaceOrmProviderDemoApplication.java
new file mode 100644
index 00000000..97fee71e
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/ReplaceOrmProviderDemoApplication.java
@@ -0,0 +1,16 @@
+package ru.otus.example.replaceormproviderdemo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Import;
+import ru.otus.example.replaceormproviderdemo.config.EclipseLinkJpaConfiguration;
+
+@SpringBootApplication
+@Import(EclipseLinkJpaConfiguration.class)
+public class ReplaceOrmProviderDemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ReplaceOrmProviderDemoApplication.class, args);
+ }
+
+}
diff --git a/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/config/EclipseLinkJpaConfiguration.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/config/EclipseLinkJpaConfiguration.java
new file mode 100644
index 00000000..6e14ede9
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/config/EclipseLinkJpaConfiguration.java
@@ -0,0 +1,37 @@
+package ru.otus.example.replaceormproviderdemo.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) {
+ super(dataSource, properties, jtaTransactionManager);
+ }
+
+ @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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Avatar.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Avatar.java
new file mode 100644
index 00000000..1a619456
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Avatar.java
@@ -0,0 +1,21 @@
+package ru.otus.example.replaceormproviderdemo.models;
+
+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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Course.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Course.java
new file mode 100644
index 00000000..caecc0c5
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/Course.java
@@ -0,0 +1,21 @@
+package ru.otus.example.replaceormproviderdemo.models;
+
+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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/EMail.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/EMail.java
new file mode 100644
index 00000000..8c69afe8
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/EMail.java
@@ -0,0 +1,22 @@
+package ru.otus.example.replaceormproviderdemo.models;
+
+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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/OtusStudent.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/OtusStudent.java
new file mode 100644
index 00000000..534d051e
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/models/OtusStudent.java
@@ -0,0 +1,42 @@
+package ru.otus.example.replaceormproviderdemo.models;
+
+import lombok.*;
+import org.eclipse.persistence.annotations.BatchFetch;
+import org.eclipse.persistence.annotations.BatchFetchType;
+
+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;
+
+ // Указывает на связь между таблицами "многие ко многим"
+ @BatchFetch(value = BatchFetchType.IN)
+ @ManyToMany(targetEntity = Course.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
+ // Задает таблицу связей между таблицами для хранения родительской и связанной сущностью
+ @JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"),
+ inverseJoinColumns = @JoinColumn(name = "course_id"))
+ private List courses;
+}
diff --git a/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpa.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpa.java
new file mode 100644
index 00000000..ed2eaf3d
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpa.java
@@ -0,0 +1,17 @@
+package ru.otus.example.replaceormproviderdemo.repositories;
+
+
+import ru.otus.example.replaceormproviderdemo.models.OtusStudent;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface OtusStudentRepositoryJpa {
+ Optional findById(long id);
+ List findAll();
+
+ List findAllWithJoinFetch();
+
+ OtusStudent save(OtusStudent student);
+
+}
diff --git a/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImpl.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImpl.java
new file mode 100644
index 00000000..196c40a4
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImpl.java
@@ -0,0 +1,49 @@
+package ru.otus.example.replaceormproviderdemo.repositories;
+
+import org.eclipse.persistence.config.QueryHints;
+import org.springframework.stereotype.Repository;
+import ru.otus.example.replaceormproviderdemo.models.OtusStudent;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.TypedQuery;
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public class OtusStudentRepositoryJpaImpl implements OtusStudentRepositoryJpa {
+
+ private static final int DEFAULT_BATCH_SIZE = 5;
+
+ @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 List findAllWithJoinFetch() {
+ TypedQuery query = em.createQuery("select distinct s from OtusStudent s join fetch s.avatar join fetch s.emails", OtusStudent.class);
+ query.setHint(QueryHints.BATCH_SIZE, DEFAULT_BATCH_SIZE);
+ return query.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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/resources/schema.sql b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..43a684bb
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImplTest.java
new file mode 100644
index 00000000..2f9b2c1e
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/java/ru/otus/example/replaceormproviderdemo/repositories/OtusStudentRepositoryJpaImplTest.java
@@ -0,0 +1,94 @@
+package ru.otus.example.replaceormproviderdemo.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.autoconfigure.orm.jpa.DataJpaTest;
+import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
+import org.springframework.context.annotation.Import;
+import ru.otus.example.replaceormproviderdemo.models.OtusStudent;
+import ru.otus.example.replaceormproviderdemo.models.Avatar;
+import ru.otus.example.replaceormproviderdemo.models.Course;
+import ru.otus.example.replaceormproviderdemo.models.EMail;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@DisplayName("Репозиторий на основе Jpa для работы со студентами ")
+@DataJpaTest
+@Import({OtusStudentRepositoryJpaImpl.class})
+class OtusStudentRepositoryJpaImplTest {
+
+ private static final int EXPECTED_NUMBER_OF_STUDENTS = 10;
+ private static final long FIRST_STUDENT_ID = 1L;
+
+ private static final String STUDENT_AVATAR_URL = "где-то там";
+ private static final String STUDENT_EMAIL = "any@mail.com";
+ private static final String COURSE_NAME = "Spring";
+ private static final String STUDENT_NAME = "Вася";
+
+ @Autowired
+ private OtusStudentRepositoryJpaImpl repositoryJpa;
+
+ @Autowired
+ private TestEntityManager em;
+
+ @DisplayName(" должен загружать информацию о нужном студенте")
+ @Test
+ void shouldFindExpectedStudentById() {
+ val optionalActualStudent = repositoryJpa.findById(FIRST_STUDENT_ID);
+ val expectedStudent = em.find(OtusStudent.class, FIRST_STUDENT_ID);
+ assertThat(optionalActualStudent).isPresent().get()
+ .isEqualToComparingFieldByField(expectedStudent);
+ }
+
+ @DisplayName("должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void shouldReturnCorrectStudentsListWithAllInfo() {
+ System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
+ val students = repositoryJpa.findAll();
+ 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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ }
+
+ @DisplayName(" с помощью 'join fetch' должен загружать список всех студентов с полной информацией о них")
+ @Test
+ void usingJoinFetchShouldReturnCorrectStudentsListWithWithAllInfo() {
+ System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
+ List students = repositoryJpa.findAllWithJoinFetch();
+ 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);
+ System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
+ }
+
+
+ @DisplayName(" должен корректно сохранять всю информацию о студенте")
+ @Test
+ void shouldSaveAllStudentInfo() {
+ val avatar = new Avatar(0, STUDENT_AVATAR_URL);
+ val email = new EMail(0, STUDENT_EMAIL);
+ val emails = Collections.singletonList(email);
+
+ val course = new Course(0, COURSE_NAME);
+ val courses = Collections.singletonList(course);
+ val vasya = new OtusStudent(0, STUDENT_NAME, 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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/resources/application.yml b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/resources/application.yml
new file mode 100644
index 00000000..b98cd926
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-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-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/resources/data.sql b/2019-11/spring-08/orm-class-work/replace-orm-provider-demo/src/test/resources/data.sql
new file mode 100644
index 00000000..a8db6b85
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/replace-orm-provider-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-11/spring-08/orm-class-work/spring-jdbc-demo/.gitignore b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/.gitignore
new file mode 100644
index 00000000..153c9335
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/README.md b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/README.md
new file mode 100644
index 00000000..462216c3
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/README.md
@@ -0,0 +1,2 @@
+# spring-jdbc-demo
+Пример работы с БД через jdbc
\ No newline at end of file
diff --git a/2019-11/spring-08/orm-class-work/spring-jdbc-demo/pom.xml b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/pom.xml
new file mode 100644
index 00000000..3d503565
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/pom.xml
@@ -0,0 +1,71 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.1.RELEASE
+
+
+ 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-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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java
new file mode 100644
index 00000000..28ebfec4
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java
new file mode 100644
index 00000000..a1963ea4
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java
new file mode 100644
index 00000000..07b6e2c2
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java
new file mode 100644
index 00000000..16985ad5
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java
new file mode 100644
index 00000000..5ae9167c
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java
new file mode 100644
index 00000000..716ab880
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java
new file mode 100644
index 00000000..aedb3ad5
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java
new file mode 100644
index 00000000..0761373d
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java b/2019-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java
new file mode 100644
index 00000000..05208672
--- /dev/null
+++ b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java b/2019-11/spring-08/orm-class-work/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-11/spring-08/orm-class-work/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