mirror of
https://github.com/OtusTeam/Spring.git
synced 2026-06-20 11:14:34 +00:00
Merge branch 'master' of github.com:OtusTeam/Spring
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
target/
|
||||
|
||||
/node_modules
|
||||
/output
|
||||
@@ -0,0 +1,33 @@
|
||||
<?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>spring-18</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.4.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>27.1-jre</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,35 @@
|
||||
package ru.otus;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public class CreateExamples {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Observable<String> obs = justExample();
|
||||
obs.forEach(System.out::println);
|
||||
obs.forEach(System.out::println);
|
||||
}
|
||||
|
||||
public static Observable<String> justExample() {
|
||||
return Observable.just("one", "two", "three");
|
||||
}
|
||||
|
||||
public static Observable<String> createExample() {
|
||||
return Observable.create(emitter -> {
|
||||
if (emitter.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
emitter.onNext("one");
|
||||
emitter.onNext("two");//!
|
||||
emitter.onNext("three");
|
||||
if (!emitter.isDisposed()) {
|
||||
emitter.onComplete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Observable<String> deferExample() {
|
||||
return Observable.defer(() -> Observable.just("one", "two", "three"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package ru.otus;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class LiveLikeExample {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
static Observable<String> getName() {
|
||||
return Observable.just("Jake");
|
||||
}
|
||||
|
||||
static Observable<String> getSurname() {
|
||||
return Observable.just("Foo");
|
||||
}
|
||||
|
||||
static Observable<String> save(String fullName) {
|
||||
System.out.println(fullName + " saved!");
|
||||
return Observable.just("OK!");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package ru.otus;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableTransformer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.PublishSubject;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
public class OperatorsExample {
|
||||
public static void main(String[] args) throws Exception {
|
||||
simpleExample();
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
public static void simpleExample() throws Exception {
|
||||
List<Person> persons = ImmutableList.of(
|
||||
new Person("John", "Dow", "male", LocalDate.of(1992, 3, 12)),
|
||||
new Person("Jane", "Dow", "female", LocalDate.of(2001, 6, 23)),
|
||||
new Person("Howard", "Lovecraft", "male", LocalDate.of(1890, 8, 20)),
|
||||
new Person("Joanne", "Rowling", "female", LocalDate.of(1965, 6, 30)));
|
||||
|
||||
Observable.fromIterable(persons)
|
||||
.filter(
|
||||
person -> person.getBirth().isAfter(LocalDate.of(1990, 1, 1))
|
||||
)
|
||||
.map(p -> p.getFirstName() + " " + p.getLastName())
|
||||
.toList()
|
||||
.subscribe(System.out::println);
|
||||
}
|
||||
|
||||
public static void publisherExample() throws Exception {
|
||||
final Observable<String> ob = magicPublisher();
|
||||
System.out.println("First subscribed");
|
||||
ob.subscribe(System.out::println);
|
||||
Thread.sleep(5000);
|
||||
System.out.println("Second subscribed");
|
||||
ob.subscribe(System.out::println);
|
||||
}
|
||||
|
||||
public static Observable<String> magicPublisher() {
|
||||
Random r = new Random(1);
|
||||
AtomicInteger i = new AtomicInteger();
|
||||
final Observable<String> obs = Observable.<String>generate(emitter ->
|
||||
emitter.onNext("" + i.incrementAndGet()))
|
||||
.concatMap(s -> Observable.just(s).delay(r.nextInt(1000), TimeUnit.MILLISECONDS))
|
||||
.subscribeOn(Schedulers.newThread());
|
||||
PublishSubject<String> subject = PublishSubject.create();
|
||||
|
||||
// BehaviorSubject<String> subject = BehaviorSubject.create();
|
||||
|
||||
// AsyncSubject<String> subject = AsyncSubject.create();
|
||||
// CompletableFuture.runAsync(() -> {
|
||||
// try {
|
||||
// Thread.sleep(7000);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
// subject.onComplete();
|
||||
// });
|
||||
|
||||
// ReplaySubject<String> subject = ReplaySubject.create();
|
||||
obs.subscribe(subject);
|
||||
return subject;
|
||||
}
|
||||
|
||||
//composeExmaple
|
||||
private static ObservableTransformer<String, String> filterAndUpperCase() {
|
||||
return upstream -> upstream
|
||||
.filter(s -> s.length() >= 4)
|
||||
.map(String::toUpperCase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package ru.otus;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Person {
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String gender;
|
||||
private LocalDate birth;
|
||||
|
||||
public Person(String firstName, String lastName, String gender, LocalDate birth) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
this.gender = gender;
|
||||
this.birth = birth;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender;
|
||||
}
|
||||
|
||||
public void setGender(String gender) {
|
||||
this.gender = gender;
|
||||
}
|
||||
|
||||
public LocalDate getBirth() {
|
||||
return birth;
|
||||
}
|
||||
|
||||
public void setBirth(LocalDate birth) {
|
||||
this.birth = birth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Person person = (Person) o;
|
||||
return Objects.equals(firstName, person.firstName) &&
|
||||
Objects.equals(lastName, person.lastName) &&
|
||||
Objects.equals(gender, person.gender) &&
|
||||
Objects.equals(birth, person.birth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(firstName, lastName, gender, birth);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package ru.otus.comparison;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AsyncComparison {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
final long timeStarted = System.currentTimeMillis();
|
||||
final Observable<String> obs = controller();
|
||||
obs.subscribe(System.out::println);
|
||||
System.out.println("Wait time " + (System.currentTimeMillis() - timeStarted));
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
static Observable<String> controller() {
|
||||
return service();
|
||||
}
|
||||
|
||||
static Observable<String> service() {
|
||||
return repository();
|
||||
}
|
||||
|
||||
static Observable<String> repository() {
|
||||
return database();
|
||||
}
|
||||
|
||||
static Observable<String> database() {
|
||||
return Observable.defer(() -> {
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Don't do this");
|
||||
}
|
||||
return Observable.just("Hello world");
|
||||
}).subscribeOn(Schedulers.newThread());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package ru.otus.comparison;
|
||||
|
||||
public class SyncComparison {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final long timeStarted = System.currentTimeMillis();
|
||||
System.out.println(controller());
|
||||
System.out.println(System.currentTimeMillis() - timeStarted);
|
||||
}
|
||||
|
||||
static String controller() {
|
||||
return service();
|
||||
}
|
||||
|
||||
static String service() {
|
||||
return repository();
|
||||
}
|
||||
|
||||
static String repository() {
|
||||
return database();
|
||||
}
|
||||
|
||||
static String database() {
|
||||
try {
|
||||
Thread.sleep(4000);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Don't do this");
|
||||
}
|
||||
return "Hello world";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
@@ -0,0 +1,18 @@
|
||||
<?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>spring-19</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-19-web-flux</module>
|
||||
<module>spring-19-reactor</module>
|
||||
<module>spring-19-reactive-spring-data</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?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>spring-19-reactive-spring-data</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>13</maven.compiler.target>
|
||||
<maven.compiler.source>13</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mongodb</groupId>
|
||||
<artifactId>mongodb-driver-reactivestreams</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import ru.otus.spring.repostory.AccountRepository;
|
||||
import ru.otus.spring.repostory.PersonRepository;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
ApplicationContext context = SpringApplication.run(Main.class);
|
||||
|
||||
PersonRepository repository = context.getBean(PersonRepository.class);
|
||||
AccountRepository accountRepository = context.getBean(AccountRepository.class);
|
||||
|
||||
Thread.sleep(20000);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
public class Account {
|
||||
private String id;
|
||||
private String personId;
|
||||
private Long amount;
|
||||
|
||||
public Account(String id, String personId, Long amount) {
|
||||
this.id = id;
|
||||
this.personId = personId;
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getPersonId() {
|
||||
return personId;
|
||||
}
|
||||
|
||||
public void setPersonId(String personId) {
|
||||
this.personId = personId;
|
||||
}
|
||||
|
||||
public Long getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(Long amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
public class Person {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
public Person(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package ru.otus.spring.repostory;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import ru.otus.spring.domain.Account;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface AccountRepository extends ReactiveMongoRepository<Account, String> {
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package ru.otus.spring.repostory;
|
||||
|
||||
import org.springframework.data.mongodb.repository.Query;
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
|
||||
Flux<Person> findByName(String name);
|
||||
|
||||
@Query("{ 'name': ?0 }")
|
||||
Mono<Person> findFirstByName(String name);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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>spring-19-reactor</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>13</maven.compiler.target>
|
||||
<maven.compiler.source>13</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import ru.otus.spring.reactor.FluxService;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ConfigurableApplicationContext context = SpringApplication.run(Main.class);
|
||||
|
||||
FluxService service = context.getBean(FluxService.class);
|
||||
|
||||
service.printHello("Ivan");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package ru.otus.spring.reactor;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.Disposable;
|
||||
import reactor.core.publisher.DirectProcessor;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Service
|
||||
public class FluxService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(FluxService.class);
|
||||
|
||||
private final NonFluxService nonFluxService;
|
||||
private final DirectProcessor<Message> processor;
|
||||
private final Disposable flow;
|
||||
|
||||
@Autowired
|
||||
public FluxService(NonFluxService nonFluxService) {
|
||||
this.nonFluxService = nonFluxService;
|
||||
// Создаём процессор - это reactor-овская реализация reactive-stream интерфейса
|
||||
// Direct processor, кстати - это простой последовательный вызов методов)
|
||||
processor = DirectProcessor.create();
|
||||
// Здесь мы настриваем flow
|
||||
flow = Mono.from(processor)
|
||||
.map(nonFluxService::nonFluxSayHello)
|
||||
.subscribe(this::printMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Этот метод будет инициировать асинзронную обрабтку сообщения
|
||||
*
|
||||
* @param name это имя будет приходить из не-reactor окружения
|
||||
*/
|
||||
public void printHello(String name) {
|
||||
processor.onNext(new Message(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* А это терминальный шаг для сообщения
|
||||
*
|
||||
* @param message а это финальный шаг для сообщения, отсюда можно вернуть рзультат в не-реактив окружение
|
||||
*/
|
||||
private void printMessage(Message message) {
|
||||
logger.info("Message received: {}", message.getValue());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.otus.spring.reactor;
|
||||
|
||||
public class Message {
|
||||
|
||||
private final String value;
|
||||
|
||||
public Message(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package ru.otus.spring.reactor;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class NonFluxService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(NonFluxService.class);
|
||||
|
||||
public Message nonFluxSayHello(Message message) {
|
||||
logger.info("Message received in non-flux service: {}", message.getValue());
|
||||
|
||||
final String name = message.getValue();
|
||||
final String withHello = "Hello, " + name + "!";
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
return new Message(withHello);
|
||||
} catch (InterruptedException ex) {
|
||||
return new Message(withHello);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?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>spring-19-web-flux</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.target>13</maven.compiler.target>
|
||||
<maven.compiler.source>13</maven.compiler.source>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- работает и без неё, но нужна нам, для RxJava методов -->
|
||||
<dependency>
|
||||
<groupId>io.reactivex.rxjava2</groupId>
|
||||
<artifactId>rxjava</artifactId>
|
||||
</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.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
public class ReactorController {
|
||||
|
||||
@GetMapping("/flux/one")
|
||||
public Mono<String> one() {
|
||||
return Mono.just("one");
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
return Flux.range(1, 10);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/flux/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> stream() {
|
||||
return Flux.generate(() -> 0, (state, emitter) -> {
|
||||
emitter.next(state);
|
||||
return state + 1;
|
||||
})
|
||||
.delayElements(Duration.ofSeconds(1L))
|
||||
.map(Object::toString);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
import io.reactivex.Single;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class RxJava2Controller {
|
||||
|
||||
@GetMapping("/rx/one")
|
||||
public Single<String> single() {
|
||||
return Single.just("one");
|
||||
}
|
||||
|
||||
@GetMapping("/rx/ten")
|
||||
public Flowable<Integer> list() {
|
||||
return Flowable.range(1, 10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
target/
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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>spring-20</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-20-exercise</module>
|
||||
<module>spring-20-solution</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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>spring-20-exercise</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Зависимости WebFlux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Зависимости Reactive SpringData -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Тестирование -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
</project>
|
||||
@@ -0,0 +1,69 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.*;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context = SpringApplication.run(Main.class);
|
||||
PersonRepository repository = context.getBean(PersonRepository.class);
|
||||
|
||||
repository.saveAll(Arrays.asList(
|
||||
new Person("Pushkin", 22),
|
||||
new Person("Lermontov", 22),
|
||||
new Person("Tolstoy", 60)
|
||||
)).subscribe(p -> System.out.println(p.getLastName()));
|
||||
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> composedRoutes(PersonRepository repository) {
|
||||
return route()
|
||||
// Обратите внимание на использование хэндлера
|
||||
.GET("/func/person", accept(APPLICATION_JSON), new PersonHandler(repository)::list)
|
||||
// Обратите внимание на использование pathVariable
|
||||
.GET("/func/person/{id}", accept(APPLICATION_JSON),
|
||||
request -> repository.findById(request.pathVariable("id"))
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromValue(person)))
|
||||
.switchIfEmpty(notFound().build())
|
||||
).build();
|
||||
}
|
||||
|
||||
// Это пример хэндлера, который даже не бин
|
||||
static class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
Mono<ServerResponse> list(ServerRequest request) {
|
||||
// Обратите внимание на пример другого порядка создания response от Flux
|
||||
return ok().contentType(APPLICATION_JSON).body(repository.findAll(), Person.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
@Document
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
@Field("name")
|
||||
private String lastName;
|
||||
|
||||
private int age;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person(String lastName, int age) {
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository
|
||||
extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
Flux<Person> findAll();
|
||||
|
||||
Mono<Person> findById(String id);
|
||||
|
||||
Mono<Person> save(Mono<Person> person);
|
||||
|
||||
Flux<Person> findAllByLastName(String lastName);
|
||||
|
||||
Flux<Person> findAllByAge(int age);
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
public class AnnotatedController {
|
||||
|
||||
@GetMapping("/flux/one")
|
||||
public Mono<String> one() {
|
||||
return Mono.just("one");
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
return Flux.range(1, 10).delayElements(Duration.ofSeconds(1));
|
||||
}
|
||||
|
||||
@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> stream() {
|
||||
return Flux.generate(() -> 0, (state, emitter) -> {
|
||||
emitter.next(state);
|
||||
return state + 1;
|
||||
})
|
||||
.delayElements(Duration.ofSeconds(1L))
|
||||
.map(i -> "" + i);
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
@RestController
|
||||
public class PersonController {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public PersonController(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping("/person")
|
||||
public Flux<Person> all() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Mono<Person> byId(@PathVariable("id") String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public Mono<Person> save(@RequestBody Mono<Person> dto) {
|
||||
return repository.save(dto);
|
||||
}
|
||||
|
||||
@GetMapping("/person/find")
|
||||
public Flux<Person> byName(@RequestParam("name") String name) {
|
||||
return repository.findAllByLastName(name);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@DataMongoTest
|
||||
public class PersonRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private PersonRepository repository;
|
||||
|
||||
@Test
|
||||
public void shouldSetIdOnSave() {
|
||||
Mono<Person> personMono = repository.save(new Person("Bill", 12));
|
||||
|
||||
StepVerifier
|
||||
.create(personMono)
|
||||
.assertNext(person -> assertNotNull(person.getId()))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
@SpringBootTest
|
||||
public class PersonControllerTest {
|
||||
|
||||
@Autowired
|
||||
private RouterFunction<ServerResponse> route;
|
||||
|
||||
@Test
|
||||
public void testRoute() {
|
||||
WebTestClient client = WebTestClient
|
||||
.bindToRouterFunction(route)
|
||||
.build();
|
||||
|
||||
client.get()
|
||||
.uri("/func/person")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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>spring-20-solution</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.4.5</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Зависимости WebFlux -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Зависимости Reactive SpringData -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Тестирование -->
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</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>
|
||||
</project>
|
||||
@@ -0,0 +1,82 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
|
||||
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.queryParam;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static org.springframework.web.reactive.function.server.ServerResponse.*;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationContext context = SpringApplication.run(Main.class);
|
||||
PersonRepository repository = context.getBean(PersonRepository.class);
|
||||
|
||||
repository.saveAll(Arrays.asList(
|
||||
new Person("Pushkin", 22),
|
||||
new Person("Lermontov", 22),
|
||||
new Person("Tolstoy", 60)
|
||||
)).subscribe(p -> System.out.println(p.getLastName()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> composedRoutes(PersonRepository repository) {
|
||||
return route()
|
||||
// эта функция должна стоять раньше findAll - порядок следования роутов - важен
|
||||
.GET("/func/person", queryParam("name", StringUtils::isNotEmpty),
|
||||
request -> request.queryParam("name")
|
||||
.map(repository::findAllByLastName)
|
||||
.map(persons -> ok().body(persons, Person.class))
|
||||
.orElse(badRequest().build())
|
||||
)
|
||||
// пример другой реализации - начиная с запроса репозитория
|
||||
.GET("/func/person", queryParam("age", StringUtils::isNotEmpty),
|
||||
req -> repository.findAllByLastName(
|
||||
req.queryParam("age").orElseThrow(IllegalArgumentException::new)
|
||||
)
|
||||
.collectList()
|
||||
.flatMap(persons -> ok().body(persons, Person.class)))
|
||||
// Обратите внимание на использование хэндлера
|
||||
.GET("/func/person", accept(APPLICATION_JSON), new PersonHandler(repository)::list)
|
||||
// Обратите внимание на использование pathVariable
|
||||
.GET("/func/person/{id}", accept(APPLICATION_JSON),
|
||||
request -> repository.findById(request.pathVariable("id"))
|
||||
.flatMap(person -> ok().contentType(APPLICATION_JSON).body(fromValue(person)))
|
||||
.switchIfEmpty(notFound().build())
|
||||
).build();
|
||||
}
|
||||
|
||||
// Это пример хэндлера, который даже не бин
|
||||
static class PersonHandler {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
PersonHandler(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
Mono<ServerResponse> list(ServerRequest request) {
|
||||
// Обратите внимание на пример другого порядка создания response от Flux
|
||||
return ok().contentType(APPLICATION_JSON).body(repository.findAll(), Person.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package ru.otus.spring.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
import org.springframework.data.mongodb.core.mapping.Field;
|
||||
|
||||
@Document
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
@JsonProperty("name")
|
||||
@Field("name")
|
||||
private String lastName;
|
||||
|
||||
private int age;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person(String lastName, int age) {
|
||||
this.lastName = lastName;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
return age;
|
||||
}
|
||||
|
||||
public void setAge(int age) {
|
||||
this.age = age;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
public interface PersonRepository
|
||||
extends ReactiveMongoRepository<Person, String> {
|
||||
|
||||
Flux<Person> findAll();
|
||||
|
||||
Mono<Person> findById(String id);
|
||||
|
||||
Mono<Person> save(Mono<Person> person);
|
||||
|
||||
Flux<Person> findAllByLastName(String lastName);
|
||||
|
||||
Flux<Person> findAllByAge(int age);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@RestController
|
||||
public class AnnotatedController {
|
||||
|
||||
@GetMapping("/flux/one")
|
||||
public Mono<String> one() {
|
||||
return Mono.just("one")
|
||||
.map(String::toUpperCase);
|
||||
}
|
||||
|
||||
@GetMapping("/flux/ten")
|
||||
public Flux<Integer> list() {
|
||||
return Flux.range(1, 10);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||
public Flux<String> stream() {
|
||||
return Flux.generate(() -> 0, (state, emitter) -> {
|
||||
emitter.next(state);
|
||||
return state + 1;
|
||||
})
|
||||
.delayElements(Duration.ofSeconds(1L))
|
||||
.map(i -> "" + i);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import ru.otus.spring.domain.Person;
|
||||
import ru.otus.spring.repository.PersonRepository;
|
||||
|
||||
@RestController
|
||||
public class PersonController {
|
||||
|
||||
private PersonRepository repository;
|
||||
|
||||
public PersonController(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
@GetMapping("/person")
|
||||
public Flux<Person> all() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
@GetMapping("/person/{id}")
|
||||
public Mono<Person> byId(@PathVariable("id") String id) {
|
||||
return repository.findById(id);
|
||||
}
|
||||
|
||||
@GetMapping("/person/byname")
|
||||
public Flux<Person> byName(@RequestParam("name") String lastName) {
|
||||
return repository.findAllByLastName(lastName);
|
||||
}
|
||||
|
||||
@GetMapping("/person/byage")
|
||||
public Flux<Person> byAge(@RequestParam int age) {
|
||||
return repository.findAllByAge(age);
|
||||
}
|
||||
|
||||
@PostMapping("/person")
|
||||
public Mono<Person> save(@RequestBody Mono<Person> dto) {
|
||||
return repository.save(dto);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import ru.otus.spring.domain.Person;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
@DataMongoTest
|
||||
public class PersonRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private PersonRepository repository;
|
||||
|
||||
@Test
|
||||
public void shouldSetIdOnSave() {
|
||||
Mono<Person> personMono = repository.save(new Person("Bill", 12));
|
||||
|
||||
StepVerifier
|
||||
.create(personMono)
|
||||
.assertNext(person -> assertNotNull(person.getId()))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
|
||||
@SpringBootTest
|
||||
public class PersonControllerTest {
|
||||
|
||||
@Autowired
|
||||
private RouterFunction<ServerResponse> route;
|
||||
|
||||
@Test
|
||||
public void testRoute() {
|
||||
WebTestClient client = WebTestClient
|
||||
.bindToRouterFunction(route)
|
||||
.build();
|
||||
|
||||
client.get()
|
||||
.uri("/func/person")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?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>spring-framework-22-spring-security-start</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<java.version>11</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</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-security</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class PagesController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String indexPage() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/public")
|
||||
public String publicPage() {
|
||||
return "public";
|
||||
}
|
||||
|
||||
@GetMapping("/authenticated")
|
||||
public String authenticatedPage() {
|
||||
return "authenticated";
|
||||
}
|
||||
|
||||
@GetMapping("/success")
|
||||
public String successPage(){
|
||||
return "success";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package ru.otus.spring.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
web.ignoring()
|
||||
.antMatchers("/")
|
||||
.antMatchers( "/static/**" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf().disable()
|
||||
// По умолчанию SecurityContext хранится в сессии. Эта часть вырубает и каждый запросом приходитТ
|
||||
// .sessionManagement()
|
||||
// .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
|
||||
// .and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/public/").anonymous()
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers("/authenticated").authenticated()
|
||||
// .and()
|
||||
// .authorizeRequests().antMatchers("/public").authenticated()
|
||||
.and()
|
||||
.httpBasic()
|
||||
.and()
|
||||
.anonymous()
|
||||
.principal( "anonymous" )
|
||||
.and()
|
||||
.rememberMe().key( "Some secret" )
|
||||
;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
// return new BCryptPasswordEncoder(10);
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
// return new PasswordEncoder() {
|
||||
// @Override
|
||||
// public String encode(CharSequence charSequence) {
|
||||
// return charSequence.toString();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean matches(CharSequence charSequence, String s) {
|
||||
// return charSequence.toString().equals(s);
|
||||
// }
|
||||
// };
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser("admin").password("password").roles("ADMIN")
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
Только для авторизованных
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
<a th:href="@{public}">/public</a>
|
||||
<br>
|
||||
<a th:href="@{authenticated}">/authenticated</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
</head>
|
||||
<body>
|
||||
Доступен всем
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Вы успешно вошли</title>
|
||||
</head>
|
||||
<body>
|
||||
Вы успешно вошли
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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>spring-framework-23-auth</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>11</java.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring MVC -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<!-- For Spring Security testing -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<version>${spring-security.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.otus.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Main.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class PagesController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String indexPage() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
@GetMapping("/public")
|
||||
public String publicPage() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
Authentication authentication = securityContext.getAuthentication();
|
||||
System.out.println(authentication.getPrincipal());
|
||||
return "public";
|
||||
}
|
||||
|
||||
@GetMapping("/authenticated")
|
||||
public String authenticatedPage() {
|
||||
SecurityContext securityContext = SecurityContextHolder.getContext();
|
||||
// Authentication authentication = securityContext.getAuthentication();
|
||||
// UserDetails userDetails = (UserDetails) authentication.getDetails();
|
||||
// System.out.println(userDetails.getUsername());
|
||||
return "authenticated";
|
||||
}
|
||||
|
||||
@GetMapping("/success")
|
||||
public String successPage() {
|
||||
return "success";
|
||||
}
|
||||
|
||||
@GetMapping("/error")
|
||||
public String errorPage() {
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ru.otus.spring.security;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class AnonimusUD implements UserDetails {
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return "anonymous";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package ru.otus.spring.security;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
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.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
public void configure( WebSecurity web ) {
|
||||
web.ignoring().antMatchers( "/" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure( HttpSecurity http ) throws Exception {
|
||||
http.csrf().disable()
|
||||
// По умолчанию SecurityContext хранится в сессии
|
||||
// Это необходимо, чтобы он нигде не хранился
|
||||
// и данные приходили каждый раз с запросом
|
||||
// .sessionManagement().sessionCreationPolicy( SessionCreationPolicy.STATELESS )
|
||||
// .and()
|
||||
.authorizeRequests().antMatchers( "/public" ).anonymous()
|
||||
.and()
|
||||
.authorizeRequests().antMatchers( "/authenticated", "/success" ).authenticated()
|
||||
|
||||
.and()
|
||||
// Включает Form-based аутентификацию
|
||||
.formLogin()
|
||||
.passwordParameter( "vk_pass" )
|
||||
.successForwardUrl( "/success" );
|
||||
|
||||
// ;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void configure( AuthenticationManagerBuilder auth ) throws Exception {
|
||||
auth.inMemoryAuthentication()
|
||||
.withUser( "admin" ).password( "password" ).roles( "ADMIN" );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package ru.otus.spring.security.filter;
|
||||
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MyOwnFilter extends GenericFilterBean {
|
||||
@Override
|
||||
public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain ) throws IOException, ServletException {
|
||||
servletRequest.getParameterMap().put( "SpecialValue", new String[]{ "My dirty secret" } );
|
||||
filterChain.doFilter( servletRequest, servletResponse );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
logging:
|
||||
level:
|
||||
root: error
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Только для авторизованных</title>
|
||||
</head>
|
||||
<body>
|
||||
Только для авторизованных
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Упс...</title>
|
||||
</head>
|
||||
<body>
|
||||
Что-то пошло не так. Печалька
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Главная страница</title>
|
||||
</head>
|
||||
<body>
|
||||
<a th:href="@{public}">/public</a>
|
||||
<br>
|
||||
<a th:href="@{authenticated}">/authenticated</a>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Доступен всем</title>
|
||||
</head>
|
||||
<body>
|
||||
Доступен всем
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<title>Вы успешно вошли !</title>
|
||||
</head>
|
||||
<body>
|
||||
Вы успешно вошли !
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
package ru.otus.spring.rest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@WebMvcTest(PagesController.class)
|
||||
public class PagesControllerTest {
|
||||
|
||||
@Autowired
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@WithMockUser(
|
||||
username = "admin",
|
||||
authorities = {"ROLE_ADMIN"}
|
||||
)
|
||||
@Test
|
||||
public void testAuthenticatedOnAdmin() throws Exception {
|
||||
mockMvc.perform(get("/authenticated"))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
target/
|
||||
@@ -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 @@
|
||||
# mybatis-demo
|
||||
Пример работы с БД через MyBatis
|
||||
@@ -0,0 +1,61 @@
|
||||
<?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.4.5</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>
|
||||
<maven.compiler.source>11</maven.compiler.source>
|
||||
<maven.compiler.target>11</maven.compiler.target>
|
||||
<mybatis.version>2.1.4</mybatis.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.mybatis.spring.boot</groupId>
|
||||
<artifactId>mybatis-spring-boot-starter</artifactId>
|
||||
<version>${mybatis.version}</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>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+14
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
+18
@@ -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);
|
||||
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
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);
|
||||
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+42
@@ -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)
|
||||
);
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package ru.otus.example.mybatisdemo.repositories;
|
||||
|
||||
import lombok.val;
|
||||
import org.assertj.core.api.recursive.comparison.RecursiveComparisonConfiguration;
|
||||
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 java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@DisplayName("Репозиторий на основе MyBatis для работы со студентами ")
|
||||
@SpringBootTest
|
||||
@Transactional
|
||||
public class OtusStudentRepositoryTest {
|
||||
|
||||
private static final String FIELD_ID = "id";
|
||||
private static final String FIELD_PHOTO_URL = "photoUrl";
|
||||
private static final String FIELD_NAME = "name";
|
||||
|
||||
private static final long FIRST_STUDENT_ID = 1L;
|
||||
private static final long FIRST_AVATAR_ID = 1L;
|
||||
private static final String FIRST_STUDENT_NAME = "student_01";
|
||||
private static final String FIRST_AVATAR_URL = "photoUrl_01";
|
||||
private static final String STUDENT_NEW_NAME = "Висусуалий";
|
||||
|
||||
private static final int EXPECTED_NUMBER_OF_STUDENTS = 10;
|
||||
private static final long INSERTED_STUDENT_ID = 11L;
|
||||
private static final int EXPECTED_EMAILS_COUNT = 2;
|
||||
private static final int EXPECTED_COURSES_COUNT = 3;
|
||||
|
||||
@Autowired
|
||||
private OtusStudentRepository studentRepositoryMyBatis;
|
||||
|
||||
@DisplayName("должен загружать список всех студентов с полной информацией о них")
|
||||
@Test
|
||||
void shouldReturnCorrectStudentsListWithAllInfo() {
|
||||
val students = studentRepositoryMyBatis.findAllWithAllInfo();
|
||||
assertThat(students).isNotNull().hasSize(EXPECTED_NUMBER_OF_STUDENTS)
|
||||
.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(EXPECTED_NUMBER_OF_STUDENTS);
|
||||
}
|
||||
|
||||
@DisplayName(" должен загружать информацию о нужном студенте")
|
||||
@Test
|
||||
void shouldFindExpectedStudentById(){
|
||||
val actualStudent = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
|
||||
|
||||
assertThat(actualStudent).isNotNull();
|
||||
assertThat(actualStudent.getName()).isEqualTo(FIRST_STUDENT_NAME);
|
||||
assertThat(actualStudent.getAvatar()).isNotNull()
|
||||
.hasFieldOrPropertyWithValue(FIELD_ID, FIRST_STUDENT_ID)
|
||||
.hasFieldOrPropertyWithValue(FIELD_PHOTO_URL, FIRST_AVATAR_URL);
|
||||
assertThat(actualStudent.getEmails()).isNotNull().hasSize(EXPECTED_EMAILS_COUNT);
|
||||
assertThat(actualStudent.getCourses()).isNotNull().hasSize(EXPECTED_COURSES_COUNT);
|
||||
}
|
||||
|
||||
@DisplayName(" должен сохранить, а потом загрузить информацию о нужном студенте")
|
||||
@Test
|
||||
void shouldSaveAndLoadCorrectStudent() {
|
||||
val expectedStudent = new OtusStudent(0, STUDENT_NEW_NAME,
|
||||
new Avatar(FIRST_AVATAR_ID, FIRST_AVATAR_URL), List.of(), List.of());
|
||||
studentRepositoryMyBatis.insert(expectedStudent);
|
||||
val actualStudent = studentRepositoryMyBatis.findById(INSERTED_STUDENT_ID);
|
||||
|
||||
assertThat(actualStudent)
|
||||
.isNotNull()
|
||||
.usingRecursiveComparison(
|
||||
RecursiveComparisonConfiguration.builder()
|
||||
.withIgnoredFields(FIELD_ID).build())
|
||||
.isEqualTo(expectedStudent);
|
||||
}
|
||||
|
||||
|
||||
@DisplayName(" должен обновлять имя студента в БД")
|
||||
@Test
|
||||
void shouldUpdateStudentName() {
|
||||
val student = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
|
||||
student.setName(STUDENT_NEW_NAME);
|
||||
studentRepositoryMyBatis.updateName(student);
|
||||
val actualStudent = studentRepositoryMyBatis.findById(FIRST_STUDENT_ID);
|
||||
|
||||
assertThat(actualStudent).isNotNull().hasFieldOrPropertyWithValue(FIELD_NAME, student.getName());
|
||||
}
|
||||
|
||||
@DisplayName("должен удалять студента из БД по id")
|
||||
@Test
|
||||
void shouldDeleteStudentFromDbById() {
|
||||
val studentsCountBefore = studentRepositoryMyBatis.getStudentsCount();
|
||||
studentRepositoryMyBatis.deleteById(FIRST_STUDENT_ID);
|
||||
val studentsCountAfter = studentRepositoryMyBatis.getStudentsCount();
|
||||
|
||||
assertThat(studentsCountBefore - studentsCountAfter).isEqualTo(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:h2:mem:testdb
|
||||
initialization-mode: always
|
||||
|
||||
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);
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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>demo-projects</artifactId>
|
||||
<version>1.0</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>spring-jdbc-demo</module>
|
||||
<module>mybatis-demo</module>
|
||||
</modules>
|
||||
</project>
|
||||
@@ -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,57 @@
|
||||
<?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.4.5</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>
|
||||
<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-jdbc</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>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
+13
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+18
@@ -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;
|
||||
}
|
||||
+9
@@ -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();
|
||||
}
|
||||
+36
@@ -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 static class CourseRowMapper implements RowMapper<Course> {
|
||||
@Override
|
||||
public Course mapRow(ResultSet rs, int i) throws SQLException {
|
||||
return new Course(rs.getLong(1), rs.getString(2));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -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();
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
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.function.Function;
|
||||
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, Function.identity()));
|
||||
relations.forEach(r -> {
|
||||
if (students.containsKey(r.getStudentId()) && coursesMap.containsKey(r.getCourseId())) {
|
||||
students.get(r.getStudentId()).getCourses().add(coursesMap.get(r.getCourseId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
}
|
||||
+11
@@ -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)
|
||||
);
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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 {
|
||||
|
||||
private static final int EXPECTED_NUMBER_OF_STUDENTS = 10;
|
||||
|
||||
@Autowired
|
||||
private OtusStudentRepositoryJdbcImpl repositoryJdbc;
|
||||
|
||||
@DisplayName("должен загружать список всех студентов с полной информацией о них")
|
||||
@Test
|
||||
void shouldReturnCorrectStudentsListWithAllInfo() {
|
||||
val students = repositoryJdbc.findAllWithAllInfo();
|
||||
assertThat(students).isNotNull().hasSize(EXPECTED_NUMBER_OF_STUDENTS)
|
||||
.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);
|
||||
students.forEach(System.out::println);
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user