deleted old examples

This commit is contained in:
stvort
2023-09-19 22:34:56 +04:00
parent a7b5e0b773
commit d567a96529
95 changed files with 0 additions and 4557 deletions
-33
View File
@@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### 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/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
-79
View File
@@ -1,79 +0,0 @@
<?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 https://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.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>db-problems-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>db-problems-demo</name>
<description>Demo project for Db usage problems </description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
-1
View File
@@ -1 +0,0 @@
Проект с миграцией для демонстрации того, как выглядит джойн нескольких таблиц при связи "многие-ко-многим"
@@ -1,60 +0,0 @@
package db.migration;
import org.flywaydb.core.api.migration.BaseJavaMigration;
import org.flywaydb.core.api.migration.Context;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("unchecked")
public class R__0002_Add_books extends BaseJavaMigration {
public static final int BOOKS_COUNT = 100_000;
@Override
public void migrate(Context context) throws Exception {
DataSource ds = new SingleConnectionDataSource(context.getConnection(), true);
NamedParameterJdbcOperations jdbc = new NamedParameterJdbcTemplate(ds);
insertBooks(jdbc);
insertBooksRelations(jdbc);
}
private void insertBooks(NamedParameterJdbcOperations jdbc) {
List<Map<String, Object>> params = new ArrayList<>(BOOKS_COUNT);
for (int i = 0; i < BOOKS_COUNT; i++) {
String bookTitle = String.format("Заголовок книги #%d", i);
String bookTextPart = String.format("Это текст книги #%d", i);
String bookText = bookTextPart.repeat( 8000 / bookTextPart.length());
params.add(new MapSqlParameterSource("title", bookTitle)
.addValue("bookText", bookText)
.getValues()
);
}
jdbc.batchUpdate("insert into books (title, book_text) values (:title, :bookText)",
params.toArray(new Map[0]));
}
private void insertBooksRelations(NamedParameterJdbcOperations jdbc) {
List<Map<String, Object>> paramsForAuthors = new ArrayList<>(BOOKS_COUNT);
List<Map<String, Object>> paramsForGenres = new ArrayList<>(BOOKS_COUNT);
for (int i = 1; i <= BOOKS_COUNT; i++) {
for (int j = 1; j <= 3; j++) {
paramsForAuthors.add(new MapSqlParameterSource("bookId", i).addValue("authorId", j).getValues());
paramsForGenres.add(new MapSqlParameterSource("bookId", i).addValue("genreId", j).getValues());
}
}
jdbc.batchUpdate("insert into books_authors (book_id, author_id) values (:bookId, :authorId)",
paramsForAuthors.toArray(new Map[0]));
jdbc.batchUpdate("insert into books_genres (book_id, genre_id) values (:bookId, :genreId)",
paramsForGenres.toArray(new Map[0]));
}
}
@@ -1,24 +0,0 @@
package ru.otus.dbproblemsdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.sql.SQLException;
/*
SELECT * FROM AUTHORS;
SELECT * FROM GENRES;
SELECT * FROM BOOKS_AUTHORS ;
SELECT * FROM BOOKS_GENRES;
SELECT * FROM BOOKS;
SELECT * FROM BOOKS_ALL_INFO ;
*/
@SpringBootApplication
public class DbProblemsDemoApplication {
public static void main(String[] args) throws SQLException {
SpringApplication.run(DbProblemsDemoApplication.class, args);
}
}
@@ -1,13 +0,0 @@
spring:
datasource:
#url: jdbc:h2:mem:testdb
url: jdbc:postgresql://localhost:5432/problems
username: postgres
password: postgres
driver-class-name: org.postgresql.Driver
initialization-mode: never
h2:
console:
enabled: true
flyway:
enabled: true
@@ -1,23 +0,0 @@
--date: 2002-10-10
--author: stvort
create table authors (id bigserial, name varchar(50), primary key (id));
create table genres (id bigserial, name varchar(50), primary key (id));
create table books (id bigserial, title varchar(50), book_text varchar(8000), primary key (id));
create table books_authors (book_id bigint not null references books (id) on delete cascade , author_id bigint not null references authors (id) on delete cascade);
create table books_genres (book_id bigint not null references books (id) on delete cascade , genre_id bigint not null references genres (id) on delete cascade);
create view books_all_info as
select b.id as "ID книги",
b.title as "Заголовок книги",
b.book_text as "Текст книги",
a.id as "ID автора",
a.name as "Имя автора",
g.id as "ID жанра",
g.name as "Название жанра"
from books b left join
books_authors ba on b.id = ba.book_id left join
books_genres bg on b.id = bg.book_id left join
authors a on ba.author_id = a.id left join
genres g on bg.genre_id = g.id;
@@ -1,15 +0,0 @@
insert into authors(id, name)
values (1, 'Админ Супервайзерович Рут'),
(2, 'Валентина Игоревна Априори'),
(3, 'Питуний Дельфинович Автостопов'),
(4, 'Инокентий Купертинович Жужа');
insert into genres(id, name)
values (1, 'Детектив'),
(2, 'Фантастика'),
(3, 'Фэнтези'),
(4, 'Техническая литература');
@@ -1,29 +0,0 @@
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/
@@ -1,77 +0,0 @@
<?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.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus.example</groupId>
<artifactId>hibernate-fetch-mode-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>HibernateFetchModeDemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</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>
</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>
@@ -1,13 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class HibernateFetchModeDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HibernateFetchModeDemoApplication.class, args);
}
}
@@ -1,21 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "knowledge")
public class Knowledge {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column
private String name;
}
@@ -1,31 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import javax.persistence.*;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "mentors")
public class Mentor {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column
private String name;
@LazyCollection(LazyCollectionOption.FALSE)
@ManyToMany(targetEntity = Knowledge.class, cascade = CascadeType.ALL)
@JoinTable(name = "mentors_experience",
joinColumns = @JoinColumn(name = "mentor_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "knowledge_id", referencedColumnName = "id"))
private List<Knowledge> experience;
}
@@ -1,33 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import javax.persistence.*;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "students")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column
private String name;
//@BatchSize(size = 5)
@LazyCollection(LazyCollectionOption.FALSE)
@ManyToMany(targetEntity = Knowledge.class, cascade = CascadeType.ALL)
@JoinTable(name = "students_experience",
joinColumns = @JoinColumn(name = "student_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "knowledge_id", referencedColumnName = "id"))
private List<Knowledge> experience;
}
@@ -1,34 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.models;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import javax.persistence.*;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "teachers")
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@Column
private String name;
@LazyCollection(LazyCollectionOption.FALSE)
@Fetch(FetchMode.SUBSELECT)
@ManyToMany(targetEntity = Knowledge.class, cascade = CascadeType.ALL)
@JoinTable(name = "teachers_experience",
joinColumns = @JoinColumn(name = "teacher_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "knowledge_id", referencedColumnName = "id"))
private List<Knowledge> experience;
}
@@ -1,9 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import ru.otus.example.hibernate_fetch_mode_demo.models.Mentor;
import java.util.List;
public interface MentorRepository {
List<Mentor> findAll();
}
@@ -1,20 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import org.springframework.stereotype.Repository;
import ru.otus.example.hibernate_fetch_mode_demo.models.Mentor;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class MentorRepositoryImpl implements MentorRepository {
@PersistenceContext
private EntityManager em;
@Override
public List<Mentor> findAll() {
return em.createQuery("select m from Mentor m join fetch m.experience", Mentor.class).getResultList();
}
}
@@ -1,9 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import ru.otus.example.hibernate_fetch_mode_demo.models.Student;
import java.util.List;
public interface StudentRepository {
List<Student> findAll();
}
@@ -1,20 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import org.springframework.stereotype.Repository;
import ru.otus.example.hibernate_fetch_mode_demo.models.Student;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class StudentRepositoryImpl implements StudentRepository {
@PersistenceContext
private EntityManager em;
@Override
public List<Student> findAll() {
return em.createQuery("select s from Student s", Student.class).getResultList();
}
}
@@ -1,9 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import ru.otus.example.hibernate_fetch_mode_demo.models.Teacher;
import java.util.List;
public interface TeacherRepository {
List<Teacher> findAll();
}
@@ -1,20 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.repositories;
import org.springframework.stereotype.Repository;
import ru.otus.example.hibernate_fetch_mode_demo.models.Teacher;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
@Repository
public class TeacherRepositoryImpl implements TeacherRepository {
@PersistenceContext
private EntityManager em;
@Override
public List<Teacher> findAll() {
return em.createQuery("select t from Teacher t", Teacher.class).getResultList();
}
}
@@ -1,48 +0,0 @@
package ru.otus.example.hibernate_fetch_mode_demo.shell;
import org.springframework.shell.standard.ShellComponent;
import org.springframework.shell.standard.ShellMethod;
import ru.otus.example.hibernate_fetch_mode_demo.models.Mentor;
import ru.otus.example.hibernate_fetch_mode_demo.models.Student;
import ru.otus.example.hibernate_fetch_mode_demo.models.Teacher;
import ru.otus.example.hibernate_fetch_mode_demo.repositories.MentorRepository;
import ru.otus.example.hibernate_fetch_mode_demo.repositories.StudentRepository;
import ru.otus.example.hibernate_fetch_mode_demo.repositories.TeacherRepository;
import java.util.stream.Collectors;
@ShellComponent
public class ShellCommands {
private static final String OFFSET = "\n\n----------------------------\n\n";
private final StudentRepository studentRepository;
private final MentorRepository mentorRepository;
private final TeacherRepository teacherRepository;
public ShellCommands(StudentRepository studentRepository, MentorRepository mentorRepository, TeacherRepository teacherRepository) {
this.studentRepository = studentRepository;
this.mentorRepository = mentorRepository;
this.teacherRepository = teacherRepository;
}
@ShellMethod(value = "Show all students", key = "find-all-students")
public String findAllStudents(){
System.out.println();
return OFFSET + studentRepository.findAll().stream().map(Student::toString).collect(Collectors.joining("\n"));
}
@ShellMethod(value = "Show all mentors", key = "find-all-mentors")
public String findAllMentors(){
System.out.println();
return OFFSET + mentorRepository.findAll().stream().map(Mentor::toString).collect(Collectors.joining("\n"));
}
@ShellMethod(value = "Show all teachers", key = "find-all-teachers")
public String findAllTeachers(){
System.out.println();
return OFFSET + teacherRepository.findAll().stream().map(Teacher::toString).collect(Collectors.joining("\n"));
}
}
@@ -1,15 +0,0 @@
spring:
datasource:
url: jdbc:h2:mem:testdb
initialization-mode: always
jpa:
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
properties:
hibernate:
#format_sql: true
@@ -1,9 +0,0 @@
INSERT INTO knowledge (name) VALUES ('Spring Core'), ('Spring Data'), ('Spring MVC'), ('Spring Batch'), ('Spring Integration'), ('Spring WebFlux');
INSERT INTO students (name) VALUES ('Student #01'), ('Student #02'), ('Student #03'), ('Student #04'), ('Student #05'), ('Student #06'), ('Student #07'), ('Student #08'), ('Student #09');
INSERT INTO mentors (name) VALUES ('Mentor #01'), ('Mentor #02'), ('Mentor #03'), ('Mentor #04'), ('Mentor #05'), ('Mentor #06'), ('Mentor #07'), ('Mentor #08'), ('Mentor #09');
INSERT INTO teachers (name) VALUES ('Teacher #01'), ('Teacher #02'), ('Teacher #03'), ('Teacher #04'), ('Teacher #05'), ('Teacher #06'), ('Teacher #07'), ('Teacher #08'), ('Teacher #09');
INSERT INTO students_experience (student_id, knowledge_id) VALUES (1, 1), (1, 2), (2, 2), (2, 3), (2, 4), (2, 5);
INSERT INTO mentors_experience (mentor_id, knowledge_id) VALUES (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6);
INSERT INTO teachers_experience (teacher_id, knowledge_id) VALUES(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (2, 6);
@@ -1,48 +0,0 @@
DROP TABLE IF EXISTS students_experience;
DROP TABLE IF EXISTS mentors_experience;
DROP TABLE IF EXISTS teachers_experience;
DROP TABLE IF EXISTS students;
DROP TABLE IF EXISTS mentors;
DROP TABLE IF EXISTS teachers;
DROP TABLE IF EXISTS knowledge;
CREATE TABLE knowledge (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE students (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE mentors (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE teachers (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE students_experience (
student_id BIGINT,
knowledge_id BIGINT,
FOREIGN KEY(student_id) REFERENCES students(id) ON DELETE CASCADE,
FOREIGN KEY(knowledge_id) REFERENCES knowledge(id) ON DELETE CASCADE
);
CREATE TABLE mentors_experience (
mentor_id BIGINT,
knowledge_id BIGINT,
FOREIGN KEY(mentor_id) REFERENCES mentors(id) ON DELETE CASCADE,
FOREIGN KEY(knowledge_id) REFERENCES knowledge(id) ON DELETE CASCADE
);
CREATE TABLE teachers_experience (
teacher_id BIGINT,
knowledge_id BIGINT,
FOREIGN KEY(teacher_id) REFERENCES teachers(id) ON DELETE CASCADE,
FOREIGN KEY(knowledge_id) REFERENCES knowledge(id) ON DELETE CASCADE
);
@@ -1,21 +0,0 @@
version: "3"
services:
keycloak-container:
image: quay.io/keycloak/keycloak:11.0.0
command:
-Djboss.socket.binding.port-offset=2
restart: always
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
KEYCLOAK_IMPORT: /tmp/realm.json
ports:
- 8082:8082
volumes:
- ./realm.json:/tmp/realm.json
File diff suppressed because it is too large Load Diff
@@ -1,81 +0,0 @@
<?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 https://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.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>resource-app</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>resource-app</name>
<description>KeyCloack resource app example</description>
<properties>
<java.version>11</java.version>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.keycloak.bom</groupId>
<artifactId>keycloak-adapter-bom</artifactId>
<version>11.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-spring-boot-starter</artifactId>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,16 +0,0 @@
package ru.otus.resourceapp;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//https://www.baeldung.com/spring-boot-keycloak
//https://www.baeldung.com/keycloak-custom-user-attributes
//https://github.com/keycloak/keycloak-quickstarts/tree/latest/app-authz-springboot
@SpringBootApplication
public class ResourceAppApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceAppApplication.class, args);
}
}
@@ -1,51 +0,0 @@
package ru.otus.resourceapp.config;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
var keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
@Bean
public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/secret*").hasRole("user")
.anyRequest().permitAll();
}
}
@@ -1,28 +0,0 @@
package ru.otus.resourceapp.controllers;
import org.keycloak.KeycloakPrincipal;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RestController
public class CommonController {
@GetMapping(path = "/")
public String commonData() {
return "Абсолютно свободные данные";
}
@SuppressWarnings("rawtypes")
@GetMapping(path = "/secret")
public String secretData(Principal principal) {
var authenticationToken = (KeycloakAuthenticationToken) principal;
var kp = (KeycloakPrincipal) authenticationToken.getPrincipal();
var token = kp.getKeycloakSecurityContext().getToken();
return "Жутко секретные данные для пользователя: " + token.getPreferredUsername() +
", \n остальные данные: " + token.getOtherClaims();
}
}
@@ -1,5 +0,0 @@
keycloak:
auth-server-url: http://localhost:8082/auth
realm: KCExample
resource: resource-app
public-client: true
-29
View File
@@ -1,29 +0,0 @@
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/
-7
View File
@@ -1,7 +0,0 @@
## Пример работы с MongoDB
В примере демонстрируется:
* *два подхода к хранению вложенных сущностей*
* *инициализация базы данными с помощью инструмента миграций Mongock*
* *использование AbstractMongoEventListener для выполнения каскадных операций*
* *работа с массивами с помощью агрегаций и Criteria api*
-121
View File
@@ -1,121 +0,0 @@
<?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.2.6.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>3.2.8</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>
<dependency>
<groupId>com.github.cloudyrock.mongock</groupId>
<artifactId>mongock-spring</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>
@@ -1,15 +0,0 @@
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);
}
}
@@ -1,41 +0,0 @@
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){
template.save(new Student("Student #1", springDataKnowledge, mongockKnowledge));
}
@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);
}
}
@@ -1,19 +0,0 @@
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)
.build();
}
}
@@ -1,14 +0,0 @@
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;
}
@@ -1,24 +0,0 @@
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);
}
}
@@ -1,27 +0,0 @@
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);
}
}
}
@@ -1,21 +0,0 @@
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;
}
}
@@ -1,29 +0,0 @@
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);
}
}
@@ -1,28 +0,0 @@
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);
}
}
@@ -1,7 +0,0 @@
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> {
}
@@ -1,8 +0,0 @@
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 {
}
@@ -1,12 +0,0 @@
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);
}
@@ -1,69 +0,0 @@
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);
}
}
@@ -1,7 +0,0 @@
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 {
}
@@ -1,9 +0,0 @@
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);
}
@@ -1,28 +0,0 @@
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();
}
}
@@ -1,8 +0,0 @@
package ru.otus.example.mongodbdemo.utils;
import org.bson.Document;
public interface RawResultPrinter {
@SuppressWarnings("unchecked")
void prettyPrintRawResult(Document document);
}
@@ -1,18 +0,0 @@
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);
}
}
@@ -1,9 +0,0 @@
spring:
data:
mongodb:
uri: mongodb://localhost
port: 27017
database: awesomeMongo
logging:
level:
root: ERROR
@@ -1,14 +0,0 @@
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 {
}
@@ -1,29 +0,0 @@
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]));
}
}
@@ -1,58 +0,0 @@
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);
}
}
@@ -1,27 +0,0 @@
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);
}
}
@@ -1,49 +0,0 @@
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);
}
}
@@ -1,41 +0,0 @@
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());
}
}
@@ -1,11 +0,0 @@
spring:
data:
mongodb:
port: 0
database: test
#uri: mongodb://localhost
#port: 27017
#database: awesomeMongo
logging:
level:
root: ERROR
-4
View File
@@ -1,4 +0,0 @@
.idea/
*.iml
target/
-6
View File
@@ -1,6 +0,0 @@
## Пример аутентификации/авторизации с помощью OAuth2 сервера и JWT
* *В примере есть проект authorization-server, призванный выдавать jwt токены в ответ на корректный POST запрос по http://localhost:8090/oauth/token *
* *В теле такого запроса нужно передать client_id, grant_type, имя и пароль пользователя пользователя пытающегося выполнить вход*
* *Далее, полученный токен XXX нужно прикладывать к каждому запросу к приложению из проекта resource-server в виде загловка "Authorization Bearer XXX"*
* *Для демонстрации работы примера нужно запустить приложение authorization-server и следом изучить работу E2E теста в проекте resource-server*
@@ -1,31 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### 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/
@@ -1,92 +0,0 @@
<?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 https://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.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>authorization-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>authorization-server</name>
<description>Authorization server demo</description>
<properties>
<java.version>13</java.version>
<maven.compiler.source>13</maven.compiler.source>
<maven.compiler.target>13</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,15 +0,0 @@
package ru.otus.authorizationserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@EnableConfigurationProperties
@SpringBootApplication
public class AuthorizationServerApplication {
public static void main(String[] args) {
SpringApplication.run(AuthorizationServerApplication.class, args);
}
}
@@ -1,66 +0,0 @@
package ru.otus.authorizationserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import ru.otus.authorizationserver.services.CustomTokenEnhancer;
import javax.sql.DataSource;
import java.util.List;
@SuppressWarnings("deprecation")
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 60 * 60 * 3;
private static final int REFRESH_TOKEN_VALIDITY_SECONDS = -1;
@Autowired
private OAuthProps oAuthProps;
@Autowired
private AuthenticationManager authManager;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(List.of(new CustomTokenEnhancer(), jwtAccessTokenConverter));
endpoints.authenticationManager(authManager)
.tokenEnhancer(tokenEnhancerChain)
.tokenStore(tokenStore)
.reuseRefreshTokens(false)
;
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient(oAuthProps.getClientId())
.secret(oAuthProps.getClientSecret())
.authorizedGrantTypes("password", "authorization_code", "refresh_token")
.scopes("read", "write")
.accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
.refreshTokenValiditySeconds(REFRESH_TOKEN_VALIDITY_SECONDS);
}
}
@@ -1,14 +0,0 @@
package ru.otus.authorizationserver.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "app.oauth2")
public class OAuthProps {
private String clientId;
private String clientSecret;
private String signingKey;
}
@@ -1,39 +0,0 @@
package ru.otus.authorizationserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
@@ -1,31 +0,0 @@
package ru.otus.authorizationserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@SuppressWarnings("deprecation")
@Configuration
public class TokenStoreConfig {
@Autowired
private OAuthProps oAuthProps;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
var converter = new JwtAccessTokenConverter();
converter.setSigningKey(oAuthProps.getSigningKey());
return converter;
}
}
@@ -1,33 +0,0 @@
package ru.otus.authorizationserver.model;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
public class CustomUser extends User {
@Getter
@Setter
private String firstName;
@Getter
@Setter
private String fatherName;
public CustomUser(String firstName, String fatherName,
String username, String password,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.firstName = firstName;
this.fatherName = fatherName;
}
public CustomUser(CustomUser user) {
super(user.getUsername(), user.getPassword(), user.getAuthorities());
this.firstName = user.getFirstName();
this.fatherName = user.getFatherName();
}
}
@@ -1,26 +0,0 @@
package ru.otus.authorizationserver.services;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import ru.otus.authorizationserver.model.CustomUser;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("deprecation")
public class CustomTokenEnhancer implements TokenEnhancer {
public static final String KEY_FIRST_NAME = "firstName";
public static final String KEY_FATHER_NAME = "fatherName";
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
CustomUser user = (CustomUser) authentication.getPrincipal();
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(Map.of(
KEY_FIRST_NAME, user.getFirstName(),
KEY_FATHER_NAME, user.getFatherName()
));
return accessToken;
}
}
@@ -1,39 +0,0 @@
package ru.otus.authorizationserver.services;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import ru.otus.authorizationserver.model.CustomUser;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class InMemoryUserDetailsService implements UserDetailsService {
private final Map<String, CustomUser> userMap;
public InMemoryUserDetailsService(PasswordEncoder passwordEncoder) {
userMap = Map.of(
"user1", new CustomUser("Василий", "Григорьевич",
"user1",
passwordEncoder.encode("user1"),
List.of(new SimpleGrantedAuthority("ADMIN"))),
"user2", new CustomUser("Ираклий", "Спиридонович",
"user2",
passwordEncoder.encode("user2"),
List.of(new SimpleGrantedAuthority("USER")))
);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return Optional.ofNullable(userMap.get(username)).map(CustomUser::new)
.orElseThrow(() -> new UsernameNotFoundException(String.format("User %s not found", username)));
}
}
@@ -1,8 +0,0 @@
server:
port: 8090
app:
oauth2:
client-id: "any_id"
client-secret: "any_secret"
signing-key: 987654321
-17
View File
@@ -1,17 +0,0 @@
<?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>
<groupId>ru.otus</groupId>
<artifactId>oauth2-example</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<modules>
<module>authorization-server</module>
<module>resource-server</module>
</modules>
</project>
@@ -1,31 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**
!**/src/test/**
### 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/
@@ -1,92 +0,0 @@
<?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 https://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.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>resource-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>resource-server</name>
<description>Resource server demo</description>
<properties>
<java.version>13</java.version>
<maven.compiler.source>13</maven.compiler.source>
<maven.compiler.target>13</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.1.RELEASE</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@@ -1,15 +0,0 @@
package ru.otus.resourceserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@EnableConfigurationProperties
@SpringBootApplication
public class ResourceServerApplication {
public static void main(String[] args) {
SpringApplication.run(ResourceServerApplication.class, args);
}
}
@@ -1,14 +0,0 @@
package ru.otus.resourceserver.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "app.oauth2")
public class OAuthProps {
private String clientId;
private String clientSecret;
private String signingKey;
}
@@ -1,51 +0,0 @@
package ru.otus.resourceserver.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
@SuppressWarnings("deprecation")
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private OAuthProps oAuthProps;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
var converter = new JwtAccessTokenConverter();
converter.setSigningKey(oAuthProps.getSigningKey());
return converter;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore())
;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous().disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
@@ -1,41 +0,0 @@
package ru.otus.resourceserver.controllers;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;
import ru.otus.resourceserver.models.CurrentUserRequestResult;
import java.util.Map;
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
@RestController
public class ApiController {
public static final String KEY_FIRST_NAME = "firstName";
public static final String KEY_FATHER_NAME = "fatherName";
private final TokenStore tokenStore;
@GetMapping("api/current-user")
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasAuthority('ADMIN')")
public CurrentUserRequestResult getCurrentUser(Authentication authentication) {
Map<String, Object> info = getAdditionalInfo(authentication);
return new CurrentUserRequestResult((String) authentication.getPrincipal(),
(String) info.get(KEY_FIRST_NAME),
(String) info.get(KEY_FATHER_NAME));
}
private Map<String, Object> getAdditionalInfo(Authentication authentication) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
OAuth2AccessToken accessToken = tokenStore.readAccessToken(details.getTokenValue());
return accessToken.getAdditionalInformation();
}
}
@@ -1,12 +0,0 @@
package ru.otus.resourceserver.models;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CurrentUserRequestResult {
private String userName;
private String firstName;
private String fatherName;
}
@@ -1,8 +0,0 @@
server:
port: 8080
app:
oauth2:
client-id: "any_id"
client-secret: "any_secret"
signing-key: 987654321
@@ -1,69 +0,0 @@
package ru.otus.resourceserver;
import lombok.Data;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import ru.otus.resourceserver.config.OAuthProps;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class ResourceServerApplicationTests {
@Autowired
private TestRestTemplate restTemplate;
@Autowired
private OAuthProps oAuthProps;
@Data
static class TokenResponse {
private String access_token;
private String token_type;
private String refresh_token;
private String expires_in;
private String scope;
}
@Test
void shouldSuccessfullyAccessTheEndpointAfterLoginOnAuthorizationServer() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.setBasicAuth(oAuthProps.getClientId(), oAuthProps.getClientSecret());
ResponseEntity<TokenResponse> tokenResponse = restTemplate.postForEntity(
"http://localhost:8090/oauth/token?client_id=" + oAuthProps.getClientId() +
"&grant_type=password&username=user1&password=user1",
new HttpEntity<>(headers),
TokenResponse.class);
/*
System.out.println(tokenResponse.getStatusCode());
System.out.println(tokenResponse.getHeaders());
System.out.println(tokenResponse.getBody());
*/
assertThat(tokenResponse.getBody()).isNotNull();
headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(tokenResponse.getBody().getAccess_token());
ResponseEntity<String> currentUserResponse = restTemplate.exchange(
"http://localhost:8080/api/current-user",
HttpMethod.GET,
new HttpEntity<>(headers),
String.class);
System.out.println(currentUserResponse.getStatusCode());
System.out.println(currentUserResponse.getHeaders());
System.out.println(currentUserResponse.getBody());
assertThat(currentUserResponse.getBody())
.isNotNull().isEqualTo("{\"userName\":\"user1\",\"firstName\":\"Василий\",\"fatherName\":\"Григорьевич\"}");
}
}
@@ -1,74 +0,0 @@
<?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 https://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.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>ru.otus</groupId>
<artifactId>readonly-transaction-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>readonly-transaction-demo</name>
<description>Readonly transaction 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>org.postgresql</groupId>
<artifactId>postgresql</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,13 +0,0 @@
package ru.otus.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ReadonlyTransactionDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ReadonlyTransactionDemoApplication.class, args);
}
}
@@ -1,22 +0,0 @@
package ru.otus.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Email {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String address;
}
@@ -1,23 +0,0 @@
package ru.otus.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@OneToOne(cascade = CascadeType.ALL)
private Email email;
}
@@ -1,13 +0,0 @@
package ru.otus.demo.repository;
import ru.otus.demo.model.Person;
import java.util.List;
public interface PersonRepository {
Person save(Person person);
List<Person> findAll();
Person findById(long id);
}
@@ -1,48 +0,0 @@
package ru.otus.demo.repository;
import lombok.RequiredArgsConstructor;
import org.hibernate.Session;
import org.springframework.stereotype.Repository;
import ru.otus.demo.model.Person;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class PersonRepositoryJpa implements PersonRepository {
@PersistenceContext
private final EntityManager em;
@Override
public Person save(Person person) {
if (person.getId() == 0) {
Person savedPerson = new Person(person.getId(), person.getName(), person.getEmail());
em.persist(savedPerson);
return savedPerson;
}
return em.merge(person);
}
@Override
public List<Person> findAll() {
TypedQuery<Person> query = em.createQuery("select p from Person p", Person.class);
return query.getResultList();
}
@Override
public Person findById(long id) {
System.out.println("FlushMode: " + em.unwrap(Session.class).getHibernateFlushMode());
return em.find(Person.class, id);
/*
TypedQuery<Person> query = em.createQuery("select p from Person p where p.id = :id", Person.class);
query.setParameter("id", id);
return query.getSingleResult();
*/
}
}
@@ -1,41 +0,0 @@
package ru.otus.demo.services;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.otus.demo.model.Person;
import ru.otus.demo.repository.PersonRepository;
@Service
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public Person findById(long id) {
return personRepository.findById(id);
}
@Transactional
public void save(Person person){
personRepository.save(person);
}
public void updateWithoutTran(long id, String name) {
Person person = personRepository.findById(id);
person.setName(name);
}
@Transactional
public void updateWithNormalTran(long id, String name) {
Person person = personRepository.findById(id);
person.setName(name);
}
@Transactional(readOnly = true)
public void updateWithReadonlyTran(long id, String name) {
Person person = personRepository.findById(id);
person.setName(name);
}
}
@@ -1,23 +0,0 @@
spring:
datasource:
#url: jdbc:postgresql://localhost:5432/persons
#username: postgres
#password: postgres
url: jdbc:h2:mem:testdb
initialization-mode: always
jpa:
generate-ddl: false
hibernate:
ddl-auto: none
show-sql: true
properties:
hibernate:
#format_sql: true
logging:
level:
org.hibernate.engine.transaction.internal: DEBUG
@@ -1,17 +0,0 @@
drop table if exists person;
drop table if exists email;
create table email (
id bigserial,
address varchar(200),
primary key(id)
);
create table person (
id bigserial,
email_id bigint references email(id),
name varchar(200),
primary key(id)
);
@@ -1,80 +0,0 @@
package ru.otus.demo.services;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import ru.otus.demo.model.Email;
import ru.otus.demo.model.Person;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
class PersonServiceTest {
@Autowired
private PersonService personService;
@BeforeEach
void setUp() {
personService.save(new Person(0, "Igor", new Email(0, "noname@nomail.ru")));
}
/*
// Изменение имени пёрсона внутри updateWithNormalTran ПОПАДАЮТ в БД после закрытия сессии/транзакции
// Работаем в обычной транзакции (в логах есть ее начало и коммит, а так же update)
// FlushMode: AUTO (есть в логах)
2021-07-24 19:21:16.310 DEBUG 8636 --- [ main] o.h.e.t.internal.TransactionImpl : begin
Hibernate: select person0_.id as id1_1_, person0_.email_id as email_id3_1_, person0_.name as name2_1_ from person person0_ where person0_.id=?
Hibernate: select email0_.id as id1_0_0_, email0_.address as address2_0_0_ from email email0_ where email0_.id=?
2021-07-24 19:21:16.390 DEBUG 8636 --- [ main] o.h.e.t.internal.TransactionImpl : committing
Hibernate: update person set email_id=?, name=? where id=?
*/
@Test
void updateWithNormalTran() {
personService.updateWithNormalTran(1, "Vasya");
Person actualPerson = personService.findById(1);
assertThat(actualPerson).extracting(Person::getName).isEqualTo("Vasya");
}
/*
// Работаем в readonly транзакции (в логах есть ее начало и коммит)
// Изменение имени пёрсона внутри updateWithReadonlyTran НЕ попадают в БД после закрытия сессии/транзакции
// FlushMode: MANUAL (есть в логах), поэтому изменения и не попадают
2021-07-24 19:15:22.889 DEBUG 11024 --- [ main] o.h.e.t.internal.TransactionImpl : begin
Hibernate: select person0_.id as id1_1_, person0_.email_id as email_id3_1_, person0_.name as name2_1_ from person person0_ where person0_.id=?
Hibernate: select email0_.id as id1_0_0_, email0_.address as address2_0_0_ from email email0_ where email0_.id=?
2021-07-24 19:15:22.967 DEBUG 11024 --- [ main] o.h.e.t.internal.TransactionImpl : committing
*/
@Test
void updateWithReadonlyTran() {
personService.updateWithReadonlyTran(1, "Vasya");
Person actualPerson = personService.findById(1);
assertThat(actualPerson).extracting(Person::getName).isEqualTo("Igor");
}
/*
// Работаем без транзакции (в логах нет ее начала и коммита)
// Изменение имени пёрсона внутри updateWithoutTran НЕ попадают в БД после закрытия сессии/транзакции
// FlushMode: AUTO (есть в логах)
Hibernate: select person0_.id as id1_1_, person0_.email_id as email_id3_1_, person0_.name as name2_1_ from person person0_ where person0_.id=?
Hibernate: select email0_.id as id1_0_0_, email0_.address as address2_0_0_ from email email0_ where email0_.id=?
*/
@Test
void updateWithoutTran() {
personService.updateWithoutTran(1, "Vasya");
Person actualPerson = personService.findById(1);
assertThat(actualPerson).extracting(Person::getName).isEqualTo("Igor");
}
}
@@ -1,83 +0,0 @@
package ru.otus.demo.services;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import ru.otus.demo.model.Email;
import ru.otus.demo.model.Person;
import javax.persistence.EntityManager;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
class TransactionsTest {
private static final Person expectedPerson = new Person(1L, "Igor",
new Email(1L, "noname@nomail.ru"));
@Autowired
private SessionFactory sf;
@Autowired
private Session s;
@Autowired
private TransactionTemplate tm;
@BeforeEach
void setUp() {
tm.setReadOnly(false);
tm.execute(status -> {
s.persist(new Person(0, expectedPerson.getName(),
new Email(0, expectedPerson.getEmail().getAddress())));
return null;
});
sf.getStatistics().setStatisticsEnabled(true);
sf.getStatistics().clear();
}
@Test
void findWithNormalTran() {
tm.setReadOnly(false);
Person actualPerson = tm.execute(status -> {
assertThat(s.getHibernateFlushMode()).isEqualTo(FlushMode.AUTO);
return s.find(Person.class, 1L);
});
assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
assertThat(sf.getStatistics().getTransactionCount()).isEqualTo(1);
}
@Test
void findWithReadOnlyTran() {
tm.setReadOnly(true);
Person actualPerson = tm.execute(status -> {
assertThat(s.getHibernateFlushMode()).isEqualTo(FlushMode.MANUAL);
return s.find(Person.class, 1L);
});
assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
assertThat(sf.getStatistics().getTransactionCount()).isEqualTo(1);
}
@Test
void findWithoutTran() {
assertThat(s.getHibernateFlushMode()).isEqualTo(FlushMode.AUTO);
Person actualPerson = s.find(Person.class, 1L);
assertThat(actualPerson).usingRecursiveComparison().isEqualTo(expectedPerson);
assertThat(sf.getStatistics().getTransactionCount()).isEqualTo(0);
}
}