mirror of
https://github.com/OtusTeam/Spring.git
synced 2026-06-20 11:14:34 +00:00
mongo example with new mongock added
This commit is contained in:
@@ -30,6 +30,11 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
|
||||
+17
@@ -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() {
|
||||
}
|
||||
}
|
||||
+5
@@ -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<EMail> emails;
|
||||
|
||||
@Fetch(FetchMode.SUBSELECT)
|
||||
// Указывает на связь между таблицами "многие ко многим"
|
||||
@ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
// Задает таблицу связей между таблицами для хранения родительской и связанной сущностью
|
||||
|
||||
+31
-10
@@ -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<OtusStudent> findById(long id) {
|
||||
return Optional.empty();
|
||||
public Optional<OtusStudent> findById (long id) {
|
||||
return Optional.ofNullable(em.find(OtusStudent.class, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OtusStudent> findAll() {
|
||||
return Collections.emptyList();
|
||||
EntityGraph<?> entityGraph = em.getEntityGraph("otus-student-avatars-entity-graph");
|
||||
TypedQuery<OtusStudent> 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<OtusStudent> findByName(String name) {
|
||||
return Collections.emptyList();
|
||||
TypedQuery<OtusStudent> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -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()
|
||||
|
||||
-2
@@ -37,8 +37,6 @@ public class OtusStudent {
|
||||
@JoinColumn(name = "student_id")
|
||||
private List<EMail> emails;
|
||||
|
||||
@Fetch(FetchMode.SELECT)
|
||||
@BatchSize(size = 5)
|
||||
// Указывает на связь между таблицами "многие ко многим"
|
||||
@ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
|
||||
// Задает таблицу связей между таблицами для хранения родительской и связанной сущностью
|
||||
|
||||
+2
@@ -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)
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
@@ -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/
|
||||
@@ -0,0 +1,7 @@
|
||||
## Пример работы с MongoDB
|
||||
|
||||
В примере демонстрируется:
|
||||
* *два подхода к хранению вложенных сущностей*
|
||||
* *инициализация базы данными с помощью инструмента миграций Mongock*
|
||||
* *использование AbstractMongoEventListener для выполнения каскадных операций*
|
||||
* *работа с массивами с помощью агрегаций и Criteria api*
|
||||
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.1.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
<groupId>ru.otus.example</groupId>
|
||||
<artifactId>mongo-db-demo</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<name>mongo-db-demo</name>
|
||||
<description>Demo project for MongoDB</description>
|
||||
|
||||
<properties>
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.sourcre>11</maven.compiler.sourcre>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<mongock.version>4.1.2.BETA</mongock.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb</artifactId>
|
||||
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.github.cloudyrock.mongock/mongock-spring-v5 -->
|
||||
<dependency>
|
||||
<groupId>com.github.cloudyrock.mongock</groupId>
|
||||
<artifactId>mongock-spring-v5</artifactId>
|
||||
<version>${mongock.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.cloudyrock.mongock</groupId>
|
||||
<artifactId>mongodb-springdata-v3-driver</artifactId>
|
||||
<version>${mongock.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Тестирование-->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
+16
@@ -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);
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+24
@@ -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<Knowledge> {
|
||||
|
||||
private final StudentRepository studentRepository;
|
||||
|
||||
@Override
|
||||
public void onAfterDelete(AfterDeleteEvent<Knowledge> event) {
|
||||
super.onAfterDelete(event);
|
||||
val source = event.getSource();
|
||||
val id = source.get("_id").toString();
|
||||
studentRepository.removeExperienceArrayElementsById(id);
|
||||
}
|
||||
}
|
||||
+27
@@ -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<Student> {
|
||||
|
||||
private final KnowledgeRepository knowledgeRepository;
|
||||
|
||||
@Override
|
||||
public void onBeforeConvert(BeforeConvertEvent<Student> event) {
|
||||
super.onBeforeConvert(event);
|
||||
val student = event.getSource();
|
||||
if (student.getExperience() != null) {
|
||||
student.getExperience().stream().filter(e -> Objects.isNull(e.getId())).forEach(knowledgeRepository::save);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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;
|
||||
}
|
||||
}
|
||||
+29
@@ -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<Knowledge> experience;
|
||||
|
||||
public Student(String name, Knowledge... experience) {
|
||||
this.name = name;
|
||||
this.experience = Arrays.asList(experience);
|
||||
}
|
||||
}
|
||||
+28
@@ -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<Knowledge> experience;
|
||||
|
||||
public Teacher(String name, Knowledge... experience) {
|
||||
this.name = name;
|
||||
this.experience = Arrays.asList(experience);
|
||||
|
||||
}
|
||||
}
|
||||
+7
@@ -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<Knowledge, String> {
|
||||
}
|
||||
+8
@@ -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<Student, String>, StudentRepositoryCustom {
|
||||
}
|
||||
+12
@@ -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<Knowledge> getStudentExperienceById(String studentId);
|
||||
|
||||
long getExperienceArrayLengthByStudentId(String id);
|
||||
void removeExperienceArrayElementsById(String id);
|
||||
}
|
||||
+69
@@ -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<Knowledge> 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);
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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<Teacher, String>, TeacherRepositoryCustom {
|
||||
}
|
||||
+9
@@ -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<Knowledge> getTeacherExperienceById(String teacherId);
|
||||
}
|
||||
+28
@@ -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<Knowledge> 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();
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package ru.otus.example.mongodbdemo.utils;
|
||||
|
||||
import org.bson.Document;
|
||||
|
||||
public interface RawResultPrinter {
|
||||
@SuppressWarnings("unchecked")
|
||||
void prettyPrintRawResult(Document document);
|
||||
}
|
||||
+18
@@ -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<Document> results = (List<Document>) document.get("results");
|
||||
String resultsAsString = results.stream().map(Document::toString).collect(Collectors.joining("\n"));
|
||||
System.out.println(resultsAsString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
spring:
|
||||
data:
|
||||
mongodb:
|
||||
uri: mongodb://localhost
|
||||
port: 27017
|
||||
database: awesomeMongo
|
||||
logging:
|
||||
level:
|
||||
root: ERROR
|
||||
+14
@@ -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 {
|
||||
}
|
||||
+29
@@ -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]));
|
||||
|
||||
}
|
||||
}
|
||||
+58
@@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
+27
@@ -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);
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
+41
@@ -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<Knowledge> knowledgeList = studentRepository.getStudentExperienceById(student.getId());
|
||||
assertThat(knowledgeList).containsExactlyInAnyOrderElementsOf(student.getExperience());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.1.RELEASE</version>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<java.version>11</java.version>
|
||||
<maven.compiler.sourcre>11</maven.compiler.sourcre>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<mongock.version>2.0.2</mongock.version>
|
||||
<mongock.version>3.2.8</mongock.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
Reference in New Issue
Block a user