diff --git a/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/pom.xml b/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/pom.xml index 436a2a0a..4f560553 100644 --- a/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/pom.xml +++ b/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/pom.xml @@ -30,6 +30,11 @@ spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-test + + org.springframework.boot spring-boot-starter-thymeleaf diff --git a/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java b/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java new file mode 100644 index 00000000..71f42162 --- /dev/null +++ b/2020-02/spring-16/spring-mvc-view-classwork/spring-mvc-view-solution/src/test/java/ru/otus/spring/rest/PersonControllerTest.java @@ -0,0 +1,17 @@ +package ru.otus.spring.rest; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.junit.jupiter.api.Assertions.*; + +@WebMvcTest +@ExtendWith(SpringExtension.class) +class PersonControllerTest { + + @org.junit.jupiter.api.Test + void listPage() { + } +} \ No newline at end of file diff --git a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java index f3822503..891e945a 100644 --- a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java +++ b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java @@ -1,6 +1,8 @@ package ru.otus.example.ormdemo.models; import lombok.*; +import org.hibernate.annotations.Fetch; +import org.hibernate.annotations.FetchMode; import javax.persistence.*; import java.util.List; @@ -10,6 +12,8 @@ import java.util.List; @AllArgsConstructor @Entity // Указывает, что данный класс является сущностью @Table(name = "otus_students") // Задает имя таблицы, на которую будет отображаться сущность +@NamedEntityGraph(name = "otus-student-avatars-entity-graph", + attributeNodes = {@NamedAttributeNode("avatar")}) public class OtusStudent { @Id // Позволяет указать какое поле является идентификатором @GeneratedValue(strategy = GenerationType.IDENTITY) // Стратегия генерации идентификаторов @@ -30,6 +34,7 @@ public class OtusStudent { @JoinColumn(name = "student_id") private List emails; + @Fetch(FetchMode.SUBSELECT) // Указывает на связь между таблицами "многие ко многим" @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) // Задает таблицу связей между таблицами для хранения родительской и связанной сущностью diff --git a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java index 5baca1e7..442d6e24 100644 --- a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java +++ b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/main/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImpl.java @@ -4,10 +4,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import ru.otus.example.ormdemo.models.OtusStudent; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; -import javax.persistence.TypedQuery; +import javax.persistence.*; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -26,31 +23,55 @@ public class OtusStudentRepositoryJpaImpl implements OtusStudentRepositoryJpa { @Override public OtusStudent save(OtusStudent student) { - return null; + if (student.getId() == 0) { + Query query = em.createQuery("insert into OtusStudent (name) " + + "select :name from OtusStudent s"); + query.setParameter("name", student.getName()); + query.executeUpdate(); + return student; + } else { + return em.merge(student); + } } @Override - public Optional findById(long id) { - return Optional.empty(); + public Optional findById (long id) { + return Optional.ofNullable(em.find(OtusStudent.class, id)); } @Override public List findAll() { - return Collections.emptyList(); + EntityGraph entityGraph = em.getEntityGraph("otus-student-avatars-entity-graph"); + TypedQuery query = em.createQuery("select s from OtusStudent s join fetch s.emails", OtusStudent.class); + query.setHint("javax.persistence.fetchgraph", entityGraph); + return query.getResultList(); } @Override public List findByName(String name) { - return Collections.emptyList(); + TypedQuery query = em.createQuery + ("select s from OtusStudent s where s.name = :name", OtusStudent.class); + query.setParameter("name", name); + return query.getResultList(); } @Override public void updateNameById(long id, String name) { - + Query query = em.createQuery("update OtusStudent s " + + "set s.name = :name " + + "where s.id = :id"); + query.setParameter("name", name); + query.setParameter("id", id); + query.executeUpdate(); } @Override public void deleteById(long id) { + Query query = em.createQuery("delete " + + "from OtusStudent s " + + "where s.id = :id"); + query.setParameter("id", id); + query.executeUpdate(); } } diff --git a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java index 9cad9ee7..818a1fe8 100644 --- a/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java +++ b/2020-05/spring-09-jpql/jpql-class-work/jpql-exercise/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java @@ -27,7 +27,7 @@ class OtusStudentRepositoryJpaImplTest { private static final long FIRST_STUDENT_ID = 1L; private static final String FIRST_STUDENT_NAME = "student_01"; - private static final int EXPECTED_QUERIES_COUNT = 31; + private static final int EXPECTED_QUERIES_COUNT = 2; private static final String STUDENT_AVATAR_URL = "где-то там"; private static final String STUDENT_EMAIL = "any@mail.com"; @@ -43,6 +43,7 @@ class OtusStudentRepositoryJpaImplTest { @DisplayName(" должен загружать информацию о нужном студенте по его id") @Test void shouldFindExpectedStudentById() { + val optionalActualStudent = repositoryJpa.findById(FIRST_STUDENT_ID); val expectedStudent = em.find(OtusStudent.class, FIRST_STUDENT_ID); assertThat(optionalActualStudent).isPresent().get() diff --git a/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java b/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java index ecdbf955..44bbbb98 100644 --- a/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java +++ b/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/main/java/ru/otus/example/ormdemo/models/OtusStudent.java @@ -37,8 +37,6 @@ public class OtusStudent { @JoinColumn(name = "student_id") private List emails; - @Fetch(FetchMode.SELECT) - @BatchSize(size = 5) // Указывает на связь между таблицами "многие ко многим" @ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) // Задает таблицу связей между таблицами для хранения родительской и связанной сущностью diff --git a/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java b/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java index e2992ba0..a8e57fe3 100644 --- a/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java +++ b/2020-05/spring-09-jpql/jpql-class-work/jpql-solution-final/src/test/java/ru/otus/example/ormdemo/repositories/OtusStudentRepositoryJpaImplTest.java @@ -47,6 +47,8 @@ class OtusStudentRepositoryJpaImplTest { System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------"); val students = repositoryJpa.findAll(); + val student = repositoryJpa.findById(1L); + assertThat(students).isNotNull().hasSize(EXPECTED_NUMBER_OF_STUDENTS) .allMatch(s -> !s.getName().equals("")) .allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0) diff --git a/2020-05/spring-09-jpql/jpql-demo/src/test/java/ru/otus/example/jpql_demo/repositories/EmployeeRepositoryImplTest.java b/2020-05/spring-09-jpql/jpql-demo/src/test/java/ru/otus/example/jpql_demo/repositories/EmployeeRepositoryImplTest.java index bf6c0b26..7f4055c8 100644 --- a/2020-05/spring-09-jpql/jpql-demo/src/test/java/ru/otus/example/jpql_demo/repositories/EmployeeRepositoryImplTest.java +++ b/2020-05/spring-09-jpql/jpql-demo/src/test/java/ru/otus/example/jpql_demo/repositories/EmployeeRepositoryImplTest.java @@ -257,6 +257,7 @@ class EmployeeRepositoryImplTest { BigDecimal newSalary = oldSalary.multiply(new BigDecimal(2)); em.detach(employee3); employeeRepository.doubleEmployeesSalary(oldSalary); + //em.refresh(employee3); employee3 = em.find(Employee.class, THIRD_EMPLOYEE_ID); assertThat(employee3.getSalary()).isEqualTo(newSalary); @@ -271,7 +272,7 @@ class EmployeeRepositoryImplTest { assertThat(employee8).isNotNull(); employeeRepository.deleteEmployeesWithoutDepartment(); - em.clear(); + em.flush(); employee2 = em.find(Employee.class, SECOND_EMPLOYEE_ID); employee8 = em.find(Employee.class, EIGTH_EMPLOYEE_ID); diff --git a/examples/mongo-db-demo-new-mongock/.gitignore b/examples/mongo-db-demo-new-mongock/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/.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/examples/mongo-db-demo-new-mongock/README.md b/examples/mongo-db-demo-new-mongock/README.md new file mode 100644 index 00000000..84065306 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/README.md @@ -0,0 +1,7 @@ +## Пример работы с MongoDB + +В примере демонстрируется: +* *два подхода к хранению вложенных сущностей* +* *инициализация базы данными с помощью инструмента миграций Mongock* +* *использование AbstractMongoEventListener для выполнения каскадных операций* +* *работа с массивами с помощью агрегаций и Criteria api* diff --git a/examples/mongo-db-demo-new-mongock/pom.xml b/examples/mongo-db-demo-new-mongock/pom.xml new file mode 100644 index 00000000..3cb9852b --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 2.3.1.RELEASE + + + + ru.otus.example + mongo-db-demo + 0.0.1-SNAPSHOT + mongo-db-demo + Demo project for MongoDB + + + 11 + 11 + 11 + 4.1.2.BETA + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + + org.projectlombok + lombok + true + + + + + com.github.cloudyrock.mongock + mongock-spring-v5 + ${mongock.version} + + + + com.github.cloudyrock.mongock + mongodb-springdata-v3-driver + ${mongock.version} + + + + + + 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.junit.jupiter + junit-jupiter-params + ${junit-jupiter.version} + test + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + + + diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/MongoDbDemoApplication.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/MongoDbDemoApplication.java new file mode 100644 index 00000000..4b26baf5 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/MongoDbDemoApplication.java @@ -0,0 +1,16 @@ +package ru.otus.example.mongodbdemo; + +import com.github.cloudyrock.spring.v5.EnableMongock; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@EnableMongock +@SpringBootApplication +@EnableConfigurationProperties +public class MongoDbDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MongoDbDemoApplication.class, args); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/changelogs/InitMongoDBDataChangeLog.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/changelogs/InitMongoDBDataChangeLog.java new file mode 100644 index 00000000..d8cd7f29 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/changelogs/InitMongoDBDataChangeLog.java @@ -0,0 +1,43 @@ +package ru.otus.example.mongodbdemo.changelogs; + +import com.github.cloudyrock.mongock.ChangeLog; +import com.github.cloudyrock.mongock.ChangeSet; +import com.mongodb.client.MongoDatabase; +import lombok.val; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.model.Teacher; +import ru.otus.example.mongodbdemo.repositories.KnowledgeRepository; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; +import ru.otus.example.mongodbdemo.repositories.TeacherRepository; + +@ChangeLog(order = "001") +public class InitMongoDBDataChangeLog { + + private Knowledge springDataKnowledge; + private Knowledge mongockKnowledge; + private Knowledge aggregationApiKnowledge; + + @ChangeSet(order = "000", id = "dropDB", author = "stvort", runAlways = true) + public void dropDB(MongoDatabase database){ + database.drop(); + } + + @ChangeSet(order = "001", id = "initKnowledges", author = "stvort", runAlways = true) + public void initKnowledges(KnowledgeRepository repository){ + springDataKnowledge = repository.save(new Knowledge("Spring Data")); + mongockKnowledge = repository.save(new Knowledge("Mongock")); + aggregationApiKnowledge = repository.save(new Knowledge("AggregationApi")); + } + + @ChangeSet(order = "002", id = "initStudents", author = "stvort", runAlways = true) + public void initStudents(StudentRepository repository){ + repository.save(new Student("Student #1", springDataKnowledge, mongockKnowledge)); + } + + @ChangeSet(order = "003", id = "Teacher", author = "stvort", runAlways = true) + public void initTeachers(TeacherRepository repository){ + val teacher = new Teacher("Teacher #1", springDataKnowledge, mongockKnowledge, aggregationApiKnowledge); + repository.save(teacher); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoKnowledgeCascadeDeleteEventsListener.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoKnowledgeCascadeDeleteEventsListener.java new file mode 100644 index 00000000..08f6c601 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoKnowledgeCascadeDeleteEventsListener.java @@ -0,0 +1,24 @@ +package ru.otus.example.mongodbdemo.events; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; +import org.springframework.data.mongodb.core.mapping.event.AfterDeleteEvent; +import org.springframework.stereotype.Component; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; + +@Component +@RequiredArgsConstructor +public class MongoKnowledgeCascadeDeleteEventsListener extends AbstractMongoEventListener { + + private final StudentRepository studentRepository; + + @Override + public void onAfterDelete(AfterDeleteEvent event) { + super.onAfterDelete(event); + val source = event.getSource(); + val id = source.get("_id").toString(); + studentRepository.removeExperienceArrayElementsById(id); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoStudentCascadeSaveEventsListener.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoStudentCascadeSaveEventsListener.java new file mode 100644 index 00000000..89ff7d97 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/events/MongoStudentCascadeSaveEventsListener.java @@ -0,0 +1,27 @@ +package ru.otus.example.mongodbdemo.events; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener; +import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent; +import org.springframework.stereotype.Component; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.repositories.KnowledgeRepository; + +import java.util.Objects; + +@Component +@RequiredArgsConstructor +public class MongoStudentCascadeSaveEventsListener extends AbstractMongoEventListener { + + private final KnowledgeRepository knowledgeRepository; + + @Override + public void onBeforeConvert(BeforeConvertEvent event) { + super.onBeforeConvert(event); + val student = event.getSource(); + if (student.getExperience() != null) { + student.getExperience().stream().filter(e -> Objects.isNull(e.getId())).forEach(knowledgeRepository::save); + } + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Knowledge.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Knowledge.java new file mode 100644 index 00000000..04583a57 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Knowledge.java @@ -0,0 +1,21 @@ +package ru.otus.example.mongodbdemo.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Document +public class Knowledge { + @Id + private String id; + private String name; + + public Knowledge(String name) { + this.name = name; + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Student.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Student.java new file mode 100644 index 00000000..bf228359 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Student.java @@ -0,0 +1,29 @@ +package ru.otus.example.mongodbdemo.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.DBRef; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Arrays; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Document +public class Student { + @Id + private String id; + private String name; + + @DBRef + private List experience; + + public Student(String name, Knowledge... experience) { + this.name = name; + this.experience = Arrays.asList(experience); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Teacher.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Teacher.java new file mode 100644 index 00000000..aa8e1166 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/model/Teacher.java @@ -0,0 +1,28 @@ +package ru.otus.example.mongodbdemo.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.Arrays; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Document +public class Teacher { + @Id + private String id; + private String name; + + private List experience; + + public Teacher(String name, Knowledge... experience) { + this.name = name; + this.experience = Arrays.asList(experience); + + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/KnowledgeRepository.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/KnowledgeRepository.java new file mode 100644 index 00000000..c8605cb4 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/KnowledgeRepository.java @@ -0,0 +1,7 @@ +package ru.otus.example.mongodbdemo.repositories; + +import org.springframework.data.mongodb.repository.MongoRepository; +import ru.otus.example.mongodbdemo.model.Knowledge; + +public interface KnowledgeRepository extends MongoRepository { +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepository.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepository.java new file mode 100644 index 00000000..bc603fe5 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepository.java @@ -0,0 +1,8 @@ +package ru.otus.example.mongodbdemo.repositories; + +import org.springframework.data.mongodb.repository.MongoRepository; +import ru.otus.example.mongodbdemo.model.Student; + + +public interface StudentRepository extends MongoRepository, StudentRepositoryCustom { +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustom.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustom.java new file mode 100644 index 00000000..8b902bbf --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustom.java @@ -0,0 +1,12 @@ +package ru.otus.example.mongodbdemo.repositories; + +import ru.otus.example.mongodbdemo.model.Knowledge; + +import java.util.List; + +public interface StudentRepositoryCustom { + List getStudentExperienceById(String studentId); + + long getExperienceArrayLengthByStudentId(String id); + void removeExperienceArrayElementsById(String id); +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustomImpl.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustomImpl.java new file mode 100644 index 00000000..91fb3536 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/StudentRepositoryCustomImpl.java @@ -0,0 +1,69 @@ +package ru.otus.example.mongodbdemo.repositories; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.bson.Document; +import org.bson.types.ObjectId; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.aggregation.Aggregation; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.utils.RawResultPrinter; + + +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; +import static org.springframework.data.mongodb.core.aggregation.ObjectOperators.ObjectToArray.valueOfToArray; +import static org.springframework.data.mongodb.core.query.Criteria.where; + +@RequiredArgsConstructor +public class StudentRepositoryCustomImpl implements StudentRepositoryCustom { + + @Data + private class ArraySizeProjection { + private int size; + } + + private final MongoTemplate mongoTemplate; + private final RawResultPrinter rawResultPrinter; + + @Override + public List getStudentExperienceById(String studentId) { + Aggregation aggregation = newAggregation( + match(Criteria.where("id").is(studentId)) + , unwind("experience") + , project().andExclude("_id").and(valueOfToArray("experience")).as("experience_map") + , project().and("experience_map").arrayElementAt(1).as("experience_id_map") + , project().and("experience_id_map.v").as("experience_id") + , lookup("knowledge", "experience_id", "_id", "experience") + , project().and("experience._id").as("_id").and("experience.name").as("name") + ); + + Document rawResults = mongoTemplate.aggregate(aggregation, Student.class, Knowledge.class).getRawResults(); + rawResultPrinter.prettyPrintRawResult(rawResults); + return mongoTemplate.aggregate(aggregation, Student.class, Knowledge.class).getMappedResults(); + } + + @Override + public long getExperienceArrayLengthByStudentId(String studentId) { + val aggregation = Aggregation.newAggregation( + match(where("id").is(studentId)), + project().andExclude("_id").and("experience").size().as("size")); + + val arraySizeProjection = mongoTemplate.aggregate(aggregation, Student.class, ArraySizeProjection.class).getUniqueMappedResult(); + return arraySizeProjection == null ? 0 : arraySizeProjection.getSize(); + } + + @Override + public void removeExperienceArrayElementsById(String id) { + val query = Query.query(Criteria.where("$id").is(new ObjectId(id))); + val update = new Update().pull("experience", query); + mongoTemplate.updateMulti(new Query(), update, Student.class); + } + +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepository.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepository.java new file mode 100644 index 00000000..b75c5200 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepository.java @@ -0,0 +1,7 @@ +package ru.otus.example.mongodbdemo.repositories; + +import org.springframework.data.mongodb.repository.MongoRepository; +import ru.otus.example.mongodbdemo.model.Teacher; + +public interface TeacherRepository extends MongoRepository, TeacherRepositoryCustom { +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustom.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustom.java new file mode 100644 index 00000000..adfd9acb --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustom.java @@ -0,0 +1,9 @@ +package ru.otus.example.mongodbdemo.repositories; + +import ru.otus.example.mongodbdemo.model.Knowledge; + +import java.util.List; + +public interface TeacherRepositoryCustom { + List getTeacherExperienceById(String teacherId); +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustomImpl.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustomImpl.java new file mode 100644 index 00000000..d529928d --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryCustomImpl.java @@ -0,0 +1,28 @@ +package ru.otus.example.mongodbdemo.repositories; + +import lombok.RequiredArgsConstructor; +import lombok.val; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Teacher; + +import java.util.List; + +import static org.springframework.data.mongodb.core.aggregation.Aggregation.*; + +@RequiredArgsConstructor +public class TeacherRepositoryCustomImpl implements TeacherRepositoryCustom { + + private final MongoTemplate mongoTemplate; + + @Override + public List getTeacherExperienceById(String teacherId) { + val aggregation = newAggregation( + match(Criteria.where("id").is(teacherId)) + , unwind("experience") + , project().andExclude("_id").and("experience.id").as("_id").and("experience.name").as("name") + ); + return mongoTemplate.aggregate(aggregation, Teacher.class, Knowledge.class).getMappedResults(); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinter.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinter.java new file mode 100644 index 00000000..e033d42d --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinter.java @@ -0,0 +1,8 @@ +package ru.otus.example.mongodbdemo.utils; + +import org.bson.Document; + +public interface RawResultPrinter { + @SuppressWarnings("unchecked") + void prettyPrintRawResult(Document document); +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinterImpl.java b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinterImpl.java new file mode 100644 index 00000000..1fcd9a7c --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/java/ru/otus/example/mongodbdemo/utils/RawResultPrinterImpl.java @@ -0,0 +1,18 @@ +package ru.otus.example.mongodbdemo.utils; + +import org.bson.Document; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class RawResultPrinterImpl implements RawResultPrinter { + @SuppressWarnings("unchecked") + @Override + public void prettyPrintRawResult(Document document) { + List results = (List) document.get("results"); + String resultsAsString = results.stream().map(Document::toString).collect(Collectors.joining("\n")); + System.out.println(resultsAsString); + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/main/resources/application.yml b/examples/mongo-db-demo-new-mongock/src/main/resources/application.yml new file mode 100644 index 00000000..4d338124 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/main/resources/application.yml @@ -0,0 +1,9 @@ +spring: + data: + mongodb: + uri: mongodb://localhost + port: 27017 + database: awesomeMongo +logging: + level: + root: ERROR \ No newline at end of file diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/AbstractRepositoryTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/AbstractRepositoryTest.java new file mode 100644 index 00000000..f1342168 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/AbstractRepositoryTest.java @@ -0,0 +1,14 @@ +package ru.otus.example.mongodbdemo.repositories; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Import; +import ru.otus.example.mongodbdemo.utils.RawResultPrinterImpl; + +@DataMongoTest +@EnableConfigurationProperties +@ComponentScan({"ru.otus.example.mongodbdemo.config", "ru.otus.example.mongodbdemo.repositories"}) +@Import(RawResultPrinterImpl.class) +public abstract class AbstractRepositoryTest { +} diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryTest.java new file mode 100644 index 00000000..f6fd8713 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/TeacherRepositoryTest.java @@ -0,0 +1,29 @@ +package ru.otus.example.mongodbdemo.repositories; + +import lombok.val; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import ru.otus.example.mongodbdemo.model.Knowledge; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("TeacherRepository должен ") +class TeacherRepositoryTest extends AbstractRepositoryTest { + + @Autowired + private TeacherRepository teacherRepository; + + @DisplayName(" возвращать корректный список знаний преподавателя") + @Test + void shouldReturnCorrectKnowledgeList(){ + val teachers = teacherRepository.findAll(); + val teacher = teachers.get(0); + val experience = teacher.getExperience(); + assertThat(experience).isNotNull().hasSize(3); + + val actualExperience = teacherRepository.getTeacherExperienceById(teacher.getId()); + assertThat(actualExperience).containsExactlyInAnyOrder(experience.toArray(new Knowledge[0])); + + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/KnowledgeRepositoryWithoutListenerTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/KnowledgeRepositoryWithoutListenerTest.java new file mode 100644 index 00000000..c68d61d6 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/KnowledgeRepositoryWithoutListenerTest.java @@ -0,0 +1,58 @@ +package ru.otus.example.mongodbdemo.repositories.plain; + +import lombok.val; +import org.assertj.core.api.AbstractObjectAssert; +import org.assertj.core.api.AbstractOptionalAssert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.repositories.AbstractRepositoryTest; +import ru.otus.example.mongodbdemo.repositories.KnowledgeRepository; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; + +import java.util.List; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + + +@DisplayName("KnowledgeRepository при отсутствии listener-ов в контексте ") +class KnowledgeRepositoryWithoutListenerTest extends AbstractRepositoryTest { + + @Autowired + private KnowledgeRepository knowledgeRepository; + + @Autowired + private StudentRepository studentRepository; + + @DisplayName("при удалении Knowledge не должен удалять его из опыта студента") + @Test + void shouldLeaveKnowledgeInStudentExperienceWhenRemovingKnowledge() { + + // Загрузка студента и его первого знания + val students = studentRepository.findAll(); + val student = students.get(0); + val experience = student.getExperience(); + val firstKnowledge = experience.get(0); + + // Удаление знания из коллекции знаний + knowledgeRepository.delete(firstKnowledge); + + // Загружаем студента заново и проверяем, что знание действительно удалено (размер стал меньше на 1) + val expectedExperienceArrayLength = experience.size() - 1; + val actualStudentOptional = studentRepository.findById(student.getId()); + assertThat(actualStudentOptional) + .isNotEmpty().get() + .matches(s -> s.getExperience() != null && s.getExperience().size() == expectedExperienceArrayLength); + + + // Загружаем размер массива с помощью аггрегаций и проверяем, что на самом деле размер массива в БД не изменился + val actualExperienceArrayLength = studentRepository.getExperienceArrayLengthByStudentId(student.getId()); + assertThat(actualExperienceArrayLength).isNotEqualTo(expectedExperienceArrayLength); + + + + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/StudentRepositoryWithoutListenerTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/StudentRepositoryWithoutListenerTest.java new file mode 100644 index 00000000..c688bcee --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/plain/StudentRepositoryWithoutListenerTest.java @@ -0,0 +1,27 @@ +package ru.otus.example.mongodbdemo.repositories.plain; + +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.data.mapping.MappingException; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.repositories.AbstractRepositoryTest; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; + +import static org.assertj.core.api.Assertions.*; + +@DisplayName("StudentRepository при отсутствии listener-ов в контексте ") +class StudentRepositoryWithoutListenerTest extends AbstractRepositoryTest { + + @Autowired + private StudentRepository studentRepository; + + @DisplayName("должен кидать MappingException во время сохранения студента с отсутствующими в БД знаниями") + @Test + void shouldThrowMappingExceptionWhenSaveStudentWithNewKnowledge(){ + val student = new Student("Student #2", new Knowledge("Knowledge #3")); + assertThatThrownBy(() -> studentRepository.save(student)).isInstanceOf(MappingException.class); + } +} \ No newline at end of file diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/KnowledgeRepositoryWithListenerTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/KnowledgeRepositoryWithListenerTest.java new file mode 100644 index 00000000..743f3599 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/KnowledgeRepositoryWithListenerTest.java @@ -0,0 +1,49 @@ +package ru.otus.example.mongodbdemo.repositories.withlisteners; + +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.context.annotation.ComponentScan; +import ru.otus.example.mongodbdemo.repositories.AbstractRepositoryTest; +import ru.otus.example.mongodbdemo.repositories.KnowledgeRepository; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("KnowledgeRepository при наличии listener-ов в контексте ") +@ComponentScan("ru.otus.example.mongodbdemo.events") +class KnowledgeRepositoryWithListenerTest extends AbstractRepositoryTest { + + @Autowired + private KnowledgeRepository knowledgeRepository; + + @Autowired + private StudentRepository studentRepository; + + @DisplayName("при удалении Knowledge должен удалить его из опыта студента") + @Test + void shouldRemoveKnowledgeFromStudentExperienceWhenRemovingKnowledge() { + + // Загрузка студента и его первого знания + val students = studentRepository.findAll(); + val student = students.get(0); + val experience = student.getExperience(); + val firstKnowledge = experience.get(0); + + // Удаление знания из коллекции знаний + knowledgeRepository.delete(firstKnowledge); + + // Загружаем студента заново и проверяем, что знание действительно удалено (размер стал меньше на 1) + val expectedExperienceArrayLength = experience.size() - 1; + val actualStudentOptional = studentRepository.findById(student.getId()); + assertThat(actualStudentOptional) + .isNotEmpty().get() + .matches(s -> s.getExperience() != null && s.getExperience().size() == expectedExperienceArrayLength); + + // Загружаем размер массива с помощью аггрегаций и проверяем, что размер массива в БД тоже изменился + val actualExperienceArrayLength = studentRepository.getExperienceArrayLengthByStudentId(student.getId()); + assertThat(actualExperienceArrayLength).isEqualTo(expectedExperienceArrayLength); + + } +} diff --git a/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/StudentRepositoryWithListenersTest.java b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/StudentRepositoryWithListenersTest.java new file mode 100644 index 00000000..f70740c8 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/java/ru/otus/example/mongodbdemo/repositories/withlisteners/StudentRepositoryWithListenersTest.java @@ -0,0 +1,41 @@ +package ru.otus.example.mongodbdemo.repositories.withlisteners; + +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.context.annotation.ComponentScan; +import ru.otus.example.mongodbdemo.model.Knowledge; +import ru.otus.example.mongodbdemo.model.Student; +import ru.otus.example.mongodbdemo.repositories.AbstractRepositoryTest; +import ru.otus.example.mongodbdemo.repositories.StudentRepository; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +@DisplayName("StudentRepository при наличии listener-ов в контексте ") +@ComponentScan("ru.otus.example.mongodbdemo.events") +class StudentRepositoryWithListenersTest extends AbstractRepositoryTest { + + @Autowired + private StudentRepository studentRepository; + + @DisplayName("должен корректно сохранять студента с отсутствующими в БД знаниями") + @Test + void shouldCorrectSaveStudentWithNewKnowledge(){ + val student = new Student("Student #2", new Knowledge("Knowledge #3")); + val actual = studentRepository.save(student); + assertThat(actual.getId()).isNotNull(); + assertThat(actual.getName()).isEqualTo(student.getName()); + } + + @DisplayName("должен возвращать корректный список знаний студента") + @Test + void shouldReturnCorrectStudentKnowledgeList(){ + val student = studentRepository.findAll().get(0); + List knowledgeList = studentRepository.getStudentExperienceById(student.getId()); + assertThat(knowledgeList).containsExactlyInAnyOrderElementsOf(student.getExperience()); + } + +} diff --git a/examples/mongo-db-demo-new-mongock/src/test/resources/application.yml b/examples/mongo-db-demo-new-mongock/src/test/resources/application.yml new file mode 100644 index 00000000..737c0712 --- /dev/null +++ b/examples/mongo-db-demo-new-mongock/src/test/resources/application.yml @@ -0,0 +1,18 @@ +spring: + data: + mongodb: + port: 0 + database: test + #uri: mongodb://localhost + #port: 27017 + #database: awesomeMongo + + + + mongock: + change-logs-scan-package: + - ru.otus.example.mongodbdemo.changelogs + +logging: + level: + root: ERROR \ No newline at end of file diff --git a/examples/mongo-db-demo/pom.xml b/examples/mongo-db-demo/pom.xml index 76744a32..9c301b94 100644 --- a/examples/mongo-db-demo/pom.xml +++ b/examples/mongo-db-demo/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.1.RELEASE + 2.2.6.RELEASE @@ -20,7 +20,7 @@ 11 11 11 - 2.0.2 + 3.2.8