diff --git a/2019-08/spring-05/application-events-demo/README.md b/2019-08/spring-05/application-events-demo/README.md new file mode 100644 index 00000000..63e68847 --- /dev/null +++ b/2019-08/spring-05/application-events-demo/README.md @@ -0,0 +1,2 @@ +# application-events-demo +Пример работы с событиями \ No newline at end of file diff --git a/2019-08/spring-05/beans-lifecycle-demo/README.md b/2019-08/spring-05/beans-lifecycle-demo/README.md new file mode 100644 index 00000000..2c841b7e --- /dev/null +++ b/2019-08/spring-05/beans-lifecycle-demo/README.md @@ -0,0 +1,2 @@ +# beans-lifecycle-demo +Пример жизненного цикла бинов \ No newline at end of file diff --git a/2019-08/spring-05/beans-scopes-demo/README.md b/2019-08/spring-05/beans-scopes-demo/README.md new file mode 100644 index 00000000..09665122 --- /dev/null +++ b/2019-08/spring-05/beans-scopes-demo/README.md @@ -0,0 +1,2 @@ +# beans-scopes-demo +Пример работы со областью действия (Scope) бинов \ No newline at end of file diff --git a/2019-08/spring-05/conditional-and-profiles-demo/README.md b/2019-08/spring-05/conditional-and-profiles-demo/README.md new file mode 100644 index 00000000..f596dfc9 --- /dev/null +++ b/2019-08/spring-05/conditional-and-profiles-demo/README.md @@ -0,0 +1,2 @@ +# conditional-and-profiles-demo +Пример работы с профилями и автоконфигурациями \ No newline at end of file diff --git a/2019-08/spring-05/test-configuration-demo/README.md b/2019-08/spring-05/test-configuration-demo/README.md new file mode 100644 index 00000000..00dadea2 --- /dev/null +++ b/2019-08/spring-05/test-configuration-demo/README.md @@ -0,0 +1,2 @@ +# test-configuration-demo +Пример конфигурирования тестов \ No newline at end of file diff --git a/2019-08/spring-07/mybatis-demo/.gitignore b/2019-08/spring-07/mybatis-demo/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/.gitignore @@ -0,0 +1,29 @@ +HELP.md +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + +### VS Code ### +.vscode/ diff --git a/2019-08/spring-07/mybatis-demo/README.md b/2019-08/spring-07/mybatis-demo/README.md new file mode 100644 index 00000000..44635273 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/README.md @@ -0,0 +1,2 @@ +# mybatis-demo +Пример работы с БД через MyBatis \ No newline at end of file diff --git a/2019-08/spring-07/mybatis-demo/pom.xml b/2019-08/spring-07/mybatis-demo/pom.xml new file mode 100644 index 00000000..b7f66cba --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.8.RELEASE + + + ru.otus.example + mybatis-demo + 0.0.1-SNAPSHOT + mybatis-demo + MyBatis demo + + + 11 + + + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.0.1 + + + + com.h2database + h2 + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java new file mode 100644 index 00000000..ab5f7513 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/MyBatisDemoApplication.java @@ -0,0 +1,14 @@ +package ru.otus.example.mybatisdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ConfigurableApplicationContext; + +@SpringBootApplication +public class MyBatisDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MyBatisDemoApplication.class, args); + } + +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java new file mode 100644 index 00000000..5c8f4728 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Avatar.java @@ -0,0 +1,13 @@ +package ru.otus.example.mybatisdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Avatar { + private long id; + private String photoUrl; +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java new file mode 100644 index 00000000..6aa8cff7 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/Course.java @@ -0,0 +1,13 @@ +package ru.otus.example.mybatisdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Course { + private long id; + private String name; +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java new file mode 100644 index 00000000..8fa43ebd --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/EMail.java @@ -0,0 +1,13 @@ +package ru.otus.example.mybatisdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EMail { + private long id; + private String email; +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java new file mode 100644 index 00000000..afb324fb --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/models/OtusStudent.java @@ -0,0 +1,18 @@ +package ru.otus.example.mybatisdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OtusStudent { + private long id; + private String name; + private Avatar avatar; + private List emails; + private List courses; +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java new file mode 100644 index 00000000..634d1aba --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/AvatarRepository.java @@ -0,0 +1,18 @@ +package ru.otus.example.mybatisdemo.repositories; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Result; +import org.apache.ibatis.annotations.Results; +import org.apache.ibatis.annotations.Select; +import ru.otus.example.mybatisdemo.models.Avatar; + +@Mapper +public interface AvatarRepository { + @Select("select * from avatars where id = #{id}") + @Results(value = { + @Result(property = "id", column = "id"), + @Result(property = "photoUrl", column = "photo_url") + }) + Avatar getAvatarById(long id); + +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java new file mode 100644 index 00000000..96c77681 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/CourseRepository.java @@ -0,0 +1,15 @@ +package ru.otus.example.mybatisdemo.repositories; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import ru.otus.example.mybatisdemo.models.Course; + +import java.util.List; + +@Mapper +public interface CourseRepository { + + @Select("select * from student_courses sc left join courses c on sc.course_id = c.id where sc.student_id = #{studentId}") + List getCoursesByStudentId(long studentId); + +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java new file mode 100644 index 00000000..989b681d --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/EmailRepository.java @@ -0,0 +1,14 @@ +package ru.otus.example.mybatisdemo.repositories; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import ru.otus.example.mybatisdemo.models.EMail; + +import java.util.List; + +@Mapper +public interface EmailRepository { + + @Select("select * from emails where student_id = #{studentId}") + List getEmailsByStudentId(long studentId); +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java new file mode 100644 index 00000000..82a8260b --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepository.java @@ -0,0 +1,42 @@ +package ru.otus.example.mybatisdemo.repositories; + +import org.apache.ibatis.annotations.*; +import org.apache.ibatis.mapping.FetchType; +import ru.otus.example.mybatisdemo.models.Avatar; +import ru.otus.example.mybatisdemo.models.OtusStudent; + +import java.util.List; + +@Mapper +public interface OtusStudentRepository { + + @Select("select * from otus_students") + @Results(id = "studentAllMap", value = { + @Result(property = "id", column = "id"), + @Result(property = "name", column = "name"), + @Result(property = "avatar", column = "avatar_id", javaType = Avatar.class, + one = @One(select = "ru.otus.example.mybatisdemo.repositories.AvatarRepository.getAvatarById", fetchType = FetchType.EAGER)), + @Result(property = "emails", column = "id", javaType = List.class, + many = @Many(select = "ru.otus.example.mybatisdemo.repositories.EmailRepository.getEmailsByStudentId", fetchType = FetchType.EAGER)), + @Result(property = "courses", column = "id", javaType = List.class, + many = @Many(select = "ru.otus.example.mybatisdemo.repositories.CourseRepository.getCoursesByStudentId", fetchType = FetchType.EAGER)) + }) + List findAllWithAllInfo(); + + @Select("select * from otus_students where id = #{id}") + @ResultMap("studentAllMap") + OtusStudent findById(long id); + + @Select("select count(*) as students_count from otus_students") + long getStudentsCount(); + + @Insert("insert into otus_students(name, avatar_id) values (#{name}, #{avatar.id})") + void insert(OtusStudent student); + + @Update("update otus_students set name = #{name} where id = #{id}") + void updateName(OtusStudent student); + + @Delete("delete from otus_students where id = #{id}") + void deleteById(long id); + +} diff --git a/2019-08/spring-07/mybatis-demo/src/main/resources/schema.sql b/2019-08/spring-07/mybatis-demo/src/main/resources/schema.sql new file mode 100644 index 00000000..43a684bb --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/main/resources/schema.sql @@ -0,0 +1,31 @@ +create table avatars( + id bigserial, + photo_url varchar(8000), + primary key (id) +); + +create table courses( + id bigserial, + name varchar(255), + primary key (id) +); + +create table otus_students( + id bigserial, + name varchar(255), + avatar_id bigint references avatars (id), + primary key (id) +); + +create table emails( + id bigserial, + student_id bigint references otus_students(id) on delete cascade, + email varchar(255), + primary key (id) +); + +create table student_courses( + student_id bigint references otus_students(id) on delete cascade, + course_id bigint references courses(id), + primary key (student_id, course_id) +); \ No newline at end of file diff --git a/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java b/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java new file mode 100644 index 00000000..76d28681 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/test/java/ru/otus/example/mybatisdemo/repositories/OtusStudentRepositoryTest.java @@ -0,0 +1,87 @@ +package ru.otus.example.mybatisdemo.repositories; + +import lombok.val; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; +import ru.otus.example.mybatisdemo.models.Avatar; +import ru.otus.example.mybatisdemo.models.OtusStudent; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Репозиторий на основе MyBatis для работы со студентами ") +@SpringBootTest +@Transactional +public class OtusStudentRepositoryTest { + + @Autowired + private OtusStudentRepository studentRepositoryMyBatis; + + @DisplayName("должен загружать список всех студентов с полной информацией о них") + @Test + void shouldReturnCorrectStudentsListWithAllInfo() { + val students = studentRepositoryMyBatis.findAllWithAllInfo(); + assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals("")) + .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) + .allMatch(s -> s.getAvatar() != null) + .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0); + } + + @DisplayName("должен загружать число студентов в БД") + @Test + void shouldReturnCorrectStudentsCount() { + long studentsCount = studentRepositoryMyBatis.getStudentsCount(); + assertThat(studentsCount).isEqualTo(10); + } + + @DisplayName(" должен загружать информацию о нужном студенте") + @Test + void shouldFindExpectedStudentById(){ + val actualStudent = studentRepositoryMyBatis.findById(1L); + + assertThat(actualStudent).isNotNull(); + assertThat(actualStudent.getName()).isEqualTo("student_01"); + assertThat(actualStudent.getAvatar()).isNotNull() + .hasFieldOrPropertyWithValue("id", 1L) + .hasFieldOrPropertyWithValue("photoUrl", "photoUrl_01"); + assertThat(actualStudent.getEmails()).isNotNull().hasSize(2); + assertThat(actualStudent.getCourses()).isNotNull().hasSize(3); + } + + @DisplayName(" должен сохранить, а потом загрузить информацию о нужном студенте") + @Test + void shouldSaveAndLoadCorrectStudent() { + val expectedStudent = new OtusStudent(); + expectedStudent.setName("Vasya"); + expectedStudent.setAvatar(new Avatar(1L, "photoUrl_01")); + studentRepositoryMyBatis.insert(expectedStudent); + val actualStudent = studentRepositoryMyBatis.findById(11L); + + assertThat(actualStudent).isNotNull().isEqualToComparingOnlyGivenFields(expectedStudent, "name"); + } + + + @DisplayName(" должен обновлять имя студента в БД") + @Test + void shouldUpdateStudentName() { + val student = studentRepositoryMyBatis.findById(1L); + student.setName("Висусуалий"); + studentRepositoryMyBatis.updateName(student); + val actualStudent = studentRepositoryMyBatis.findById(1L); + + assertThat(actualStudent).isNotNull().hasFieldOrPropertyWithValue("name", student.getName()); + } + + @DisplayName("должен удалять студента из БД по id") + @Test + void shouldDeleteStudentFromDbById() { + val studentsCountBefore = studentRepositoryMyBatis.getStudentsCount(); + studentRepositoryMyBatis.deleteById(1L); + val studentsCountAfter = studentRepositoryMyBatis.getStudentsCount(); + + assertThat(studentsCountBefore - studentsCountAfter).isEqualTo(1); + } + +} diff --git a/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml b/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml new file mode 100644 index 00000000..037f7eae --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/test/resources/application.yml @@ -0,0 +1,18 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + initialization-mode: always + + jpa: + generate-ddl: false + #generate-ddl: true + hibernate: + ddl-auto: none + #ddl-auto: create-drop + + show-sql: true + + +logging: + level: + ru.otus.example.mybatisdemo.repositories: TRACE \ No newline at end of file diff --git a/2019-08/spring-07/mybatis-demo/src/test/resources/data.sql b/2019-08/spring-07/mybatis-demo/src/test/resources/data.sql new file mode 100644 index 00000000..a8db6b85 --- /dev/null +++ b/2019-08/spring-07/mybatis-demo/src/test/resources/data.sql @@ -0,0 +1,29 @@ +insert into avatars(photo_url) +values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'), + ('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10'); + +insert into courses(name) +values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'), + ('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11'); + +insert into otus_students(name, avatar_id) +values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5), + ('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10); + + +insert into emails(email, student_id) +values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4), + ('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10); + + +insert into student_courses(student_id, course_id) +values (1, 1), (1, 2), (1, 3), + (2, 2), (2, 4), (2, 5), + (3, 3), (3, 6), (3, 7), + (4, 4), (4, 8), (4, 9), + (5, 5), (5, 10), (5, 1), + (6, 6), (6, 2), (6, 3), + (7, 7), (7, 4), (7, 5), + (8, 8), (8, 6), (8, 7), + (9, 9), (9, 8), (9, 10), + (10, 10), (10, 1), (10, 2); diff --git a/2019-08/spring-07/orm-demo/.gitignore b/2019-08/spring-07/orm-demo/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/2019-08/spring-07/orm-demo/.gitignore @@ -0,0 +1,29 @@ +HELP.md +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + +### VS Code ### +.vscode/ diff --git a/2019-08/spring-07/orm-demo/README.md b/2019-08/spring-07/orm-demo/README.md new file mode 100644 index 00000000..a1e907ee --- /dev/null +++ b/2019-08/spring-07/orm-demo/README.md @@ -0,0 +1,2 @@ +# orm-demo +Пример работы с БД через ORM \ No newline at end of file diff --git a/2019-08/spring-07/orm-demo/pom.xml b/2019-08/spring-07/orm-demo/pom.xml new file mode 100644 index 00000000..5b8662a4 --- /dev/null +++ b/2019-08/spring-07/orm-demo/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.8.RELEASE + + + ru.otus.example + orm-demo + 0.0.1-SNAPSHOT + orm-demo + Orm demo + + + 11 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.eclipse.persistence + org.eclipse.persistence.jpa + 2.7.4 + + + + com.h2database + h2 + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java new file mode 100644 index 00000000..83624381 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/OrmDemoApplication.java @@ -0,0 +1,16 @@ +package ru.otus.example.ormdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; +import ru.otus.example.ormdemo.config.EclipseLinkJpaConfiguration; + +@SpringBootApplication +//@Import(EclipseLinkJpaConfiguration.class) +public class OrmDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(OrmDemoApplication.class, args); + } + +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java new file mode 100644 index 00000000..f078b295 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/config/EclipseLinkJpaConfiguration.java @@ -0,0 +1,38 @@ +package ru.otus.example.ormdemo.config; + +import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration; +import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties; +import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers; +import org.springframework.context.annotation.Configuration; +import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter; +import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter; +import org.springframework.transaction.jta.JtaTransactionManager; + +import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration { + @Autowired + protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties, + ObjectProvider jtaTransactionManager, + ObjectProvider transactionManagerCustomizers) { + super(dataSource, properties, jtaTransactionManager, transactionManagerCustomizers); + } + + @Override + protected AbstractJpaVendorAdapter createJpaVendorAdapter() { + return new EclipseLinkJpaVendorAdapter(); + } + + @Override + protected Map getVendorProperties() { + HashMap map = new HashMap<>(); + map.put(PersistenceUnitProperties.WEAVING, "false"); + return map; + } +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java new file mode 100644 index 00000000..bd7f4852 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java @@ -0,0 +1,42 @@ +package ru.otus.example.ormdemo.models; + +import lombok.*; +import ru.otus.example.ormdemo.models.common.Avatar; +import ru.otus.example.ormdemo.models.common.Course; +import ru.otus.example.ormdemo.models.common.EMail; + +import javax.persistence.*; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity // Указывает, что данный класс является сущностью +@Table(name = "otus_students") // Задает имя таблицы, на которую будет отображаться сущность +public class OtusStudent { + @Id // Позволяет указать какое поле является идентификатором + @GeneratedValue(strategy = GenerationType.IDENTITY) // Стратегия генерации идентификаторов + private long id; + + // Задает имя и некоторые свойства поля таблицы, на которое будет отображаться поле сущности + @Column(name = "name", nullable = false, unique = true) + private String name; + + // Указывает на связь между таблицами "один к одному" + @OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL) + // Задает поле, по которому происходит объединение с таблицей для хранения связанной сущности + @JoinColumn(name = "avatar_id") + private Avatar avatar; + + // Указывает на связь между таблицами "один ко многим" + @OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER) + @JoinColumn(name = "student_id") + private List emails; + + // Указывает на связь между таблицами "многие ко многим" + @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY /*, cascade = CascadeType.ALL*/) + // Задает таблицу связей между таблицами для хранения родительской и связанной сущностью + @JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "course_id")) + private List courses; +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java new file mode 100644 index 00000000..73750fe0 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/OtusStudentV2.java @@ -0,0 +1,44 @@ +package ru.otus.example.ormdemo.models; + +import lombok.*; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; +import ru.otus.example.ormdemo.models.common.Avatar; +import ru.otus.example.ormdemo.models.common.Course; +import ru.otus.example.ormdemo.models.common.EMail; + +import javax.persistence.*; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "otus_students") +@NamedEntityGraph(name = "OtusStudentWithAvatarAndEmails", + attributeNodes = {@NamedAttributeNode(value = "avatar"), + @NamedAttributeNode(value = "emails")}) +public class OtusStudentV2 { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "name", nullable = false, unique = true) + private String name; + + @OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL) + @JoinColumn(name = "avatar_id") + private Avatar avatar; + + @OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL) + @JoinColumn(name = "student_id") + private List emails; + + //@BatchSize(size = 5) + //@Fetch(FetchMode.SUBSELECT) + @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY) + @JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"), + inverseJoinColumns = @JoinColumn(name = "course_id")) + private List courses; +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java new file mode 100644 index 00000000..2ed2b5f8 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Avatar.java @@ -0,0 +1,21 @@ +package ru.otus.example.ormdemo.models.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "avatars") +public class Avatar { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "photo_url", nullable = false, unique = true) + private String photoUrl; +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java new file mode 100644 index 00000000..6ce8b993 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/Course.java @@ -0,0 +1,21 @@ +package ru.otus.example.ormdemo.models.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "courses") +public class Course { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "name", nullable = false, unique = true) + private String name; +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java new file mode 100644 index 00000000..1bc8cf74 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/models/common/EMail.java @@ -0,0 +1,22 @@ +package ru.otus.example.ormdemo.models.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "emails") +public class EMail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name = "email", nullable = false, unique = true) + private String email; +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java new file mode 100644 index 00000000..dc6d0811 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpa.java @@ -0,0 +1,7 @@ +package ru.otus.example.ormdemo.repositories; + +import ru.otus.example.ormdemo.models.common.Course; + +public interface CourseRepositoryJpa { + Course save(Course course); +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java new file mode 100644 index 00000000..d4ce0bd8 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/CourseRepositoryJpaImpl.java @@ -0,0 +1,18 @@ +package ru.otus.example.ormdemo.repositories; + +import ru.otus.example.ormdemo.models.common.Course; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +public class CourseRepositoryJpaImpl implements CourseRepositoryJpa { + + @PersistenceContext + private EntityManager em; + + @Override + public Course save(Course course) { + em.persist(course); + return course; + } +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java new file mode 100644 index 00000000..ba9eedda --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpa.java @@ -0,0 +1,14 @@ +package ru.otus.example.ormdemo.repositories; + + +import ru.otus.example.ormdemo.models.OtusStudent; + +import java.util.List; +import java.util.Optional; + +public interface OtusStudentRepositoryJpa { + Optional findById(long id); + List findAll(); + OtusStudent save(OtusStudent student); + +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java new file mode 100644 index 00000000..0f27448a --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java @@ -0,0 +1,37 @@ +package ru.otus.example.ormdemo.repositories; + +import org.springframework.stereotype.Repository; +import ru.otus.example.ormdemo.models.OtusStudent; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.Optional; + +@Repository +public class OtusStudentRepositoryJpaImpl implements OtusStudentRepositoryJpa { + + @PersistenceContext + private EntityManager em; + + @Override + public Optional findById(long id) { + return Optional.ofNullable(em.find(OtusStudent.class, id)); + } + + @Override + public List findAll() { + return em.createQuery("select s from OtusStudent s", OtusStudent.class).getResultList(); + } + + @Override + public OtusStudent save(OtusStudent student) { + if (student.getId() <= 0) { + em.persist(student); + //em.flush(); + return student; + } else { + return em.merge(student); + } + } +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java new file mode 100644 index 00000000..5893319f --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpa.java @@ -0,0 +1,11 @@ +package ru.otus.example.ormdemo.repositories; + + +import ru.otus.example.ormdemo.models.OtusStudentV2; + +import java.util.List; + +public interface OtusStudentV2RepositoryJpa { + List findAllWithEntityGraph(); + List findAllWithJoinFetch(); +} diff --git a/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java new file mode 100644 index 00000000..df615f41 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImpl.java @@ -0,0 +1,30 @@ +package ru.otus.example.ormdemo.repositories; + +import org.springframework.stereotype.Repository; +import ru.otus.example.ormdemo.models.OtusStudentV2; + +import javax.persistence.EntityGraph; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; +import java.util.List; + +@Repository +public class OtusStudentV2RepositoryJpaImpl implements OtusStudentV2RepositoryJpa { + + @PersistenceContext + private EntityManager em; + + @Override + public List findAllWithEntityGraph() { + EntityGraph entityGraph = em.getEntityGraph("OtusStudentWithAvatarAndEmails"); + TypedQuery query = em.createQuery("select s from OtusStudentV2 s", OtusStudentV2.class); + query.setHint("javax.persistence.fetchgraph", entityGraph); + return query.getResultList(); + } + + @Override + public List findAllWithJoinFetch() { + return em.createQuery("select distinct s from OtusStudentV2 s join fetch s.avatar join fetch s.emails", OtusStudentV2.class).getResultList(); + } +} diff --git a/2019-08/spring-07/orm-demo/src/main/resources/schema.sql b/2019-08/spring-07/orm-demo/src/main/resources/schema.sql new file mode 100644 index 00000000..43a684bb --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/main/resources/schema.sql @@ -0,0 +1,31 @@ +create table avatars( + id bigserial, + photo_url varchar(8000), + primary key (id) +); + +create table courses( + id bigserial, + name varchar(255), + primary key (id) +); + +create table otus_students( + id bigserial, + name varchar(255), + avatar_id bigint references avatars (id), + primary key (id) +); + +create table emails( + id bigserial, + student_id bigint references otus_students(id) on delete cascade, + email varchar(255), + primary key (id) +); + +create table student_courses( + student_id bigint references otus_students(id) on delete cascade, + course_id bigint references courses(id), + primary key (student_id, course_id) +); \ No newline at end of file diff --git a/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java new file mode 100644 index 00000000..6ce2aff9 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java @@ -0,0 +1,85 @@ +package ru.otus.example.ormdemo.repositories; + +import lombok.val; +import org.assertj.core.api.Assertions; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.context.annotation.Import; +import ru.otus.example.ormdemo.models.OtusStudent; +import ru.otus.example.ormdemo.models.common.Avatar; +import ru.otus.example.ormdemo.models.common.Course; +import ru.otus.example.ormdemo.models.common.EMail; + +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Репозиторий на основе Jpa для работы со студентами ") +@DataJpaTest +@Import({OtusStudentRepositoryJpaImpl.class, CourseRepositoryJpaImpl.class}) +class OtusStudentRepositoryJpaImplTest { + + private static final int EXPECTED_QUERIES_COUNT = 31; + + @Autowired + private OtusStudentRepositoryJpaImpl repositoryJpa; + + @Autowired + private CourseRepositoryJpaImpl courseRepositoryJpa; + + @Autowired + private TestEntityManager em; + + @DisplayName(" должен загружать информацию о нужном студенте") + @Test + void shouldFindExpectedStudentById() { + val optionalActualStudent = repositoryJpa.findById(1L); + val expectedStudent = em.find(OtusStudent.class, 1L); + assertThat(optionalActualStudent).isPresent().get().isEqualToComparingFieldByFieldRecursively(expectedStudent); + } + + @DisplayName("должен загружать список всех студентов с полной информацией о них") + @Test + void shouldReturnCorrectStudentsListWithAllInfo() { + SessionFactory sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class); + sessionFactory.getStatistics().setStatisticsEnabled(true); + + + System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------"); + val students = repositoryJpa.findAll(); + assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals("")) + .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) + .allMatch(s -> s.getAvatar() != null) + .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0); + System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n"); + assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT); + } + + @DisplayName(" должен корректно сохранять всю информацию о студенте") + @Test + void shouldSaveAllStudentInfo() { + val avatar = new Avatar(0, "где-то там"); + val email = new EMail(0, "any@mail.com"); + val emails = Collections.singletonList(email); + + val course = new Course(0, "Spring"); + val courses = Collections.singletonList(course); + //courseRepositoryJpa.save(course); + + + val vasya = new OtusStudent(- 1, "Vasya", avatar, emails, courses); + repositoryJpa.save(vasya); + assertThat(vasya.getId()).isGreaterThan(0); + + val actualStudent = em.find(OtusStudent.class, vasya.getId()); + assertThat(actualStudent).isNotNull().matches(s -> !s.getName().equals("")) + .matches(s -> s.getCourses() != null && s.getCourses().size() > 0 && s.getCourses().get(0).getId() > 0) + .matches(s -> s.getAvatar() != null) + .matches(s -> s.getEmails() != null && s.getEmails().size() > 0); + } +} \ No newline at end of file diff --git a/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java new file mode 100644 index 00000000..c23ff494 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentV2RepositoryJpaImplTest.java @@ -0,0 +1,67 @@ +package ru.otus.example.ormdemo.repositories; + +import org.hibernate.SessionFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager; +import org.springframework.context.annotation.Import; +import ru.otus.example.ormdemo.models.OtusStudentV2; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Репозиторий v2 на основе Jpa для работы со студентами ") +@DataJpaTest +@Import({OtusStudentV2RepositoryJpaImpl.class}) +class OtusStudentV2RepositoryJpaImplTest { + + private static final int EXPECTED_QUERIES_COUNT = 11; + //private static final int EXPECTED_QUERIES_COUNT = 2; + //private static final int EXPECTED_QUERIES_COUNT = 3; + + @Autowired + private OtusStudentV2RepositoryJpaImpl repositoryJpa; + + @Autowired + private TestEntityManager em; + + private SessionFactory sessionFactory; + + @BeforeEach + void setUp() { + sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class); + sessionFactory.getStatistics().setStatisticsEnabled(true); + sessionFactory.getStatistics().clear(); + } + + @DisplayName(" с помощью EntityGraph должен загружать список всех студентов с полной информацией о них") + @Test + void usingEntityGraphShouldReturnCorrectStudentsListWithWithAllInfo() { + System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------"); + List students = repositoryJpa.findAllWithEntityGraph(); + assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals("")) + .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) + .allMatch(s -> s.getAvatar() != null) + .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0); + System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n"); + assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT); + + } + + @DisplayName(" с помощью 'join fetch' должен загружать список всех студентов с полной информацией о них") + @Test + void usingJoinFetchShouldReturnCorrectStudentsListWithWithAllInfo() { + System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------"); + List students = repositoryJpa.findAllWithJoinFetch(); + assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals("")) + .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) + .allMatch(s -> s.getAvatar() != null) + .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0); + System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n"); + assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT); + } +} \ No newline at end of file diff --git a/2019-08/spring-07/orm-demo/src/test/resources/application.yml b/2019-08/spring-07/orm-demo/src/test/resources/application.yml new file mode 100644 index 00000000..b98cd926 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/test/resources/application.yml @@ -0,0 +1,18 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + initialization-mode: always + + jpa: + show-sql: false + generate-ddl: false + #generate-ddl: true + hibernate: + ddl-auto: none + #ddl-auto: create-drop + + #show-sql: true + +logging: + level: + ROOT: ERROR \ No newline at end of file diff --git a/2019-08/spring-07/orm-demo/src/test/resources/data.sql b/2019-08/spring-07/orm-demo/src/test/resources/data.sql new file mode 100644 index 00000000..a8db6b85 --- /dev/null +++ b/2019-08/spring-07/orm-demo/src/test/resources/data.sql @@ -0,0 +1,29 @@ +insert into avatars(photo_url) +values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'), + ('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10'); + +insert into courses(name) +values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'), + ('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11'); + +insert into otus_students(name, avatar_id) +values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5), + ('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10); + + +insert into emails(email, student_id) +values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4), + ('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10); + + +insert into student_courses(student_id, course_id) +values (1, 1), (1, 2), (1, 3), + (2, 2), (2, 4), (2, 5), + (3, 3), (3, 6), (3, 7), + (4, 4), (4, 8), (4, 9), + (5, 5), (5, 10), (5, 1), + (6, 6), (6, 2), (6, 3), + (7, 7), (7, 4), (7, 5), + (8, 8), (8, 6), (8, 7), + (9, 9), (9, 8), (9, 10), + (10, 10), (10, 1), (10, 2); diff --git a/2019-08/spring-07/spring-jdbc-demo/.gitignore b/2019-08/spring-07/spring-jdbc-demo/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/.gitignore @@ -0,0 +1,29 @@ +HELP.md +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + +### VS Code ### +.vscode/ diff --git a/2019-08/spring-07/spring-jdbc-demo/README.md b/2019-08/spring-07/spring-jdbc-demo/README.md new file mode 100644 index 00000000..462216c3 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/README.md @@ -0,0 +1,2 @@ +# spring-jdbc-demo +Пример работы с БД через jdbc \ No newline at end of file diff --git a/2019-08/spring-07/spring-jdbc-demo/pom.xml b/2019-08/spring-07/spring-jdbc-demo/pom.xml new file mode 100644 index 00000000..48052d83 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/pom.xml @@ -0,0 +1,69 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.8.RELEASE + + + ru.otus.example + spring-jdbc-demo + 0.0.1-SNAPSHOT + spring-jdbc-demo + Spring jdbc demo + + + 11 + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + com.h2database + h2 + runtime + + + + org.projectlombok + lombok + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter.version} + test + + + + org.junit.jupiter + junit-jupiter-engine + ${junit-jupiter.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java new file mode 100644 index 00000000..28ebfec4 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/SpringJdbcDemoApplication.java @@ -0,0 +1,13 @@ +package ru.otus.example.springjdbcdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringJdbcDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringJdbcDemoApplication.class, args); + } + +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java new file mode 100644 index 00000000..a1963ea4 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Avatar.java @@ -0,0 +1,13 @@ +package ru.otus.example.springjdbcdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Avatar { + private long id; + private String photoUrl; +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java new file mode 100644 index 00000000..07b6e2c2 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/Course.java @@ -0,0 +1,13 @@ +package ru.otus.example.springjdbcdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class Course { + private long id; + private String name; +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java new file mode 100644 index 00000000..16985ad5 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/EMail.java @@ -0,0 +1,13 @@ +package ru.otus.example.springjdbcdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EMail { + private long id; + private String email; +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java new file mode 100644 index 00000000..5ae9167c --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/models/OtusStudent.java @@ -0,0 +1,18 @@ +package ru.otus.example.springjdbcdemo.models; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class OtusStudent { + private long id; + private String name; + private Avatar avatar; + private List emails; + private List courses; +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java new file mode 100644 index 00000000..716ab880 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbc.java @@ -0,0 +1,9 @@ +package ru.otus.example.springjdbcdemo.repositories; + +import ru.otus.example.springjdbcdemo.models.Course; + +import java.util.List; + +public interface CourseRepositoryJdbc { + List findAllUsed(); +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java new file mode 100644 index 00000000..aedb3ad5 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/CourseRepositoryJdbcImpl.java @@ -0,0 +1,36 @@ +package ru.otus.example.springjdbcdemo.repositories; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Repository; +import ru.otus.example.springjdbcdemo.models.Course; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +@Repository +@RequiredArgsConstructor +public class CourseRepositoryJdbcImpl implements CourseRepositoryJdbc { + + @Autowired + private final JdbcOperations op; + + @Override + public List findAllUsed() { + return op.query("select c.id, c.name " + + "from courses c inner join student_courses sc on c.id = sc.course_id " + + "group by c.id, c.name " + + "order by c.name", new CourseRowMapper()); + } + + private class CourseRowMapper implements RowMapper { + @Override + public Course mapRow(ResultSet rs, int i) throws SQLException { + return new Course(rs.getLong(1), rs.getString(2)); + } + } + +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java new file mode 100644 index 00000000..0761373d --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbc.java @@ -0,0 +1,9 @@ +package ru.otus.example.springjdbcdemo.repositories; + +import ru.otus.example.springjdbcdemo.models.OtusStudent; + +import java.util.List; + +public interface OtusStudentRepositoryJdbc { + List findAllWithAllInfo(); +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java new file mode 100644 index 00000000..05208672 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImpl.java @@ -0,0 +1,49 @@ +package ru.otus.example.springjdbcdemo.repositories; + +import lombok.RequiredArgsConstructor; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.stereotype.Repository; +import ru.otus.example.springjdbcdemo.models.Course; +import ru.otus.example.springjdbcdemo.models.OtusStudent; +import ru.otus.example.springjdbcdemo.repositories.ext.OtusStudentResultSetExtractor; +import ru.otus.example.springjdbcdemo.repositories.ext.StudentCourseRelation; + +import java.util.*; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class OtusStudentRepositoryJdbcImpl implements OtusStudentRepositoryJdbc { + + private final CourseRepositoryJdbc courseRepository; + private final JdbcOperations op; + + @Override + public List findAllWithAllInfo() { + List courses = courseRepository.findAllUsed(); + List relations = getAllRelations(); + Map students = op.query("select os.id, os.name, a.id avatar_id, a.photo_url, e.id email_id, e.email " + + "from (otus_students os left join avatars a on " + + "os.avatar_id = a.id) left join emails e on os.id = e.student_id", + new OtusStudentResultSetExtractor()); + + mergeStudentsInfo(students, courses, relations); + return new ArrayList<>(Objects.requireNonNull(students).values()); + } + + private List getAllRelations() { + return op.query("select student_id, course_id from student_courses sc order by student_id, course_id", + (rs, i) -> new StudentCourseRelation(rs.getLong(1), rs.getLong(2))); + } + + private void mergeStudentsInfo(Map students, List courses, List relations) { + Map coursesMap = courses.stream().collect(Collectors.toMap(Course::getId, c -> c)); + relations.forEach(r -> { + if (students.containsKey(r.getStudentId()) && coursesMap.containsKey(r.getCourseId())) { + students.get(r.getStudentId()).getCourses().add(coursesMap.get(r.getCourseId())); + } + }); + } + + +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java new file mode 100644 index 00000000..778a46b7 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/OtusStudentResultSetExtractor.java @@ -0,0 +1,37 @@ +package ru.otus.example.springjdbcdemo.repositories.ext; + +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ResultSetExtractor; +import ru.otus.example.springjdbcdemo.models.Avatar; +import ru.otus.example.springjdbcdemo.models.EMail; +import ru.otus.example.springjdbcdemo.models.OtusStudent; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class OtusStudentResultSetExtractor implements + ResultSetExtractor> { + @Override + public Map extractData(ResultSet rs) throws SQLException, + DataAccessException { + + Map students = new HashMap<>(); + while (rs.next()) { + long id = rs.getLong("id"); + OtusStudent student = students.get(id); + if (student == null) { + student = new OtusStudent(id, rs.getString("name"), + new Avatar(rs.getLong("avatar_id"), rs.getString("photo_url")), + new ArrayList<>(), new ArrayList<>()); + students.put(student.getId(), student); + } + + student.getEmails().add(new EMail(rs.getLong("email_id"), + rs.getString("email"))); + } + return students; + } +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/StudentCourseRelation.java b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/StudentCourseRelation.java new file mode 100644 index 00000000..408c97d2 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/java/ru/otus/example/springjdbcdemo/repositories/ext/StudentCourseRelation.java @@ -0,0 +1,11 @@ +package ru.otus.example.springjdbcdemo.repositories.ext; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +@Data +@RequiredArgsConstructor +public class StudentCourseRelation { + private final long studentId; + private final long courseId; +} diff --git a/2019-08/spring-07/spring-jdbc-demo/src/main/resources/schema.sql b/2019-08/spring-07/spring-jdbc-demo/src/main/resources/schema.sql new file mode 100644 index 00000000..43a684bb --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/main/resources/schema.sql @@ -0,0 +1,31 @@ +create table avatars( + id bigserial, + photo_url varchar(8000), + primary key (id) +); + +create table courses( + id bigserial, + name varchar(255), + primary key (id) +); + +create table otus_students( + id bigserial, + name varchar(255), + avatar_id bigint references avatars (id), + primary key (id) +); + +create table emails( + id bigserial, + student_id bigint references otus_students(id) on delete cascade, + email varchar(255), + primary key (id) +); + +create table student_courses( + student_id bigint references otus_students(id) on delete cascade, + course_id bigint references courses(id), + primary key (student_id, course_id) +); \ No newline at end of file diff --git a/2019-08/spring-07/spring-jdbc-demo/src/test/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImplTest.java b/2019-08/spring-07/spring-jdbc-demo/src/test/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImplTest.java new file mode 100644 index 00000000..fa924ae2 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-demo/src/test/java/ru/otus/example/springjdbcdemo/repositories/OtusStudentRepositoryJdbcImplTest.java @@ -0,0 +1,31 @@ +package ru.otus.example.springjdbcdemo.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.jdbc.JdbcTest; +import org.springframework.context.annotation.Import; + + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("Репозиторий на основе Jdbc для работы со студентами ") +@JdbcTest +@Import({OtusStudentRepositoryJdbcImpl.class, CourseRepositoryJdbcImpl.class}) +class OtusStudentRepositoryJdbcImplTest { + + @Autowired + private OtusStudentRepositoryJdbcImpl repositoryJdbc; + + @DisplayName("должен загружать список всех студентов с полной информацией о них") + @Test + void shouldReturnCorrectStudentsListWithAllInfo() { + val students = repositoryJdbc.findAllWithAllInfo(); + assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals("")) + .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) + .allMatch(s -> s.getAvatar() != null) + .allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0); + + } +} \ No newline at end of file diff --git a/2019-08/spring-07/spring-jdbc-demo/src/test/resources/application.yml b/2019-08/spring-07/spring-jdbc-demo/src/test/resources/application.yml new file mode 100644 index 00000000..a91bdbba --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-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.ormbasicsdemo.repositories.mybatis: TRACE \ No newline at end of file diff --git a/2019-08/spring-07/spring-jdbc-demo/src/test/resources/data.sql b/2019-08/spring-07/spring-jdbc-demo/src/test/resources/data.sql new file mode 100644 index 00000000..a8db6b85 --- /dev/null +++ b/2019-08/spring-07/spring-jdbc-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);