From cd6f9757dfc507326e3d484d28edae97f9ed16dc Mon Sep 17 00:00:00 2001 From: stvort Date: Mon, 1 Apr 2019 23:05:36 +0400 Subject: [PATCH] mongo-db-demo added --- README.md | 3 +- mongo-db-demo/.gitignore | 29 +++++ mongo-db-demo/README.md | 7 + mongo-db-demo/pom.xml | 123 ++++++++++++++++++ .../mongoDbDemo/MongoDbDemoApplication.java | 15 +++ .../changelogs/InitMongoDBDataChangeLog.java | 42 ++++++ .../mongoDbDemo/config/ApplicationConfig.java | 20 +++ .../mongoDbDemo/config/MongoProps.java | 14 ++ ...oKnowledgeCascadeDeleteEventsListener.java | 24 ++++ ...MongoStudentCascadeSaveEventsListener.java | 27 ++++ .../example/mongoDbDemo/model/Knowledge.java | 21 +++ .../example/mongoDbDemo/model/Student.java | 29 +++++ .../example/mongoDbDemo/model/Teacher.java | 28 ++++ .../repositories/KnowledgeRepository.java | 7 + .../repositories/StudentRepository.java | 8 ++ .../repositories/StudentRepositoryCustom.java | 6 + .../StudentRepositoryCustomImpl.java | 42 ++++++ .../repositories/TeacherRepository.java | 7 + .../repositories/TeacherRepositoryCustom.java | 9 ++ .../TeacherRepositoryCustomImpl.java | 28 ++++ .../src/main/resources/application.yml | 6 + .../repositories/AbstractRepositoryTest.java | 13 ++ .../KnowledgeRepositoryWithListenerTest.java | 41 ++++++ ...nowledgeRepositoryWithoutListenerTest.java | 39 ++++++ .../StudentRepositoryWithListenersTest.java | 29 +++++ .../StudentRepositoryWithoutListenerTest.java | 25 ++++ .../repositories/TeacherRepositoryTest.java | 29 +++++ .../src/test/resources/application.yml | 5 + 28 files changed, 675 insertions(+), 1 deletion(-) create mode 100644 mongo-db-demo/.gitignore create mode 100644 mongo-db-demo/README.md create mode 100644 mongo-db-demo/pom.xml create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/MongoDbDemoApplication.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/changelogs/InitMongoDBDataChangeLog.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/ApplicationConfig.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/MongoProps.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoKnowledgeCascadeDeleteEventsListener.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoStudentCascadeSaveEventsListener.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Knowledge.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Student.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Teacher.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepository.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepository.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustom.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustomImpl.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepository.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustom.java create mode 100644 mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustomImpl.java create mode 100644 mongo-db-demo/src/main/resources/application.yml create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/AbstractRepositoryTest.java create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithListenerTest.java create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithoutListenerTest.java create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithListenersTest.java create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithoutListenerTest.java create mode 100644 mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryTest.java create mode 100644 mongo-db-demo/src/test/resources/application.yml diff --git a/README.md b/README.md index 962be65c..598f2fc3 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,5 @@ * *unit-testing-plain-spring* - Spring * *unit-testing-spring-boot* - , Spring Boot -* *hibernate-fetch-mode-demo* - Hibernate, N+1 \ No newline at end of file +* *hibernate-fetch-mode-demo* - Hibernate, N+1 +* *mongo-db-demo* - MongoDB, MongoEventListener, Mongock \ No newline at end of file diff --git a/mongo-db-demo/.gitignore b/mongo-db-demo/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/mongo-db-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/mongo-db-demo/README.md b/mongo-db-demo/README.md new file mode 100644 index 00000000..84065306 --- /dev/null +++ b/mongo-db-demo/README.md @@ -0,0 +1,7 @@ +## Пример работы с MongoDB + +В примере демонстрируется: +* *два подхода к хранению вложенных сущностей* +* *инициализация базы данными с помощью инструмента миграций Mongock* +* *использование AbstractMongoEventListener для выполнения каскадных операций* +* *работа с массивами с помощью агрегаций и Criteria api* diff --git a/mongo-db-demo/pom.xml b/mongo-db-demo/pom.xml new file mode 100644 index 00000000..471f134e --- /dev/null +++ b/mongo-db-demo/pom.xml @@ -0,0 +1,123 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.3.RELEASE + + + ru.otus.example + mongo-db-demo + 0.0.1-SNAPSHOT + mongo-db-demo + Demo project for MongoDB + + + 1.8 + 1.8. + 1.8 + + + + + + org.springframework.boot + spring-boot-starter-data-mongodb + + + + org.projectlombok + lombok + true + + + + com.github.cloudyrock.mongock + mongock-spring + 2.0.0 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.assertj + org.assertj.core + + + + + + + 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 + 2.21.0 + test + + + + org.mockito + mockito-junit-jupiter + 2.23.0 + test + + + + org.assertj + assertj-core + 3.12.0 + 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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/MongoDbDemoApplication.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/MongoDbDemoApplication.java new file mode 100644 index 00000000..9c73fb61 --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/MongoDbDemoApplication.java @@ -0,0 +1,15 @@ +package ru.otus.example.mongoDbDemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties +public class MongoDbDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(MongoDbDemoApplication.class, args); + } + +} diff --git a/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/changelogs/InitMongoDBDataChangeLog.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/changelogs/InitMongoDBDataChangeLog.java new file mode 100644 index 00000000..2ea282ad --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/changelogs/InitMongoDBDataChangeLog.java @@ -0,0 +1,42 @@ +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 org.springframework.data.mongodb.core.MongoTemplate; +import ru.otus.example.mongoDbDemo.model.Knowledge; +import ru.otus.example.mongoDbDemo.model.Student; +import ru.otus.example.mongoDbDemo.model.Teacher; + +@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(MongoTemplate template){ + springDataKnowledge = template.save(new Knowledge("Spring Data")); + mongockKnowledge = template.save(new Knowledge("Mongock")); + aggregationApiKnowledge = template.save(new Knowledge("AggregationApi")); + } + + @ChangeSet(order = "002", id = "initStudents", author = "stvort", runAlways = true) + public void initStudents(MongoTemplate template){ + val student = new Student("Student #1", springDataKnowledge, mongockKnowledge); + template.save(student); + } + + @ChangeSet(order = "003", id = "Teacher", author = "stvort", runAlways = true) + public void initTeachers(MongoTemplate template){ + val teacher = new Teacher("Teacher #1", springDataKnowledge, mongockKnowledge, aggregationApiKnowledge); + template.save(teacher); + } +} diff --git a/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/ApplicationConfig.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/ApplicationConfig.java new file mode 100644 index 00000000..9324a457 --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/ApplicationConfig.java @@ -0,0 +1,20 @@ +package ru.otus.example.mongoDbDemo.config; + +import com.github.cloudyrock.mongock.Mongock; +import com.github.cloudyrock.mongock.SpringMongockBuilder; +import com.mongodb.MongoClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApplicationConfig { + + private static final String CHANGELOGS_PACKAGE = "ru.otus.example.mongoDbDemo.changelogs"; + + @Bean + public Mongock mongock(MongoProps mongoProps, MongoClient mongoClient) { + return new SpringMongockBuilder(mongoClient, mongoProps.getDatabase(), CHANGELOGS_PACKAGE) + .setLockQuickConfig() + .build(); + } +} diff --git a/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/MongoProps.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/MongoProps.java new file mode 100644 index 00000000..182bd70e --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/config/MongoProps.java @@ -0,0 +1,14 @@ +package ru.otus.example.mongoDbDemo.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties("spring.data.mongodb") +public class MongoProps { + private int port; + private String database; + private String uri; +} diff --git a/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoKnowledgeCascadeDeleteEventsListener.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoKnowledgeCascadeDeleteEventsListener.java new file mode 100644 index 00000000..4d87adb5 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoStudentCascadeSaveEventsListener.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/events/MongoStudentCascadeSaveEventsListener.java new file mode 100644 index 00000000..552adf39 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Knowledge.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Knowledge.java new file mode 100644 index 00000000..8853fcc6 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Student.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Student.java new file mode 100644 index 00000000..38ada6ef --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Teacher.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/model/Teacher.java new file mode 100644 index 00000000..8544e281 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepository.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepository.java new file mode 100644 index 00000000..96bc0258 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepository.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepository.java new file mode 100644 index 00000000..87643323 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustom.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustom.java new file mode 100644 index 00000000..47be7f5f --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustom.java @@ -0,0 +1,6 @@ +package ru.otus.example.mongoDbDemo.repositories; + +public interface StudentRepositoryCustom { + long getExperienceArrayLengthByStudentId(String id); + void removeExperienceArrayElementsById(String id); +} diff --git a/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustomImpl.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustomImpl.java new file mode 100644 index 00000000..e32e7772 --- /dev/null +++ b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryCustomImpl.java @@ -0,0 +1,42 @@ +package ru.otus.example.mongoDbDemo.repositories; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.val; +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.Student; + + +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; + + public long getExperienceArrayLengthByStudentId(String id) { + val aggregation = Aggregation.newAggregation( + Aggregation.match(where("id").is(id)), + Aggregation.project().andExclude("_id").and("experience").size().as("size")); + + val arraySizeProjection = mongoTemplate.aggregate(aggregation, Student.class, ArraySizeProjection.class).getUniqueMappedResult(); + return arraySizeProjection == null ? 0 : arraySizeProjection.getSize(); + } + + 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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepository.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepository.java new file mode 100644 index 00000000..1a9c45ab --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustom.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustom.java new file mode 100644 index 00000000..bfc2fdf5 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustomImpl.java b/mongo-db-demo/src/main/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryCustomImpl.java new file mode 100644 index 00000000..d4da4d53 --- /dev/null +++ b/mongo-db-demo/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/mongo-db-demo/src/main/resources/application.yml b/mongo-db-demo/src/main/resources/application.yml new file mode 100644 index 00000000..85b3052d --- /dev/null +++ b/mongo-db-demo/src/main/resources/application.yml @@ -0,0 +1,6 @@ +spring: + data: + mongodb: + uri: mongodb://localhost + port: 27017 + database: test diff --git a/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/AbstractRepositoryTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/AbstractRepositoryTest.java new file mode 100644 index 00000000..02984b19 --- /dev/null +++ b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/AbstractRepositoryTest.java @@ -0,0 +1,13 @@ +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.test.annotation.DirtiesContext; + +@DataMongoTest +@EnableConfigurationProperties +@ComponentScan({"ru.otus.example.mongoDbDemo.config", "ru.otus.example.mongoDbDemo.repositories"}) +@DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) +abstract class AbstractRepositoryTest { +} diff --git a/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithListenerTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithListenerTest.java new file mode 100644 index 00000000..0e3e5976 --- /dev/null +++ b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithListenerTest.java @@ -0,0 +1,41 @@ +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 org.springframework.context.annotation.ComponentScan; + +import static org.assertj.core.api.AssertionsForClassTypes.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); + + val expectedExperienceArrayLength = experience.size() - 1; + val actualExperienceArrayLength = studentRepository.getExperienceArrayLengthByStudentId(student.getId()); + assertThat(actualExperienceArrayLength).isEqualTo(expectedExperienceArrayLength); + + val actualStudentOptional = studentRepository.findById(student.getId()); + assertThat(actualStudentOptional.get().getExperience().size()).isEqualTo(expectedExperienceArrayLength); + + } +} diff --git a/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithoutListenerTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithoutListenerTest.java new file mode 100644 index 00000000..d42084d2 --- /dev/null +++ b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/KnowledgeRepositoryWithoutListenerTest.java @@ -0,0 +1,39 @@ +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 static org.assertj.core.api.AssertionsForClassTypes.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); + + val expectedExperienceArrayLength = experience.size(); + val actualExperienceArrayLength = studentRepository.getExperienceArrayLengthByStudentId(student.getId()); + assertThat(actualExperienceArrayLength).isEqualTo(expectedExperienceArrayLength); + + val actualStudentOptional = studentRepository.findById(student.getId()); + assertThat(actualStudentOptional.get().getExperience().size()).isNotEqualTo(expectedExperienceArrayLength); + + } +} diff --git a/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithListenersTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithListenersTest.java new file mode 100644 index 00000000..6073664f --- /dev/null +++ b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithListenersTest.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 org.springframework.context.annotation.ComponentScan; +import ru.otus.example.mongoDbDemo.model.Knowledge; +import ru.otus.example.mongoDbDemo.model.Student; + +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()); + } + +} diff --git a/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithoutListenerTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithoutListenerTest.java new file mode 100644 index 00000000..878ec199 --- /dev/null +++ b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/StudentRepositoryWithoutListenerTest.java @@ -0,0 +1,25 @@ +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 org.springframework.data.mapping.MappingException; +import ru.otus.example.mongoDbDemo.model.Knowledge; +import ru.otus.example.mongoDbDemo.model.Student; + +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/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryTest.java b/mongo-db-demo/src/test/java/ru/otus/example/mongoDbDemo/repositories/TeacherRepositoryTest.java new file mode 100644 index 00000000..01241aa2 --- /dev/null +++ b/mongo-db-demo/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[experience.size()])); + + } +} diff --git a/mongo-db-demo/src/test/resources/application.yml b/mongo-db-demo/src/test/resources/application.yml new file mode 100644 index 00000000..25d7422a --- /dev/null +++ b/mongo-db-demo/src/test/resources/application.yml @@ -0,0 +1,5 @@ +spring: + data: + mongodb: + port: 0 + database: test \ No newline at end of file