spring-07 examples added

This commit is contained in:
stvort
2019-09-13 22:55:39 +04:00
parent 382a622db7
commit 1f2a956f5c
60 changed files with 1537 additions and 0 deletions
@@ -0,0 +1,2 @@
# application-events-demo
Пример работы с событиями
@@ -0,0 +1,2 @@
# beans-lifecycle-demo
Пример жизненного цикла бинов
@@ -0,0 +1,2 @@
# beans-scopes-demo
Пример работы со областью действия (Scope) бинов
@@ -0,0 +1,2 @@
# conditional-and-profiles-demo
Пример работы с профилями и автоконфигурациями
@@ -0,0 +1,2 @@
# test-configuration-demo
Пример конфигурирования тестов
+29
View File
@@ -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/
+2
View File
@@ -0,0 +1,2 @@
# mybatis-demo
Пример работы с БД через MyBatis
+71
View File
@@ -0,0 +1,71 @@
<?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.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>ru.otus.example</groupId>
<artifactId>mybatis-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-demo</name>
<description>MyBatis demo</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,14 @@
package ru.otus.example.mybatisdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class MyBatisDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisDemoApplication.class, args);
}
}
@@ -0,0 +1,13 @@
package ru.otus.example.mybatisdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Avatar {
private long id;
private String photoUrl;
}
@@ -0,0 +1,13 @@
package ru.otus.example.mybatisdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Course {
private long id;
private String name;
}
@@ -0,0 +1,13 @@
package ru.otus.example.mybatisdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EMail {
private long id;
private String email;
}
@@ -0,0 +1,18 @@
package ru.otus.example.mybatisdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OtusStudent {
private long id;
private String name;
private Avatar avatar;
private List<EMail> emails;
private List<Course> courses;
}
@@ -0,0 +1,18 @@
package ru.otus.example.mybatisdemo.repositories;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import ru.otus.example.mybatisdemo.models.Avatar;
@Mapper
public interface AvatarRepository {
@Select("select * from avatars where id = #{id}")
@Results(value = {
@Result(property = "id", column = "id"),
@Result(property = "photoUrl", column = "photo_url")
})
Avatar getAvatarById(long id);
}
@@ -0,0 +1,15 @@
package ru.otus.example.mybatisdemo.repositories;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import ru.otus.example.mybatisdemo.models.Course;
import java.util.List;
@Mapper
public interface CourseRepository {
@Select("select * from student_courses sc left join courses c on sc.course_id = c.id where sc.student_id = #{studentId}")
List<Course> getCoursesByStudentId(long studentId);
}
@@ -0,0 +1,14 @@
package ru.otus.example.mybatisdemo.repositories;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import ru.otus.example.mybatisdemo.models.EMail;
import java.util.List;
@Mapper
public interface EmailRepository {
@Select("select * from emails where student_id = #{studentId}")
List<EMail> getEmailsByStudentId(long studentId);
}
@@ -0,0 +1,42 @@
package ru.otus.example.mybatisdemo.repositories;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import ru.otus.example.mybatisdemo.models.Avatar;
import ru.otus.example.mybatisdemo.models.OtusStudent;
import java.util.List;
@Mapper
public interface OtusStudentRepository {
@Select("select * from otus_students")
@Results(id = "studentAllMap", value = {
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "avatar", column = "avatar_id", javaType = Avatar.class,
one = @One(select = "ru.otus.example.mybatisdemo.repositories.AvatarRepository.getAvatarById", fetchType = FetchType.EAGER)),
@Result(property = "emails", column = "id", javaType = List.class,
many = @Many(select = "ru.otus.example.mybatisdemo.repositories.EmailRepository.getEmailsByStudentId", fetchType = FetchType.EAGER)),
@Result(property = "courses", column = "id", javaType = List.class,
many = @Many(select = "ru.otus.example.mybatisdemo.repositories.CourseRepository.getCoursesByStudentId", fetchType = FetchType.EAGER))
})
List<OtusStudent> findAllWithAllInfo();
@Select("select * from otus_students where id = #{id}")
@ResultMap("studentAllMap")
OtusStudent findById(long id);
@Select("select count(*) as students_count from otus_students")
long getStudentsCount();
@Insert("insert into otus_students(name, avatar_id) values (#{name}, #{avatar.id})")
void insert(OtusStudent student);
@Update("update otus_students set name = #{name} where id = #{id}")
void updateName(OtusStudent student);
@Delete("delete from otus_students where id = #{id}")
void deleteById(long id);
}
@@ -0,0 +1,31 @@
create table avatars(
id bigserial,
photo_url varchar(8000),
primary key (id)
);
create table courses(
id bigserial,
name varchar(255),
primary key (id)
);
create table otus_students(
id bigserial,
name varchar(255),
avatar_id bigint references avatars (id),
primary key (id)
);
create table emails(
id bigserial,
student_id bigint references otus_students(id) on delete cascade,
email varchar(255),
primary key (id)
);
create table student_courses(
student_id bigint references otus_students(id) on delete cascade,
course_id bigint references courses(id),
primary key (student_id, course_id)
);
@@ -0,0 +1,87 @@
package ru.otus.example.mybatisdemo.repositories;
import lombok.val;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import ru.otus.example.mybatisdemo.models.Avatar;
import ru.otus.example.mybatisdemo.models.OtusStudent;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Репозиторий на основе MyBatis для работы со студентами ")
@SpringBootTest
@Transactional
public class OtusStudentRepositoryTest {
@Autowired
private OtusStudentRepository studentRepositoryMyBatis;
@DisplayName("должен загружать список всех студентов с полной информацией о них")
@Test
void shouldReturnCorrectStudentsListWithAllInfo() {
val students = studentRepositoryMyBatis.findAllWithAllInfo();
assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals(""))
.allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
.allMatch(s -> s.getAvatar() != null)
.allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
}
@DisplayName("должен загружать число студентов в БД")
@Test
void shouldReturnCorrectStudentsCount() {
long studentsCount = studentRepositoryMyBatis.getStudentsCount();
assertThat(studentsCount).isEqualTo(10);
}
@DisplayName(" должен загружать информацию о нужном студенте")
@Test
void shouldFindExpectedStudentById(){
val actualStudent = studentRepositoryMyBatis.findById(1L);
assertThat(actualStudent).isNotNull();
assertThat(actualStudent.getName()).isEqualTo("student_01");
assertThat(actualStudent.getAvatar()).isNotNull()
.hasFieldOrPropertyWithValue("id", 1L)
.hasFieldOrPropertyWithValue("photoUrl", "photoUrl_01");
assertThat(actualStudent.getEmails()).isNotNull().hasSize(2);
assertThat(actualStudent.getCourses()).isNotNull().hasSize(3);
}
@DisplayName(" должен сохранить, а потом загрузить информацию о нужном студенте")
@Test
void shouldSaveAndLoadCorrectStudent() {
val expectedStudent = new OtusStudent();
expectedStudent.setName("Vasya");
expectedStudent.setAvatar(new Avatar(1L, "photoUrl_01"));
studentRepositoryMyBatis.insert(expectedStudent);
val actualStudent = studentRepositoryMyBatis.findById(11L);
assertThat(actualStudent).isNotNull().isEqualToComparingOnlyGivenFields(expectedStudent, "name");
}
@DisplayName(" должен обновлять имя студента в БД")
@Test
void shouldUpdateStudentName() {
val student = studentRepositoryMyBatis.findById(1L);
student.setName("Висусуалий");
studentRepositoryMyBatis.updateName(student);
val actualStudent = studentRepositoryMyBatis.findById(1L);
assertThat(actualStudent).isNotNull().hasFieldOrPropertyWithValue("name", student.getName());
}
@DisplayName("должен удалять студента из БД по id")
@Test
void shouldDeleteStudentFromDbById() {
val studentsCountBefore = studentRepositoryMyBatis.getStudentsCount();
studentRepositoryMyBatis.deleteById(1L);
val studentsCountAfter = studentRepositoryMyBatis.getStudentsCount();
assertThat(studentsCountBefore - studentsCountAfter).isEqualTo(1);
}
}
@@ -0,0 +1,18 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
initialization-mode: always
jpa:
generate-ddl: false
#generate-ddl: true
hibernate:
ddl-auto: none
#ddl-auto: create-drop
show-sql: true
logging:
level:
ru.otus.example.mybatisdemo.repositories: TRACE
@@ -0,0 +1,29 @@
insert into avatars(photo_url)
values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'),
('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10');
insert into courses(name)
values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'),
('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11');
insert into otus_students(name, avatar_id)
values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5),
('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10);
insert into emails(email, student_id)
values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4),
('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10);
insert into student_courses(student_id, course_id)
values (1, 1), (1, 2), (1, 3),
(2, 2), (2, 4), (2, 5),
(3, 3), (3, 6), (3, 7),
(4, 4), (4, 8), (4, 9),
(5, 5), (5, 10), (5, 1),
(6, 6), (6, 2), (6, 3),
(7, 7), (7, 4), (7, 5),
(8, 8), (8, 6), (8, 7),
(9, 9), (9, 8), (9, 10),
(10, 10), (10, 1), (10, 2);
+29
View File
@@ -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/
+2
View File
@@ -0,0 +1,2 @@
# orm-demo
Пример работы с БД через ORM
+88
View File
@@ -0,0 +1,88 @@
<?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.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>ru.otus.example</groupId>
<artifactId>orm-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>orm-demo</name>
<description>Orm demo</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<!--
<exclusions>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</exclusion>
<exclusion>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
-->
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.7.4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,16 @@
package ru.otus.example.ormdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;
import ru.otus.example.ormdemo.config.EclipseLinkJpaConfiguration;
@SpringBootApplication
//@Import(EclipseLinkJpaConfiguration.class)
public class OrmDemoApplication {
public static void main(String[] args) {
SpringApplication.run(OrmDemoApplication.class, args);
}
}
@@ -0,0 +1,38 @@
package ru.otus.example.ormdemo.config;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class EclipseLinkJpaConfiguration extends JpaBaseConfiguration {
@Autowired
protected EclipseLinkJpaConfiguration(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, properties, jtaTransactionManager, transactionManagerCustomizers);
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new EclipseLinkJpaVendorAdapter();
}
@Override
protected Map<String, Object> getVendorProperties() {
HashMap<String, Object> map = new HashMap<>();
map.put(PersistenceUnitProperties.WEAVING, "false");
return map;
}
}
@@ -0,0 +1,42 @@
package ru.otus.example.ormdemo.models;
import lombok.*;
import ru.otus.example.ormdemo.models.common.Avatar;
import ru.otus.example.ormdemo.models.common.Course;
import ru.otus.example.ormdemo.models.common.EMail;
import javax.persistence.*;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity // Указывает, что данный класс является сущностью
@Table(name = "otus_students") // Задает имя таблицы, на которую будет отображаться сущность
public class OtusStudent {
@Id // Позволяет указать какое поле является идентификатором
@GeneratedValue(strategy = GenerationType.IDENTITY) // Стратегия генерации идентификаторов
private long id;
// Задает имя и некоторые свойства поля таблицы, на которое будет отображаться поле сущности
@Column(name = "name", nullable = false, unique = true)
private String name;
// Указывает на связь между таблицами "один к одному"
@OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL)
// Задает поле, по которому происходит объединение с таблицей для хранения связанной сущности
@JoinColumn(name = "avatar_id")
private Avatar avatar;
// Указывает на связь между таблицами "один ко многим"
@OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "student_id")
private List<EMail> emails;
// Указывает на связь между таблицами "многие ко многим"
@ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY /*, cascade = CascadeType.ALL*/)
// Задает таблицу связей между таблицами для хранения родительской и связанной сущностью
@JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private List<Course> courses;
}
@@ -0,0 +1,44 @@
package ru.otus.example.ormdemo.models;
import lombok.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import ru.otus.example.ormdemo.models.common.Avatar;
import ru.otus.example.ormdemo.models.common.Course;
import ru.otus.example.ormdemo.models.common.EMail;
import javax.persistence.*;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "otus_students")
@NamedEntityGraph(name = "OtusStudentWithAvatarAndEmails",
attributeNodes = {@NamedAttributeNode(value = "avatar"),
@NamedAttributeNode(value = "emails")})
public class OtusStudentV2 {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "name", nullable = false, unique = true)
private String name;
@OneToOne(targetEntity = Avatar.class, cascade = CascadeType.ALL)
@JoinColumn(name = "avatar_id")
private Avatar avatar;
@OneToMany(targetEntity = EMail.class, cascade = CascadeType.ALL)
@JoinColumn(name = "student_id")
private List<EMail> emails;
//@BatchSize(size = 5)
//@Fetch(FetchMode.SUBSELECT)
@ManyToMany(targetEntity = Course.class, fetch = FetchType.LAZY)
@JoinTable(name = "student_courses", joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id"))
private List<Course> courses;
}
@@ -0,0 +1,21 @@
package ru.otus.example.ormdemo.models.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "avatars")
public class Avatar {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "photo_url", nullable = false, unique = true)
private String photoUrl;
}
@@ -0,0 +1,21 @@
package ru.otus.example.ormdemo.models.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "courses")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "name", nullable = false, unique = true)
private String name;
}
@@ -0,0 +1,22 @@
package ru.otus.example.ormdemo.models.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "emails")
public class EMail {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column(name = "email", nullable = false, unique = true)
private String email;
}
@@ -0,0 +1,7 @@
package ru.otus.example.ormdemo.repositories;
import ru.otus.example.ormdemo.models.common.Course;
public interface CourseRepositoryJpa {
Course save(Course course);
}
@@ -0,0 +1,18 @@
package ru.otus.example.ormdemo.repositories;
import ru.otus.example.ormdemo.models.common.Course;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class CourseRepositoryJpaImpl implements CourseRepositoryJpa {
@PersistenceContext
private EntityManager em;
@Override
public Course save(Course course) {
em.persist(course);
return course;
}
}
@@ -0,0 +1,14 @@
package ru.otus.example.ormdemo.repositories;
import ru.otus.example.ormdemo.models.OtusStudent;
import java.util.List;
import java.util.Optional;
public interface OtusStudentRepositoryJpa {
Optional<OtusStudent> findById(long id);
List<OtusStudent> findAll();
OtusStudent save(OtusStudent student);
}
@@ -0,0 +1,37 @@
package ru.otus.example.ormdemo.repositories;
import org.springframework.stereotype.Repository;
import ru.otus.example.ormdemo.models.OtusStudent;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;
@Repository
public class OtusStudentRepositoryJpaImpl implements OtusStudentRepositoryJpa {
@PersistenceContext
private EntityManager em;
@Override
public Optional<OtusStudent> findById(long id) {
return Optional.ofNullable(em.find(OtusStudent.class, id));
}
@Override
public List<OtusStudent> findAll() {
return em.createQuery("select s from OtusStudent s", OtusStudent.class).getResultList();
}
@Override
public OtusStudent save(OtusStudent student) {
if (student.getId() <= 0) {
em.persist(student);
//em.flush();
return student;
} else {
return em.merge(student);
}
}
}
@@ -0,0 +1,11 @@
package ru.otus.example.ormdemo.repositories;
import ru.otus.example.ormdemo.models.OtusStudentV2;
import java.util.List;
public interface OtusStudentV2RepositoryJpa {
List<OtusStudentV2> findAllWithEntityGraph();
List<OtusStudentV2> findAllWithJoinFetch();
}
@@ -0,0 +1,30 @@
package ru.otus.example.ormdemo.repositories;
import org.springframework.stereotype.Repository;
import ru.otus.example.ormdemo.models.OtusStudentV2;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.List;
@Repository
public class OtusStudentV2RepositoryJpaImpl implements OtusStudentV2RepositoryJpa {
@PersistenceContext
private EntityManager em;
@Override
public List<OtusStudentV2> findAllWithEntityGraph() {
EntityGraph<?> entityGraph = em.getEntityGraph("OtusStudentWithAvatarAndEmails");
TypedQuery<OtusStudentV2> query = em.createQuery("select s from OtusStudentV2 s", OtusStudentV2.class);
query.setHint("javax.persistence.fetchgraph", entityGraph);
return query.getResultList();
}
@Override
public List<OtusStudentV2> findAllWithJoinFetch() {
return em.createQuery("select distinct s from OtusStudentV2 s join fetch s.avatar join fetch s.emails", OtusStudentV2.class).getResultList();
}
}
@@ -0,0 +1,31 @@
create table avatars(
id bigserial,
photo_url varchar(8000),
primary key (id)
);
create table courses(
id bigserial,
name varchar(255),
primary key (id)
);
create table otus_students(
id bigserial,
name varchar(255),
avatar_id bigint references avatars (id),
primary key (id)
);
create table emails(
id bigserial,
student_id bigint references otus_students(id) on delete cascade,
email varchar(255),
primary key (id)
);
create table student_courses(
student_id bigint references otus_students(id) on delete cascade,
course_id bigint references courses(id),
primary key (student_id, course_id)
);
@@ -0,0 +1,85 @@
package ru.otus.example.ormdemo.repositories;
import lombok.val;
import org.assertj.core.api.Assertions;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.context.annotation.Import;
import ru.otus.example.ormdemo.models.OtusStudent;
import ru.otus.example.ormdemo.models.common.Avatar;
import ru.otus.example.ormdemo.models.common.Course;
import ru.otus.example.ormdemo.models.common.EMail;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Репозиторий на основе Jpa для работы со студентами ")
@DataJpaTest
@Import({OtusStudentRepositoryJpaImpl.class, CourseRepositoryJpaImpl.class})
class OtusStudentRepositoryJpaImplTest {
private static final int EXPECTED_QUERIES_COUNT = 31;
@Autowired
private OtusStudentRepositoryJpaImpl repositoryJpa;
@Autowired
private CourseRepositoryJpaImpl courseRepositoryJpa;
@Autowired
private TestEntityManager em;
@DisplayName(" должен загружать информацию о нужном студенте")
@Test
void shouldFindExpectedStudentById() {
val optionalActualStudent = repositoryJpa.findById(1L);
val expectedStudent = em.find(OtusStudent.class, 1L);
assertThat(optionalActualStudent).isPresent().get().isEqualToComparingFieldByFieldRecursively(expectedStudent);
}
@DisplayName("должен загружать список всех студентов с полной информацией о них")
@Test
void shouldReturnCorrectStudentsListWithAllInfo() {
SessionFactory sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class);
sessionFactory.getStatistics().setStatisticsEnabled(true);
System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
val students = repositoryJpa.findAll();
assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals(""))
.allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
.allMatch(s -> s.getAvatar() != null)
.allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
}
@DisplayName(" должен корректно сохранять всю информацию о студенте")
@Test
void shouldSaveAllStudentInfo() {
val avatar = new Avatar(0, "где-то там");
val email = new EMail(0, "any@mail.com");
val emails = Collections.singletonList(email);
val course = new Course(0, "Spring");
val courses = Collections.singletonList(course);
//courseRepositoryJpa.save(course);
val vasya = new OtusStudent(- 1, "Vasya", avatar, emails, courses);
repositoryJpa.save(vasya);
assertThat(vasya.getId()).isGreaterThan(0);
val actualStudent = em.find(OtusStudent.class, vasya.getId());
assertThat(actualStudent).isNotNull().matches(s -> !s.getName().equals(""))
.matches(s -> s.getCourses() != null && s.getCourses().size() > 0 && s.getCourses().get(0).getId() > 0)
.matches(s -> s.getAvatar() != null)
.matches(s -> s.getEmails() != null && s.getEmails().size() > 0);
}
}
@@ -0,0 +1,67 @@
package ru.otus.example.ormdemo.repositories;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.context.annotation.Import;
import ru.otus.example.ormdemo.models.OtusStudentV2;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Репозиторий v2 на основе Jpa для работы со студентами ")
@DataJpaTest
@Import({OtusStudentV2RepositoryJpaImpl.class})
class OtusStudentV2RepositoryJpaImplTest {
private static final int EXPECTED_QUERIES_COUNT = 11;
//private static final int EXPECTED_QUERIES_COUNT = 2;
//private static final int EXPECTED_QUERIES_COUNT = 3;
@Autowired
private OtusStudentV2RepositoryJpaImpl repositoryJpa;
@Autowired
private TestEntityManager em;
private SessionFactory sessionFactory;
@BeforeEach
void setUp() {
sessionFactory = em.getEntityManager().getEntityManagerFactory().unwrap(SessionFactory.class);
sessionFactory.getStatistics().setStatisticsEnabled(true);
sessionFactory.getStatistics().clear();
}
@DisplayName(" с помощью EntityGraph должен загружать список всех студентов с полной информацией о них")
@Test
void usingEntityGraphShouldReturnCorrectStudentsListWithWithAllInfo() {
System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
List<OtusStudentV2> students = repositoryJpa.findAllWithEntityGraph();
assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals(""))
.allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
.allMatch(s -> s.getAvatar() != null)
.allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
}
@DisplayName(" с помощью 'join fetch' должен загружать список всех студентов с полной информацией о них")
@Test
void usingJoinFetchShouldReturnCorrectStudentsListWithWithAllInfo() {
System.out.println("\n\n\n\n----------------------------------------------------------------------------------------------------------");
List<OtusStudentV2> students = repositoryJpa.findAllWithJoinFetch();
assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals(""))
.allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
.allMatch(s -> s.getAvatar() != null)
.allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
System.out.println("----------------------------------------------------------------------------------------------------------\n\n\n\n");
assertThat(sessionFactory.getStatistics().getPrepareStatementCount()).isEqualTo(EXPECTED_QUERIES_COUNT);
}
}
@@ -0,0 +1,18 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
initialization-mode: always
jpa:
show-sql: false
generate-ddl: false
#generate-ddl: true
hibernate:
ddl-auto: none
#ddl-auto: create-drop
#show-sql: true
logging:
level:
ROOT: ERROR
@@ -0,0 +1,29 @@
insert into avatars(photo_url)
values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'),
('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10');
insert into courses(name)
values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'),
('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11');
insert into otus_students(name, avatar_id)
values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5),
('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10);
insert into emails(email, student_id)
values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4),
('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10);
insert into student_courses(student_id, course_id)
values (1, 1), (1, 2), (1, 3),
(2, 2), (2, 4), (2, 5),
(3, 3), (3, 6), (3, 7),
(4, 4), (4, 8), (4, 9),
(5, 5), (5, 10), (5, 1),
(6, 6), (6, 2), (6, 3),
(7, 7), (7, 4), (7, 5),
(8, 8), (8, 6), (8, 7),
(9, 9), (9, 8), (9, 10),
(10, 10), (10, 1), (10, 2);
@@ -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,2 @@
# spring-jdbc-demo
Пример работы с БД через jdbc
@@ -0,0 +1,69 @@
<?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.1.8.RELEASE</version>
<relativePath/>
</parent>
<groupId>ru.otus.example</groupId>
<artifactId>spring-jdbc-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-jdbc-demo</name>
<description>Spring jdbc demo</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,13 @@
package ru.otus.example.springjdbcdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringJdbcDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJdbcDemoApplication.class, args);
}
}
@@ -0,0 +1,13 @@
package ru.otus.example.springjdbcdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Avatar {
private long id;
private String photoUrl;
}
@@ -0,0 +1,13 @@
package ru.otus.example.springjdbcdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Course {
private long id;
private String name;
}
@@ -0,0 +1,13 @@
package ru.otus.example.springjdbcdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EMail {
private long id;
private String email;
}
@@ -0,0 +1,18 @@
package ru.otus.example.springjdbcdemo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OtusStudent {
private long id;
private String name;
private Avatar avatar;
private List<EMail> emails;
private List<Course> courses;
}
@@ -0,0 +1,9 @@
package ru.otus.example.springjdbcdemo.repositories;
import ru.otus.example.springjdbcdemo.models.Course;
import java.util.List;
public interface CourseRepositoryJdbc {
List<Course> findAllUsed();
}
@@ -0,0 +1,36 @@
package ru.otus.example.springjdbcdemo.repositories;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import ru.otus.example.springjdbcdemo.models.Course;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class CourseRepositoryJdbcImpl implements CourseRepositoryJdbc {
@Autowired
private final JdbcOperations op;
@Override
public List<Course> findAllUsed() {
return op.query("select c.id, c.name " +
"from courses c inner join student_courses sc on c.id = sc.course_id " +
"group by c.id, c.name " +
"order by c.name", new CourseRowMapper());
}
private class CourseRowMapper implements RowMapper<Course> {
@Override
public Course mapRow(ResultSet rs, int i) throws SQLException {
return new Course(rs.getLong(1), rs.getString(2));
}
}
}
@@ -0,0 +1,9 @@
package ru.otus.example.springjdbcdemo.repositories;
import ru.otus.example.springjdbcdemo.models.OtusStudent;
import java.util.List;
public interface OtusStudentRepositoryJdbc {
List<OtusStudent> findAllWithAllInfo();
}
@@ -0,0 +1,49 @@
package ru.otus.example.springjdbcdemo.repositories;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Repository;
import ru.otus.example.springjdbcdemo.models.Course;
import ru.otus.example.springjdbcdemo.models.OtusStudent;
import ru.otus.example.springjdbcdemo.repositories.ext.OtusStudentResultSetExtractor;
import ru.otus.example.springjdbcdemo.repositories.ext.StudentCourseRelation;
import java.util.*;
import java.util.stream.Collectors;
@Repository
@RequiredArgsConstructor
public class OtusStudentRepositoryJdbcImpl implements OtusStudentRepositoryJdbc {
private final CourseRepositoryJdbc courseRepository;
private final JdbcOperations op;
@Override
public List<OtusStudent> findAllWithAllInfo() {
List<Course> courses = courseRepository.findAllUsed();
List<StudentCourseRelation> relations = getAllRelations();
Map<Long, OtusStudent> students = op.query("select os.id, os.name, a.id avatar_id, a.photo_url, e.id email_id, e.email " +
"from (otus_students os left join avatars a on " +
"os.avatar_id = a.id) left join emails e on os.id = e.student_id",
new OtusStudentResultSetExtractor());
mergeStudentsInfo(students, courses, relations);
return new ArrayList<>(Objects.requireNonNull(students).values());
}
private List<StudentCourseRelation> getAllRelations() {
return op.query("select student_id, course_id from student_courses sc order by student_id, course_id",
(rs, i) -> new StudentCourseRelation(rs.getLong(1), rs.getLong(2)));
}
private void mergeStudentsInfo(Map<Long, OtusStudent> students, List<Course> courses, List<StudentCourseRelation> relations) {
Map<Long, Course> coursesMap = courses.stream().collect(Collectors.toMap(Course::getId, c -> c));
relations.forEach(r -> {
if (students.containsKey(r.getStudentId()) && coursesMap.containsKey(r.getCourseId())) {
students.get(r.getStudentId()).getCourses().add(coursesMap.get(r.getCourseId()));
}
});
}
}
@@ -0,0 +1,37 @@
package ru.otus.example.springjdbcdemo.repositories.ext;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import ru.otus.example.springjdbcdemo.models.Avatar;
import ru.otus.example.springjdbcdemo.models.EMail;
import ru.otus.example.springjdbcdemo.models.OtusStudent;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
public class OtusStudentResultSetExtractor implements
ResultSetExtractor<Map<Long, OtusStudent>> {
@Override
public Map<Long, OtusStudent> extractData(ResultSet rs) throws SQLException,
DataAccessException {
Map<Long, OtusStudent> students = new HashMap<>();
while (rs.next()) {
long id = rs.getLong("id");
OtusStudent student = students.get(id);
if (student == null) {
student = new OtusStudent(id, rs.getString("name"),
new Avatar(rs.getLong("avatar_id"), rs.getString("photo_url")),
new ArrayList<>(), new ArrayList<>());
students.put(student.getId(), student);
}
student.getEmails().add(new EMail(rs.getLong("email_id"),
rs.getString("email")));
}
return students;
}
}
@@ -0,0 +1,11 @@
package ru.otus.example.springjdbcdemo.repositories.ext;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class StudentCourseRelation {
private final long studentId;
private final long courseId;
}
@@ -0,0 +1,31 @@
create table avatars(
id bigserial,
photo_url varchar(8000),
primary key (id)
);
create table courses(
id bigserial,
name varchar(255),
primary key (id)
);
create table otus_students(
id bigserial,
name varchar(255),
avatar_id bigint references avatars (id),
primary key (id)
);
create table emails(
id bigserial,
student_id bigint references otus_students(id) on delete cascade,
email varchar(255),
primary key (id)
);
create table student_courses(
student_id bigint references otus_students(id) on delete cascade,
course_id bigint references courses(id),
primary key (student_id, course_id)
);
@@ -0,0 +1,31 @@
package ru.otus.example.springjdbcdemo.repositories;
import lombok.val;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("Репозиторий на основе Jdbc для работы со студентами ")
@JdbcTest
@Import({OtusStudentRepositoryJdbcImpl.class, CourseRepositoryJdbcImpl.class})
class OtusStudentRepositoryJdbcImplTest {
@Autowired
private OtusStudentRepositoryJdbcImpl repositoryJdbc;
@DisplayName("должен загружать список всех студентов с полной информацией о них")
@Test
void shouldReturnCorrectStudentsListWithAllInfo() {
val students = repositoryJdbc.findAllWithAllInfo();
assertThat(students).isNotNull().hasSize(10).allMatch(s -> !s.getName().equals(""))
.allMatch(s -> s.getCourses() != null && s.getCourses().size() > 0)
.allMatch(s -> s.getAvatar() != null)
.allMatch(s -> s.getEmails() != null && s.getEmails().size() > 0);
}
}
@@ -0,0 +1,18 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
initialization-mode: always
jpa:
generate-ddl: false
#generate-ddl: true
hibernate:
ddl-auto: none
#ddl-auto: create-drop
show-sql: true
logging:
level:
ru.otus.example.ormbasicsdemo.repositories.mybatis: TRACE
@@ -0,0 +1,29 @@
insert into avatars(photo_url)
values ('photoUrl_01'), ('photoUrl_02'), ('photoUrl_03'), ('photoUrl_04'), ('photoUrl_05'),
('photoUrl_06'), ('photoUrl_07'), ('photoUrl_08'), ('photoUrl_09'), ('photoUrl_10');
insert into courses(name)
values ('course_name_01'), ('course_name_02'), ('course_name_03'), ('course_name_04'), ('course_name_05'),
('course_name_06'), ('course_name_07'), ('course_name_08'), ('course_name_09'), ('course_name_10'), ('not_used_11');
insert into otus_students(name, avatar_id)
values ('student_01', 1), ('student_02', 2), ('student_03', 3), ('student_04', 4), ('student_05', 5),
('student_06', 6), ('student_07', 7), ('student_08', 8), ('student_09', 9), ('student_10', 10);
insert into emails(email, student_id)
values ('email_01', 1), ('email_02', 1), ('email_03', 2), ('email_04', 2), ('email_05', 3), ('email_06', 4),
('email_07', 5), ('email_08', 6), ('email_09', 7), ('email_10', 8), ('email_11', 9), ('email_12', 10);
insert into student_courses(student_id, course_id)
values (1, 1), (1, 2), (1, 3),
(2, 2), (2, 4), (2, 5),
(3, 3), (3, 6), (3, 7),
(4, 4), (4, 8), (4, 9),
(5, 5), (5, 10), (5, 1),
(6, 6), (6, 2), (6, 3),
(7, 7), (7, 4), (7, 5),
(8, 8), (8, 6), (8, 7),
(9, 9), (9, 8), (9, 10),
(10, 10), (10, 1), (10, 2);