diff --git a/Chapter 5/mongodb-demo/.gitignore b/Chapter 5/mongodb-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/mongodb-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/mongodb-demo/pom.xml b/Chapter 5/mongodb-demo/pom.xml
new file mode 100644
index 00000000..8474a258
--- /dev/null
+++ b/Chapter 5/mongodb-demo/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+ geektime.spring.data.reactive
+ mongodb-demo
+ 0.0.1-SNAPSHOT
+ mongodb-demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb-reactive
+
+
+
+ org.joda
+ joda-money
+ 1.0.1
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplication.java b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplication.java
new file mode 100644
index 00000000..11c08bfc
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplication.java
@@ -0,0 +1,101 @@
+package geektime.spring.data.reactive.mongodbdemo;
+
+import geektime.spring.data.reactive.mongodbdemo.converter.MoneyReadConverter;
+import geektime.spring.data.reactive.mongodbdemo.converter.MoneyWriteConverter;
+import geektime.spring.data.reactive.mongodbdemo.model.Coffee;
+import lombok.extern.slf4j.Slf4j;
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
+import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
+import org.springframework.data.mongodb.core.query.Update;
+import reactor.core.scheduler.Schedulers;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import static org.springframework.data.mongodb.core.query.Criteria.where;
+import static org.springframework.data.mongodb.core.query.Query.query;
+
+@SpringBootApplication
+@Slf4j
+public class MongodbDemoApplication implements ApplicationRunner {
+ @Autowired
+ private ReactiveMongoTemplate mongoTemplate;
+ private CountDownLatch cdl = new CountDownLatch(2);
+
+ public static void main(String[] args) {
+ SpringApplication.run(MongodbDemoApplication.class, args);
+ }
+
+ @Bean
+ public MongoCustomConversions mongoCustomConversions() {
+ return new MongoCustomConversions(
+ Arrays.asList(new MoneyReadConverter(),
+ new MoneyWriteConverter()));
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+// startFromInsertion(() -> log.info("Runnable"));
+ startFromInsertion(() -> {
+ log.info("Runnable");
+ decreaseHighPrice();
+ });
+
+ log.info("after starting");
+
+// decreaseHighPrice();
+
+ cdl.await();
+ }
+
+ private void startFromInsertion(Runnable runnable) {
+ mongoTemplate.insertAll(initCoffee())
+ .publishOn(Schedulers.elastic())
+ .doOnNext(c -> log.info("Next: {}", c))
+ .doOnComplete(runnable)
+ .doFinally(s -> {
+ cdl.countDown();
+ log.info("Finnally 1, {}", s);
+ })
+ .count()
+ .subscribe(c -> log.info("Insert {} records", c));
+ }
+
+ private void decreaseHighPrice() {
+ mongoTemplate.updateMulti(query(where("price").gte(3000L)),
+ new Update().inc("price", -500L)
+ .currentDate("updateTime"), Coffee.class)
+ .doFinally(s -> {
+ cdl.countDown();
+ log.info("Finnally 2, {}", s);
+ })
+ .subscribe(r -> log.info("Result is {}", r));
+ }
+
+ private List initCoffee() {
+ Coffee espresso = Coffee.builder()
+ .name("espresso")
+ .price(Money.of(CurrencyUnit.of("CNY"), 20.0))
+ .createTime(new Date())
+ .updateTime(new Date())
+ .build();
+ Coffee latte = Coffee.builder()
+ .name("latte")
+ .price(Money.of(CurrencyUnit.of("CNY"), 30.0))
+ .createTime(new Date())
+ .updateTime(new Date())
+ .build();
+
+ return Arrays.asList(espresso, latte);
+ }
+}
diff --git a/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyReadConverter.java b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyReadConverter.java
new file mode 100644
index 00000000..579e4ff0
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyReadConverter.java
@@ -0,0 +1,12 @@
+package geektime.spring.data.reactive.mongodbdemo.converter;
+
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyReadConverter implements Converter {
+ @Override
+ public Money convert(Long aLong) {
+ return Money.ofMinor(CurrencyUnit.of("CNY"), aLong);
+ }
+}
diff --git a/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyWriteConverter.java b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyWriteConverter.java
new file mode 100644
index 00000000..a52b1376
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/converter/MoneyWriteConverter.java
@@ -0,0 +1,11 @@
+package geektime.spring.data.reactive.mongodbdemo.converter;
+
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyWriteConverter implements Converter {
+ @Override
+ public Long convert(Money money) {
+ return money.getAmountMinorLong();
+ }
+}
diff --git a/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/model/Coffee.java b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/model/Coffee.java
new file mode 100644
index 00000000..7eb18c3c
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/main/java/geektime/spring/data/reactive/mongodbdemo/model/Coffee.java
@@ -0,0 +1,21 @@
+package geektime.spring.data.reactive.mongodbdemo.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.joda.money.Money;
+
+import java.util.Date;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Coffee {
+ private String id;
+ private String name;
+ private Money price;
+ private Date createTime;
+ private Date updateTime;
+}
diff --git a/Chapter 5/mongodb-demo/src/main/resources/application.properties b/Chapter 5/mongodb-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..38c73bce
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.data.mongodb.uri=mongodb://springbucks:springbucks@localhost:27017/springbucks
\ No newline at end of file
diff --git a/Chapter 5/mongodb-demo/src/test/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplicationTests.java b/Chapter 5/mongodb-demo/src/test/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplicationTests.java
new file mode 100644
index 00000000..8b86be35
--- /dev/null
+++ b/Chapter 5/mongodb-demo/src/test/java/geektime/spring/data/reactive/mongodbdemo/MongodbDemoApplicationTests.java
@@ -0,0 +1,16 @@
+package geektime.spring.data.reactive.mongodbdemo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MongodbDemoApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/Chapter 5/performance-aspect-demo/.gitignore b/Chapter 5/performance-aspect-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/performance-aspect-demo/pom.xml b/Chapter 5/performance-aspect-demo/pom.xml
new file mode 100644
index 00000000..195251c0
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/pom.xml
@@ -0,0 +1,70 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ geektime.spring
+ springbucks
+ 0.0.1-SNAPSHOT
+ springbucks
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.joda
+ joda-money
+ 1.0.1
+
+
+ org.jadira.usertype
+ usertype.core
+ 6.0.1.GA
+
+
+
+ p6spy
+ p6spy
+ 3.8.1
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/SpringBucksApplication.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/SpringBucksApplication.java
new file mode 100644
index 00000000..cc0cda5d
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/SpringBucksApplication.java
@@ -0,0 +1,50 @@
+package geektime.spring.springbucks;
+
+import geektime.spring.springbucks.model.Coffee;
+import geektime.spring.springbucks.model.CoffeeOrder;
+import geektime.spring.springbucks.model.OrderState;
+import geektime.spring.springbucks.repository.CoffeeRepository;
+import geektime.spring.springbucks.service.CoffeeOrderService;
+import geektime.spring.springbucks.service.CoffeeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import java.util.Optional;
+
+@Slf4j
+@EnableTransactionManagement
+@SpringBootApplication
+@EnableJpaRepositories
+@EnableAspectJAutoProxy
+public class SpringBucksApplication implements ApplicationRunner {
+ @Autowired
+ private CoffeeRepository coffeeRepository;
+ @Autowired
+ private CoffeeService coffeeService;
+ @Autowired
+ private CoffeeOrderService orderService;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBucksApplication.class, args);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ log.info("All Coffee: {}", coffeeRepository.findAll());
+
+ Optional latte = coffeeService.findOneCoffee("Latte");
+ if (latte.isPresent()) {
+ CoffeeOrder order = orderService.createOrder("Li Lei", latte.get());
+ log.info("Update INIT to PAID: {}", orderService.updateState(order, OrderState.PAID));
+ log.info("Update PAID to INIT: {}", orderService.updateState(order, OrderState.INIT));
+ }
+ }
+}
+
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/aspect/PerformanceAspect.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/aspect/PerformanceAspect.java
new file mode 100644
index 00000000..b091ba6d
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/aspect/PerformanceAspect.java
@@ -0,0 +1,35 @@
+package geektime.spring.springbucks.aspect;
+
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+@Slf4j
+public class PerformanceAspect {
+// @Around("execution(* geektime.spring.springbucks.repository..*(..))")
+ @Around("repositoryOps()")
+ public Object logPerformance(ProceedingJoinPoint pjp) throws Throwable {
+ long startTime = System.currentTimeMillis();
+ String name = "-";
+ String result = "Y";
+ try {
+ name = pjp.getSignature().toShortString();
+ return pjp.proceed();
+ } catch (Throwable t) {
+ result = "N";
+ throw t;
+ } finally {
+ long endTime = System.currentTimeMillis();
+ log.info("{};{};{}ms", name, result, endTime - startTime);
+ }
+ }
+
+ @Pointcut("execution(* geektime.spring.springbucks.repository..*(..))")
+ private void repositoryOps() {
+ }
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/BaseEntity.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/BaseEntity.java
new file mode 100644
index 00000000..31233fb6
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/BaseEntity.java
@@ -0,0 +1,30 @@
+package geektime.spring.springbucks.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import javax.persistence.Column;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import java.io.Serializable;
+import java.util.Date;
+
+@MappedSuperclass
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaseEntity implements Serializable {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+ @Column(updatable = false)
+ @CreationTimestamp
+ private Date createTime;
+ @UpdateTimestamp
+ private Date updateTime;
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/Coffee.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/Coffee.java
new file mode 100644
index 00000000..a4482e7b
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/Coffee.java
@@ -0,0 +1,28 @@
+package geektime.spring.springbucks.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.hibernate.annotations.Type;
+import org.joda.money.Money;
+
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import java.io.Serializable;
+
+@Entity
+@Table(name = "T_COFFEE")
+@Builder
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class Coffee extends BaseEntity implements Serializable {
+ private String name;
+ @Type(type = "org.jadira.usertype.moneyandcurrency.joda.PersistentMoneyMinorAmount",
+ parameters = {@org.hibernate.annotations.Parameter(name = "currencyCode", value = "CNY")})
+ private Money price;
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java
new file mode 100644
index 00000000..0e45357d
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java
@@ -0,0 +1,37 @@
+package geektime.spring.springbucks.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Enumerated;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.List;
+
+@Entity
+@Table(name = "T_ORDER")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CoffeeOrder extends BaseEntity implements Serializable {
+ private String customer;
+ @ManyToMany
+ @JoinTable(name = "T_ORDER_COFFEE")
+ @OrderBy("id")
+ private List items;
+ @Enumerated
+ @Column(nullable = false)
+ private OrderState state;
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/OrderState.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/OrderState.java
new file mode 100644
index 00000000..212d6b0d
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/model/OrderState.java
@@ -0,0 +1,5 @@
+package geektime.spring.springbucks.model;
+
+public enum OrderState {
+ INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java
new file mode 100644
index 00000000..333c5ceb
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java
@@ -0,0 +1,7 @@
+package geektime.spring.springbucks.repository;
+
+import geektime.spring.springbucks.model.CoffeeOrder;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CoffeeOrderRepository extends JpaRepository {
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java
new file mode 100644
index 00000000..614bb341
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java
@@ -0,0 +1,7 @@
+package geektime.spring.springbucks.repository;
+
+import geektime.spring.springbucks.model.Coffee;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CoffeeRepository extends JpaRepository {
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeOrderService.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeOrderService.java
new file mode 100644
index 00000000..19f015d4
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeOrderService.java
@@ -0,0 +1,43 @@
+package geektime.spring.springbucks.service;
+
+import geektime.spring.springbucks.model.Coffee;
+import geektime.spring.springbucks.model.CoffeeOrder;
+import geektime.spring.springbucks.model.OrderState;
+import geektime.spring.springbucks.repository.CoffeeOrderRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.transaction.Transactional;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+@Slf4j
+@Service
+@Transactional
+public class CoffeeOrderService {
+ @Autowired
+ private CoffeeOrderRepository orderRepository;
+
+ public CoffeeOrder createOrder(String customer, Coffee...coffee) {
+ CoffeeOrder order = CoffeeOrder.builder()
+ .customer(customer)
+ .items(new ArrayList<>(Arrays.asList(coffee)))
+ .state(OrderState.INIT)
+ .build();
+ CoffeeOrder saved = orderRepository.save(order);
+ log.info("New Order: {}", saved);
+ return saved;
+ }
+
+ public boolean updateState(CoffeeOrder order, OrderState state) {
+ if (state.compareTo(order.getState()) <= 0) {
+ log.warn("Wrong State order: {}, {}", state, order.getState());
+ return false;
+ }
+ order.setState(state);
+ orderRepository.save(order);
+ log.info("Updated Order: {}", order);
+ return true;
+ }
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeService.java b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeService.java
new file mode 100644
index 00000000..81cc3e86
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/java/geektime/spring/springbucks/service/CoffeeService.java
@@ -0,0 +1,29 @@
+package geektime.spring.springbucks.service;
+
+import geektime.spring.springbucks.model.Coffee;
+import geektime.spring.springbucks.repository.CoffeeRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.ExampleMatcher;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+
+import static org.springframework.data.domain.ExampleMatcher.GenericPropertyMatchers.exact;
+
+@Slf4j
+@Service
+public class CoffeeService {
+ @Autowired
+ private CoffeeRepository coffeeRepository;
+
+ public Optional findOneCoffee(String name) {
+ ExampleMatcher matcher = ExampleMatcher.matching()
+ .withMatcher("name", exact().ignoreCase());
+ Optional coffee = coffeeRepository.findOne(
+ Example.of(Coffee.builder().name(name).build(), matcher));
+ log.info("Coffee Found: {}", coffee);
+ return coffee;
+ }
+}
diff --git a/Chapter 5/performance-aspect-demo/src/main/resources/application.properties b/Chapter 5/performance-aspect-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..a925e566
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/resources/application.properties
@@ -0,0 +1,8 @@
+spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
+spring.datasource.url=jdbc:p6spy:h2:mem:testdb
+spring.datasource.username=sa
+spring.datasource.password=
+
+spring.jpa.hibernate.ddl-auto=none
+#spring.jpa.properties.hibernate.show_sql=true
+#spring.jpa.properties.hibernate.format_sql=true
\ No newline at end of file
diff --git a/Chapter 5/performance-aspect-demo/src/main/resources/data.sql b/Chapter 5/performance-aspect-demo/src/main/resources/data.sql
new file mode 100644
index 00000000..929682f1
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/resources/data.sql
@@ -0,0 +1,5 @@
+insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
diff --git a/Chapter 5/performance-aspect-demo/src/main/resources/schema.sql b/Chapter 5/performance-aspect-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..6d2aab60
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/resources/schema.sql
@@ -0,0 +1,26 @@
+drop table t_coffee if exists;
+drop table t_order if exists;
+drop table t_order_coffee if exists;
+
+create table t_coffee (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ name varchar(255),
+ price bigint,
+ primary key (id)
+);
+
+create table t_order (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ customer varchar(255),
+ state integer not null,
+ primary key (id)
+);
+
+create table t_order_coffee (
+ coffee_order_id bigint not null,
+ items_id bigint not null
+);
diff --git a/Chapter 5/performance-aspect-demo/src/main/resources/spy.properties b/Chapter 5/performance-aspect-demo/src/main/resources/spy.properties
new file mode 100644
index 00000000..49e45f51
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/main/resources/spy.properties
@@ -0,0 +1,8 @@
+# 单行日志
+logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat
+# 使用Slf4J记录sql
+appender=com.p6spy.engine.spy.appender.Slf4JLogger
+# 是否开启慢SQL记录
+outagedetection=true
+# 慢SQL记录标准,单位秒
+outagedetectioninterval=2
\ No newline at end of file
diff --git a/Chapter 5/performance-aspect-demo/src/test/java/geektime/spring/springbucks/SpringBucksApplicationTests.java b/Chapter 5/performance-aspect-demo/src/test/java/geektime/spring/springbucks/SpringBucksApplicationTests.java
new file mode 100644
index 00000000..db4843b4
--- /dev/null
+++ b/Chapter 5/performance-aspect-demo/src/test/java/geektime/spring/springbucks/SpringBucksApplicationTests.java
@@ -0,0 +1,17 @@
+package geektime.spring.springbucks;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SpringBucksApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git a/Chapter 5/r2dbc-repository-demo/.gitignore b/Chapter 5/r2dbc-repository-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/r2dbc-repository-demo/pom.xml b/Chapter 5/r2dbc-repository-demo/pom.xml
new file mode 100644
index 00000000..836ec549
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+ geektime.spring.data.reactive
+ r2dbc-repository-demo
+ 0.0.1-SNAPSHOT
+ r2dbc-repository-demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+
+ org.springframework.data
+ spring-data-r2dbc
+ 1.0.0.M1
+
+
+
+ io.r2dbc
+ r2dbc-h2
+ 1.0.0.M6
+
+
+
+
+ org.joda
+ joda-money
+ 1.0.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ spring-milestone
+ Spring Maven MILESTONE Repository
+ http://repo.spring.io/libs-milestone
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryDemoApplication.java b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryDemoApplication.java
new file mode 100644
index 00000000..1127aef7
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryDemoApplication.java
@@ -0,0 +1,71 @@
+package geektime.spring.data.reactive.r2dbc;
+
+import geektime.spring.data.reactive.r2dbc.converter.MoneyReadConverter;
+import geektime.spring.data.reactive.r2dbc.converter.MoneyWriteConverter;
+import geektime.spring.data.reactive.r2dbc.repository.CoffeeRepository;
+import io.r2dbc.h2.H2ConnectionConfiguration;
+import io.r2dbc.h2.H2ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
+import org.springframework.data.r2dbc.dialect.Dialect;
+import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
+import reactor.core.publisher.Flux;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+@SpringBootApplication
+@Slf4j
+@EnableR2dbcRepositories
+public class R2dbcRepositoryDemoApplication extends AbstractR2dbcConfiguration
+ implements ApplicationRunner {
+ @Autowired
+ private CoffeeRepository repository;
+
+ public static void main(String[] args) {
+ SpringApplication.run(R2dbcRepositoryDemoApplication.class, args);
+ }
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ return new H2ConnectionFactory(
+ H2ConnectionConfiguration.builder()
+ .inMemory("testdb")
+ .username("sa")
+ .build());
+ }
+
+ @Bean
+ public R2dbcCustomConversions r2dbcCustomConversions() {
+ Dialect dialect = getDialect(connectionFactory());
+ CustomConversions.StoreConversions storeConversions =
+ CustomConversions.StoreConversions.of(dialect.getSimpleTypeHolder());
+ return new R2dbcCustomConversions(storeConversions,
+ Arrays.asList(new MoneyReadConverter(), new MoneyWriteConverter()));
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ CountDownLatch cdl = new CountDownLatch(2);
+
+ repository.findAllById(Flux.just(1L, 2L))
+ .map(c -> c.getName() + "-" + c.getPrice().toString())
+ .doFinally(s -> cdl.countDown())
+ .subscribe(c -> log.info("Find {}", c));
+
+ repository.findByName("mocha")
+ .doFinally(s -> cdl.countDown())
+ .subscribe(c -> log.info("Find {}", c));
+
+ cdl.await();
+ }
+}
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java
new file mode 100644
index 00000000..66d6e37f
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java
@@ -0,0 +1,12 @@
+package geektime.spring.data.reactive.r2dbc.converter;
+
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyReadConverter implements Converter {
+ @Override
+ public Money convert(Long aLong) {
+ return Money.ofMinor(CurrencyUnit.of("CNY"), aLong);
+ }
+}
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java
new file mode 100644
index 00000000..2c4dcf08
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java
@@ -0,0 +1,11 @@
+package geektime.spring.data.reactive.r2dbc.converter;
+
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyWriteConverter implements Converter {
+ @Override
+ public Long convert(Money money) {
+ return money.getAmountMinorLong();
+ }
+}
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java
new file mode 100644
index 00000000..ee105380
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java
@@ -0,0 +1,25 @@
+package geektime.spring.data.reactive.r2dbc.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.joda.money.Money;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.util.Date;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+@Table("t_coffee")
+public class Coffee {
+ @Id
+ private Long id;
+ private String name;
+ private Money price;
+ private Date createTime;
+ private Date updateTime;
+}
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/repository/CoffeeRepository.java b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/repository/CoffeeRepository.java
new file mode 100644
index 00000000..945730f4
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/java/geektime/spring/data/reactive/r2dbc/repository/CoffeeRepository.java
@@ -0,0 +1,11 @@
+package geektime.spring.data.reactive.r2dbc.repository;
+
+import geektime.spring.data.reactive.r2dbc.model.Coffee;
+import org.springframework.data.r2dbc.repository.query.Query;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import reactor.core.publisher.Flux;
+
+public interface CoffeeRepository extends ReactiveCrudRepository {
+ @Query("select * from t_coffee where name = $1")
+ Flux findByName(String name);
+}
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/resources/application.properties b/Chapter 5/r2dbc-repository-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/resources/data.sql b/Chapter 5/r2dbc-repository-demo/src/main/resources/data.sql
new file mode 100644
index 00000000..929682f1
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/resources/data.sql
@@ -0,0 +1,5 @@
+insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
diff --git a/Chapter 5/r2dbc-repository-demo/src/main/resources/schema.sql b/Chapter 5/r2dbc-repository-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..8a98c383
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/main/resources/schema.sql
@@ -0,0 +1,10 @@
+drop table t_coffee if exists;
+
+create table t_coffee (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ name varchar(255),
+ price bigint,
+ primary key (id)
+);
diff --git a/Chapter 5/r2dbc-repository-demo/src/test/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryApplicationTests.java b/Chapter 5/r2dbc-repository-demo/src/test/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryApplicationTests.java
new file mode 100644
index 00000000..61606d11
--- /dev/null
+++ b/Chapter 5/r2dbc-repository-demo/src/test/java/geektime/spring/data/reactive/r2dbc/R2dbcRepositoryApplicationTests.java
@@ -0,0 +1,16 @@
+package geektime.spring.data.reactive.r2dbc;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class R2dbcRepositoryApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/Chapter 5/reactive-springbucks/.gitignore b/Chapter 5/reactive-springbucks/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/reactive-springbucks/pom.xml b/Chapter 5/reactive-springbucks/pom.xml
new file mode 100644
index 00000000..0fe0b991
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+ geektime.spring
+ reactive-springbucks
+ 0.0.1-SNAPSHOT
+ reactive-springbucks
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis-reactive
+
+
+
+ org.apache.commons
+ commons-pool2
+
+
+
+ org.springframework.data
+ spring-data-r2dbc
+ 1.0.0.M1
+
+
+
+ io.r2dbc
+ r2dbc-h2
+ 1.0.0.M6
+
+
+
+ org.joda
+ joda-money
+ 1.0.1
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.5
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ spring-milestone
+ Spring Maven MILESTONE Repository
+ http://repo.spring.io/libs-milestone
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/ReactiveSpringbucksApplication.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/ReactiveSpringbucksApplication.java
new file mode 100644
index 00000000..876babe8
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/ReactiveSpringbucksApplication.java
@@ -0,0 +1,65 @@
+package geektime.spring.springbucks;
+
+import geektime.spring.springbucks.converter.MoneyReadConverter;
+import geektime.spring.springbucks.converter.MoneyWriteConverter;
+import geektime.spring.springbucks.model.Coffee;
+import io.r2dbc.h2.H2ConnectionConfiguration;
+import io.r2dbc.h2.H2ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
+import org.springframework.data.r2dbc.dialect.Dialect;
+import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
+import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
+import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.util.Arrays;
+
+@SpringBootApplication
+@EnableR2dbcRepositories
+@Slf4j
+public class ReactiveSpringbucksApplication extends AbstractR2dbcConfiguration {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ReactiveSpringbucksApplication.class, args);
+ }
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ return new H2ConnectionFactory(
+ H2ConnectionConfiguration.builder()
+ .inMemory("testdb")
+ .username("sa")
+ .build());
+ }
+
+ @Bean
+ public R2dbcCustomConversions r2dbcCustomConversions() {
+ Dialect dialect = getDialect(connectionFactory());
+ CustomConversions.StoreConversions storeConversions =
+ CustomConversions.StoreConversions.of(dialect.getSimpleTypeHolder());
+ return new R2dbcCustomConversions(storeConversions,
+ Arrays.asList(new MoneyReadConverter(), new MoneyWriteConverter()));
+ }
+
+ @Bean
+ public ReactiveRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
+ StringRedisSerializer keySerializer = new StringRedisSerializer();
+ Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer<>(Coffee.class);
+
+ RedisSerializationContext.RedisSerializationContextBuilder builder
+ = RedisSerializationContext.newSerializationContext(keySerializer);
+
+ RedisSerializationContext context = builder.value(valueSerializer).build();
+
+ return new ReactiveRedisTemplate<>(factory, context);
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/SpringbucksRunner.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/SpringbucksRunner.java
new file mode 100644
index 00000000..28a12014
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/SpringbucksRunner.java
@@ -0,0 +1,49 @@
+package geektime.spring.springbucks;
+
+import geektime.spring.springbucks.model.Coffee;
+import geektime.spring.springbucks.model.CoffeeOrder;
+import geektime.spring.springbucks.model.OrderState;
+import geektime.spring.springbucks.service.CoffeeService;
+import geektime.spring.springbucks.service.OrderService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.stereotype.Component;
+
+import java.util.Arrays;
+import java.util.Date;
+
+@Component
+@Slf4j
+public class SpringbucksRunner implements ApplicationRunner {
+ @Autowired
+ private CoffeeService coffeeService;
+ @Autowired
+ private OrderService orderService;
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ coffeeService.initCache()
+ .then(
+ coffeeService.findOneCoffee("mocha")
+ .flatMap(c -> {
+ CoffeeOrder order = createOrder("Li Lei", c);
+ return orderService.create(order);
+ })
+ .doOnError(t -> log.error("error", t)))
+ .subscribe(o -> log.info("Create Order: {}", o));
+ log.info("After Subscribe");
+ Thread.sleep(5000);
+ }
+
+ private CoffeeOrder createOrder(String customer, Coffee... coffee) {
+ return CoffeeOrder.builder()
+ .customer(customer)
+ .items(Arrays.asList(coffee))
+ .state(OrderState.INIT)
+ .createTime(new Date())
+ .updateTime(new Date())
+ .build();
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyReadConverter.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyReadConverter.java
new file mode 100644
index 00000000..e1c21414
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyReadConverter.java
@@ -0,0 +1,14 @@
+package geektime.spring.springbucks.converter;
+
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.ReadingConverter;
+
+@ReadingConverter
+public class MoneyReadConverter implements Converter {
+ @Override
+ public Money convert(Long aLong) {
+ return Money.ofMinor(CurrencyUnit.of("CNY"), aLong);
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyWriteConverter.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyWriteConverter.java
new file mode 100644
index 00000000..0912246d
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/converter/MoneyWriteConverter.java
@@ -0,0 +1,13 @@
+package geektime.spring.springbucks.converter;
+
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.convert.WritingConverter;
+
+@WritingConverter
+public class MoneyWriteConverter implements Converter {
+ @Override
+ public Long convert(Money money) {
+ return money.getAmountMinorLong();
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/Coffee.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/Coffee.java
new file mode 100644
index 00000000..46b38310
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/Coffee.java
@@ -0,0 +1,32 @@
+package geektime.spring.springbucks.model;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import geektime.spring.springbucks.serializer.MoneyDeserializer;
+import geektime.spring.springbucks.serializer.MoneySerializer;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.joda.money.Money;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Table("t_coffee")
+@Builder
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Coffee implements Serializable {
+ @Id
+ private Long id;
+ private String name;
+ @JsonSerialize(using = MoneySerializer.class)
+ @JsonDeserialize(using = MoneyDeserializer.class)
+ private Money price;
+ private Date createTime;
+ private Date updateTime;
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java
new file mode 100644
index 00000000..668987dc
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/CoffeeOrder.java
@@ -0,0 +1,23 @@
+package geektime.spring.springbucks.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class CoffeeOrder implements Serializable {
+ private Long id;
+ private String customer;
+ private OrderState state;
+ private List items;
+ private Date createTime;
+ private Date updateTime;
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/OrderState.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/OrderState.java
new file mode 100644
index 00000000..212d6b0d
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/model/OrderState.java
@@ -0,0 +1,5 @@
+package geektime.spring.springbucks.model;
+
+public enum OrderState {
+ INIT, PAID, BREWING, BREWED, TAKEN, CANCELLED
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java
new file mode 100644
index 00000000..22899fc2
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeOrderRepository.java
@@ -0,0 +1,32 @@
+package geektime.spring.springbucks.repository;
+
+import geektime.spring.springbucks.model.CoffeeOrder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.r2dbc.function.DatabaseClient;
+import org.springframework.stereotype.Repository;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.sql.Timestamp;
+
+@Repository
+public class CoffeeOrderRepository {
+ @Autowired
+ private DatabaseClient databaseClient;
+
+ public Mono save(CoffeeOrder order) {
+ return databaseClient.insert().into("t_order")
+ .value("customer", order.getCustomer())
+ .value("state", order.getState().ordinal())
+ .value("create_time", new Timestamp(order.getCreateTime().getTime()))
+ .value("update_time", new Timestamp(order.getUpdateTime().getTime()))
+ .fetch()
+ .first()
+ .flatMap(m -> Mono.just((Long) m.get("ID")))
+ .flatMap(id -> Flux.fromIterable(order.getItems())
+ .flatMap(c -> databaseClient.insert().into("t_order_coffee")
+ .value("coffee_order_id", id)
+ .value("items_id", c.getId())
+ .then()).then(Mono.just(id)));
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java
new file mode 100644
index 00000000..e81a9be4
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/repository/CoffeeRepository.java
@@ -0,0 +1,11 @@
+package geektime.spring.springbucks.repository;
+
+import geektime.spring.springbucks.model.Coffee;
+import org.springframework.data.r2dbc.repository.R2dbcRepository;
+import org.springframework.data.r2dbc.repository.query.Query;
+import reactor.core.publisher.Mono;
+
+public interface CoffeeRepository extends R2dbcRepository {
+ @Query("select * from t_coffee where name=$1")
+ Mono findByName(String name);
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneyDeserializer.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneyDeserializer.java
new file mode 100644
index 00000000..f514f710
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneyDeserializer.java
@@ -0,0 +1,21 @@
+package geektime.spring.springbucks.serializer;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+
+import java.io.IOException;
+
+public class MoneyDeserializer extends StdDeserializer {
+ protected MoneyDeserializer() {
+ super(Money.class);
+ }
+
+ @Override
+ public Money deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
+ return Money.ofMinor(CurrencyUnit.of("CNY"), p.getLongValue());
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneySerializer.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneySerializer.java
new file mode 100644
index 00000000..a72996e4
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/serializer/MoneySerializer.java
@@ -0,0 +1,19 @@
+package geektime.spring.springbucks.serializer;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.joda.money.Money;
+
+import java.io.IOException;
+
+public class MoneySerializer extends StdSerializer {
+ protected MoneySerializer() {
+ super(Money.class);
+ }
+
+ @Override
+ public void serialize(Money money, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
+ jsonGenerator.writeNumber(money.getAmountMinorLong());
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/CoffeeService.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/CoffeeService.java
new file mode 100644
index 00000000..6247a191
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/CoffeeService.java
@@ -0,0 +1,36 @@
+package geektime.spring.springbucks.service;
+
+import geektime.spring.springbucks.model.Coffee;
+import geektime.spring.springbucks.repository.CoffeeRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.ReactiveRedisTemplate;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import java.time.Duration;
+
+@Service
+@Slf4j
+public class CoffeeService {
+ private static final String PREFIX = "springbucks-";
+ @Autowired
+ private CoffeeRepository coffeeRepository;
+ @Autowired
+ private ReactiveRedisTemplate redisTemplate;
+
+ public Flux initCache() {
+ return coffeeRepository.findAll()
+ .flatMap(c -> redisTemplate.opsForValue()
+ .set(PREFIX + c.getName(), c)
+ .flatMap(b -> redisTemplate.expire(PREFIX + c.getName(), Duration.ofMinutes(1)))
+ .doOnSuccess(v -> log.info("Loading and caching {}", c)));
+ }
+
+ public Mono findOneCoffee(String name) {
+ return redisTemplate.opsForValue().get(PREFIX + name)
+ .switchIfEmpty(coffeeRepository.findByName(name)
+ .doOnSuccess(s -> log.info("Loading {} from DB.", name)));
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/OrderService.java b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/OrderService.java
new file mode 100644
index 00000000..48052519
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/java/geektime/spring/springbucks/service/OrderService.java
@@ -0,0 +1,20 @@
+package geektime.spring.springbucks.service;
+
+import geektime.spring.springbucks.model.CoffeeOrder;
+import geektime.spring.springbucks.repository.CoffeeOrderRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.r2dbc.function.DatabaseClient;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Mono;
+
+@Service
+public class OrderService {
+ @Autowired
+ private CoffeeOrderRepository repository;
+ @Autowired
+ private DatabaseClient client;
+
+ public Mono create(CoffeeOrder order) {
+ return repository.save(order);
+ }
+}
diff --git a/Chapter 5/reactive-springbucks/src/main/resources/application.properties b/Chapter 5/reactive-springbucks/src/main/resources/application.properties
new file mode 100644
index 00000000..607e8234
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+spring.redis.host=localhost
+spring.redis.lettuce.pool.maxActive=5
+spring.redis.lettuce.pool.maxIdle=5
+
+#spring.datasource.initialization-mode=always
+logging.level.org.springframework.data.r2dbc=DEBUG
\ No newline at end of file
diff --git a/Chapter 5/reactive-springbucks/src/main/resources/data.sql b/Chapter 5/reactive-springbucks/src/main/resources/data.sql
new file mode 100644
index 00000000..929682f1
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/resources/data.sql
@@ -0,0 +1,5 @@
+insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
diff --git a/Chapter 5/reactive-springbucks/src/main/resources/schema.sql b/Chapter 5/reactive-springbucks/src/main/resources/schema.sql
new file mode 100644
index 00000000..6d2aab60
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/main/resources/schema.sql
@@ -0,0 +1,26 @@
+drop table t_coffee if exists;
+drop table t_order if exists;
+drop table t_order_coffee if exists;
+
+create table t_coffee (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ name varchar(255),
+ price bigint,
+ primary key (id)
+);
+
+create table t_order (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ customer varchar(255),
+ state integer not null,
+ primary key (id)
+);
+
+create table t_order_coffee (
+ coffee_order_id bigint not null,
+ items_id bigint not null
+);
diff --git a/Chapter 5/reactive-springbucks/src/test/java/geektime/spring/springbucks/ReactiveSpringbucksApplicationTests.java b/Chapter 5/reactive-springbucks/src/test/java/geektime/spring/springbucks/ReactiveSpringbucksApplicationTests.java
new file mode 100644
index 00000000..677bd205
--- /dev/null
+++ b/Chapter 5/reactive-springbucks/src/test/java/geektime/spring/springbucks/ReactiveSpringbucksApplicationTests.java
@@ -0,0 +1,16 @@
+package geektime.spring.springbucks;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ReactiveSpringbucksApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/Chapter 5/redis-demo/.gitignore b/Chapter 5/redis-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/redis-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/redis-demo/pom.xml b/Chapter 5/redis-demo/pom.xml
new file mode 100644
index 00000000..0ee8d32c
--- /dev/null
+++ b/Chapter 5/redis-demo/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+ geektime.spring.data.reactive
+ redis-demo
+ 0.0.1-SNAPSHOT
+ redis-demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis-reactive
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/Coffee.java b/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/Coffee.java
new file mode 100644
index 00000000..d9a29ed3
--- /dev/null
+++ b/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/Coffee.java
@@ -0,0 +1,16 @@
+package geektime.spring.data.reactive.redisdemo;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class Coffee {
+ private Long id;
+ private String name;
+ private Long price;
+}
diff --git a/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplication.java b/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplication.java
new file mode 100644
index 00000000..58b3a0d3
--- /dev/null
+++ b/Chapter 5/redis-demo/src/main/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplication.java
@@ -0,0 +1,75 @@
+package geektime.spring.data.reactive.redisdemo;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
+import org.springframework.data.redis.core.ReactiveHashOperations;
+import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
+import org.springframework.jdbc.core.JdbcTemplate;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SpringBootApplication
+@Slf4j
+public class RedisDemoApplication implements ApplicationRunner {
+ private static final String KEY = "COFFEE_MENU";
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+ @Autowired
+ private ReactiveStringRedisTemplate redisTemplate;
+
+ public static void main(String[] args) {
+ SpringApplication.run(RedisDemoApplication.class, args);
+ }
+
+ @Bean
+ ReactiveStringRedisTemplate reactiveRedisTemplate(ReactiveRedisConnectionFactory factory) {
+ return new ReactiveStringRedisTemplate(factory);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ ReactiveHashOperations hashOps = redisTemplate.opsForHash();
+ CountDownLatch cdl = new CountDownLatch(1);
+
+ List list = jdbcTemplate.query(
+ "select * from t_coffee", (rs, i) ->
+ Coffee.builder()
+ .id(rs.getLong("id"))
+ .name(rs.getString("name"))
+ .price(rs.getLong("price"))
+ .build()
+ );
+
+ Flux.fromIterable(list)
+ .publishOn(Schedulers.single())
+ .doOnComplete(() -> log.info("list ok"))
+ .flatMap(c -> {
+ log.info("try to put {},{}", c.getName(), c.getPrice());
+ return hashOps.put(KEY, c.getName(), c.getPrice().toString());
+ })
+ .doOnComplete(() -> log.info("set ok"))
+ .concatWith(redisTemplate.expire(KEY, Duration.ofMinutes(1)))
+ .doOnComplete(() -> log.info("expire ok"))
+ .onErrorResume(e -> {
+ log.error("exception {}", e.getMessage());
+ return Mono.just(false);
+ })
+ .subscribe(b -> log.info("Boolean: {}", b),
+ e -> log.error("Exception {}", e.getMessage()),
+ () -> cdl.countDown());
+ log.info("Waiting");
+ cdl.await();
+ }
+}
diff --git a/Chapter 5/redis-demo/src/main/resources/application.properties b/Chapter 5/redis-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..89f6450a
--- /dev/null
+++ b/Chapter 5/redis-demo/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.redis.host=localhost
diff --git a/Chapter 5/redis-demo/src/main/resources/data.sql b/Chapter 5/redis-demo/src/main/resources/data.sql
new file mode 100644
index 00000000..929682f1
--- /dev/null
+++ b/Chapter 5/redis-demo/src/main/resources/data.sql
@@ -0,0 +1,5 @@
+insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
diff --git a/Chapter 5/redis-demo/src/main/resources/schema.sql b/Chapter 5/redis-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..8a98c383
--- /dev/null
+++ b/Chapter 5/redis-demo/src/main/resources/schema.sql
@@ -0,0 +1,10 @@
+drop table t_coffee if exists;
+
+create table t_coffee (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ name varchar(255),
+ price bigint,
+ primary key (id)
+);
diff --git a/Chapter 5/redis-demo/src/test/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplicationTests.java b/Chapter 5/redis-demo/src/test/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplicationTests.java
new file mode 100644
index 00000000..ea7ece4d
--- /dev/null
+++ b/Chapter 5/redis-demo/src/test/java/geektime/spring/data/reactive/redisdemo/RedisDemoApplicationTests.java
@@ -0,0 +1,16 @@
+package geektime.spring.data.reactive.redisdemo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class RedisDemoApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/Chapter 5/simple-r2dbc-demo/.gitignore b/Chapter 5/simple-r2dbc-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/simple-r2dbc-demo/pom.xml b/Chapter 5/simple-r2dbc-demo/pom.xml
new file mode 100644
index 00000000..062aa94c
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/pom.xml
@@ -0,0 +1,82 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.3.RELEASE
+
+
+ geektime.spring.data.reactive
+ simple-r2dbc-demo
+ 0.0.1-SNAPSHOT
+ simple-r2dbc-demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+
+ org.springframework.data
+ spring-data-r2dbc
+ 1.0.0.M1
+
+
+
+ io.r2dbc
+ r2dbc-h2
+ 1.0.0.M6
+
+
+
+
+ org.joda
+ joda-money
+ 1.0.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ com.h2database
+ h2
+ runtime
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ spring-milestone
+ Spring Maven MILESTONE Repository
+ http://repo.spring.io/libs-milestone
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplication.java b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplication.java
new file mode 100644
index 00000000..11ff8e84
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplication.java
@@ -0,0 +1,84 @@
+package geektime.spring.data.reactive.r2dbc;
+
+import geektime.spring.data.reactive.r2dbc.converter.MoneyReadConverter;
+import geektime.spring.data.reactive.r2dbc.converter.MoneyWriteConverter;
+import geektime.spring.data.reactive.r2dbc.model.Coffee;
+import io.r2dbc.h2.H2ConnectionConfiguration;
+import io.r2dbc.h2.H2ConnectionFactory;
+import io.r2dbc.spi.ConnectionFactory;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.convert.CustomConversions;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
+import org.springframework.data.r2dbc.dialect.Dialect;
+import org.springframework.data.r2dbc.function.DatabaseClient;
+import org.springframework.data.r2dbc.function.convert.R2dbcCustomConversions;
+import reactor.core.scheduler.Schedulers;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+@SpringBootApplication
+@Slf4j
+public class SimpleR2dbcDemoApplication extends AbstractR2dbcConfiguration
+ implements ApplicationRunner {
+ @Autowired
+ private DatabaseClient client;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SimpleR2dbcDemoApplication.class, args);
+ }
+
+ @Bean
+ public ConnectionFactory connectionFactory() {
+ return new H2ConnectionFactory(
+ H2ConnectionConfiguration.builder()
+ .inMemory("testdb")
+ .username("sa")
+ .build());
+ }
+
+ @Bean
+ public R2dbcCustomConversions r2dbcCustomConversions() {
+ Dialect dialect = getDialect(connectionFactory());
+ CustomConversions.StoreConversions storeConversions =
+ CustomConversions.StoreConversions.of(dialect.getSimpleTypeHolder());
+ return new R2dbcCustomConversions(storeConversions,
+ Arrays.asList(new MoneyReadConverter(), new MoneyWriteConverter()));
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ CountDownLatch cdl = new CountDownLatch(2);
+
+ client.execute()
+ .sql("select * from t_coffee")
+ .as(Coffee.class)
+ .fetch()
+ .first()
+ .doFinally(s -> cdl.countDown())
+// .subscribeOn(Schedulers.elastic())
+ .subscribe(c -> log.info("Fetch execute() {}", c));
+
+ client.select()
+ .from("t_coffee")
+ .orderBy(Sort.by(Sort.Direction.DESC, "id"))
+ .page(PageRequest.of(0, 3))
+ .as(Coffee.class)
+ .fetch()
+ .all()
+ .doFinally(s -> cdl.countDown())
+// .subscribeOn(Schedulers.elastic())
+ .subscribe(c -> log.info("Fetch select() {}", c));
+
+ log.info("After Starting.");
+ cdl.await();
+ }
+}
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java
new file mode 100644
index 00000000..66d6e37f
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyReadConverter.java
@@ -0,0 +1,12 @@
+package geektime.spring.data.reactive.r2dbc.converter;
+
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyReadConverter implements Converter {
+ @Override
+ public Money convert(Long aLong) {
+ return Money.ofMinor(CurrencyUnit.of("CNY"), aLong);
+ }
+}
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java
new file mode 100644
index 00000000..2c4dcf08
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/converter/MoneyWriteConverter.java
@@ -0,0 +1,11 @@
+package geektime.spring.data.reactive.r2dbc.converter;
+
+import org.joda.money.Money;
+import org.springframework.core.convert.converter.Converter;
+
+public class MoneyWriteConverter implements Converter {
+ @Override
+ public Long convert(Money money) {
+ return money.getAmountMinorLong();
+ }
+}
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java
new file mode 100644
index 00000000..a4569753
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/java/geektime/spring/data/reactive/r2dbc/model/Coffee.java
@@ -0,0 +1,21 @@
+package geektime.spring.data.reactive.r2dbc.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.joda.money.Money;
+
+import java.util.Date;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class Coffee {
+ private Long id;
+ private String name;
+ private Money price;
+ private Date createTime;
+ private Date updateTime;
+}
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/resources/application.properties b/Chapter 5/simple-r2dbc-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/resources/data.sql b/Chapter 5/simple-r2dbc-demo/src/main/resources/data.sql
new file mode 100644
index 00000000..1a0f4890
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/resources/data.sql
@@ -0,0 +1,5 @@
+insert into t_coffee (name, price, create_time, update_time) values ('espresso', 2000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('latte', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('capuccino', 2500, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('mocha', 3000, now(), now());
+insert into t_coffee (name, price, create_time, update_time) values ('macchiato', 3000, now(), now());
\ No newline at end of file
diff --git a/Chapter 5/simple-r2dbc-demo/src/main/resources/schema.sql b/Chapter 5/simple-r2dbc-demo/src/main/resources/schema.sql
new file mode 100644
index 00000000..8a98c383
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/main/resources/schema.sql
@@ -0,0 +1,10 @@
+drop table t_coffee if exists;
+
+create table t_coffee (
+ id bigint auto_increment,
+ create_time timestamp,
+ update_time timestamp,
+ name varchar(255),
+ price bigint,
+ primary key (id)
+);
diff --git a/Chapter 5/simple-r2dbc-demo/src/test/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplicationTests.java b/Chapter 5/simple-r2dbc-demo/src/test/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplicationTests.java
new file mode 100644
index 00000000..4d20021b
--- /dev/null
+++ b/Chapter 5/simple-r2dbc-demo/src/test/java/geektime/spring/data/reactive/r2dbc/SimpleR2dbcDemoApplicationTests.java
@@ -0,0 +1,16 @@
+package geektime.spring.data.reactive.r2dbc;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SimpleR2dbcDemoApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/Chapter 5/simple-reactor-demo/.gitignore b/Chapter 5/simple-reactor-demo/.gitignore
new file mode 100644
index 00000000..c456c4a3
--- /dev/null
+++ b/Chapter 5/simple-reactor-demo/.gitignore
@@ -0,0 +1,25 @@
+/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/
diff --git a/Chapter 5/simple-reactor-demo/pom.xml b/Chapter 5/simple-reactor-demo/pom.xml
new file mode 100644
index 00000000..28224698
--- /dev/null
+++ b/Chapter 5/simple-reactor-demo/pom.xml
@@ -0,0 +1,50 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.2.RELEASE
+
+
+ geektime.spring.reactor
+ simple-reactor-demo
+ 0.0.1-SNAPSHOT
+ simple-reactor-demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+ org.projectlombok
+ lombok
+
+
+ io.projectreactor
+ reactor-core
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/Chapter 5/simple-reactor-demo/src/main/java/geektime/spring/reactor/simple/SimpleReactorDemoApplication.java b/Chapter 5/simple-reactor-demo/src/main/java/geektime/spring/reactor/simple/SimpleReactorDemoApplication.java
new file mode 100644
index 00000000..30e07264
--- /dev/null
+++ b/Chapter 5/simple-reactor-demo/src/main/java/geektime/spring/reactor/simple/SimpleReactorDemoApplication.java
@@ -0,0 +1,46 @@
+package geektime.spring.reactor.simple;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+
+@SpringBootApplication
+@Slf4j
+public class SimpleReactorDemoApplication implements ApplicationRunner {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SimpleReactorDemoApplication.class, args);
+ }
+
+ @Override
+ public void run(ApplicationArguments args) throws Exception {
+ Flux.range(1, 6)
+ .doOnRequest(n -> log.info("Request {} number", n)) // 注意顺序造成的区别
+// .publishOn(Schedulers.elastic())
+ .doOnComplete(() -> log.info("Publisher COMPLETE 1"))
+ .map(i -> {
+ log.info("Publish {}, {}", Thread.currentThread(), i);
+ return 10 / (i - 3);
+// return i;
+ })
+ .doOnComplete(() -> log.info("Publisher COMPLETE 2"))
+// .subscribeOn(Schedulers.single())
+// .onErrorResume(e -> {
+// log.error("Exception {}", e.toString());
+// return Mono.just(-1);
+// })
+// .onErrorReturn(-1)
+ .subscribe(i -> log.info("Subscribe {}: {}", Thread.currentThread(), i),
+ e -> log.error("error {}", e.toString()),
+ () -> log.info("Subscriber COMPLETE")//,
+// s -> s.request(4)
+ );
+ Thread.sleep(2000);
+ }
+}
+
diff --git a/Chapter 5/simple-reactor-demo/src/main/resources/application.properties b/Chapter 5/simple-reactor-demo/src/main/resources/application.properties
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/Chapter 5/simple-reactor-demo/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/Chapter 5/simple-reactor-demo/src/test/java/geektime/spring/reactor/simple/SimpleReactorDemoApplicationTests.java b/Chapter 5/simple-reactor-demo/src/test/java/geektime/spring/reactor/simple/SimpleReactorDemoApplicationTests.java
new file mode 100644
index 00000000..d01de30f
--- /dev/null
+++ b/Chapter 5/simple-reactor-demo/src/test/java/geektime/spring/reactor/simple/SimpleReactorDemoApplicationTests.java
@@ -0,0 +1,17 @@
+package geektime.spring.reactor.simple;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class SimpleReactorDemoApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
+
diff --git "a/PDF/\347\254\2545\347\253\240.pdf" "b/PDF/\347\254\2545\347\253\240.pdf"
new file mode 100644
index 00000000..fa7ef917
Binary files /dev/null and "b/PDF/\347\254\2545\347\253\240.pdf" differ