record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
- System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
- /*
- * 如果需要手工提交异步 consumer.commitSync();
- * 手工同步提交 consumer.commitAsync()
- */
- }
-}
-```
-
-#### 2.4 测试整合结果
-
-```java
-@Slf4j
-@RestController
-public class SendMsgController {
-
- @Autowired
- private KafKaCustomrProducer producer;
- @Autowired
- private KafkaTemplate kafkaTemplate;
-
- /***
- * 发送消息体为基本类型的消息
- */
- @GetMapping("sendSimple")
- public void sendSimple() {
- producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
- }
-}
-```
-
-
-
-## 三、关于多消费者组的测试
-
-#### 3.1 创建多分区主题
-
-```java
-/**
- * @author : heibaiying
- * @description : kafka配置类
- */
-@Configuration
-public class KafkaConfig {
-
- @Bean
- public NewTopic groupTopic() {
- // 指定主题名称,分区数量,和复制因子
- return new NewTopic(Topic.GROUP, 10, (short) 2);
- }
-
-}
-```
-
-#### 3.2 多消费者组对同一主题的监听
-
-1. 消费者1-1 监听主题的 0、1 分区
-2. 消费者1-2 监听主题的 2、3 分区
-3. 消费者1-3 监听主题的 0、1 分区
-4. 消费者2-1 监听主题的所有分区
-
-```java
-/**
- * @author : heibaiying
- * @description : kafka 消费者组
- *
- * 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
- */
-@Component
-@Slf4j
-public class KafkaGroupConsumer {
-
- // 分组1 中的消费者1
- @KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
- {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
- })
- public void consumer1_1(ConsumerRecord record) {
- System.out.println("consumer1-1 收到消息:" + record.value());
- }
-
- // 分组1 中的消费者2
- @KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
- {@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
- })
- public void consumer1_2(ConsumerRecord record) {
- System.out.println("consumer1-2 收到消息:" + record.value());
- }
-
- // 分组1 中的消费者3
- @KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
- {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
- })
- public void consumer1_3(ConsumerRecord record) {
- System.out.println("consumer1-3 收到消息:" + record.value());
- }
-
- // 分组2 中的消费者
- @KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
- public void consumer2_1(ConsumerRecord record) {
- System.err.println("consumer2-1 收到消息:" + record.value());
- }
-}
-
-```
-
-#### 3.2 发送消息时候指定主题的具体分区
-
-```java
-/***
- * 多消费者组、组中多消费者对同一主题的消费情况
- */
-@GetMapping("sendGroup")
-public void sendGroup() {
- for (int i = 0; i < 4; i++) {
- // 第二个参数指定分区,第三个参数指定消息键 分区优先
- ListenableFuture> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
- future.addCallback(new ListenableFutureCallback>() {
- @Override
- public void onFailure(Throwable throwable) {
- log.info("发送消息失败:" + throwable.getMessage());
- }
-
- @Override
- public void onSuccess(SendResult sendResult) {
- System.out.println("发送结果:" + sendResult.toString());
- }
- });
- }
-}
-```
-
-测试结果:
-
-```yaml
-# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
-发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
-发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
-发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
-发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
-# 消费者组2 接收情况
-consumer2-1 收到消息:hello group 1
-consumer2-1 收到消息:hello group 0
-consumer2-1 收到消息:hello group 2
-consumer2-1 收到消息:hello group 3
-# 消费者1-1接收情况
-consumer1-1 收到消息:hello group 1
-consumer1-1 收到消息:hello group 0
-# 消费者1-3接收情况
-consumer1-3 收到消息:hello group 1
-consumer1-3 收到消息:hello group 0
-# 消费者1-2接收情况
-consumer1-2 收到消息:hello group 3
-consumer1-2 收到消息:hello group 2
-```
-
-#### 3.4 测试结果
-
-1. 和kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
-2. 和kafka原本机制不一样的是,这里我们消费者1-1和消费1-3共同属于同一个消费者组,并且监听同样的分区,按照原本kafka的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照spring的声明方式实现的消息监听,这里被两个消费者都监听到了。
-
-
-
-## 四、序列化与反序列化
-
-用例采用的是第三方fastjson将实体类序列化为json后发送。实现如下:
-
-```java
-/***
- * 发送消息体为bean的消息
- */
-@GetMapping("sendBean")
-public void sendBean() {
- Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
- producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
-}
-
-```
-
-```java
-@Component
-@Slf4j
-public class KafkaBeanConsumer {
-
- @KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
- public void consumer(ConsumerRecord record) {
- System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
- }
-}
-```
-
+## 一、kafka的相关概念:
+
+### 1.主题和分区
+
+kafka 的消息通过主题进行分类。一个主题可以被分为若干个分区,一个分区就是一个提交日志。消息以追加的方式写入分区,然后以先入先出的顺序读取。kafka 通过分区来实现数据的冗余和伸缩性,分区可以分布在不同的服务器上,也就是说一个主题可以横跨多个服务器,以此来提供比单个服务器更强大的性能(类比 HDFS 分布式文件系统)。
+
+注意:由于一个主题包含多个分区,因此无法在整个主题范围内保证消息的顺序性,**但可以保证消息在单个分区内的顺序性**。
+
+
+
+### 2.分区复制
+
+每个主题被分为若干个分区,每个分区有多个副本。那些副本被保存在 broker 上,每个 broker 可以保存成百上千个属于不同主题和分区的副本。副本有以下两种类型 :
+
+- 首领副本 每个分区都有一个首领副本 。 为了保证一致性,所有生产者请求和消费者请求都会经过这个副本。
+- 跟随者副本 首领以外的副本都是跟随者副本。跟随者副本不处理来自客户端的请求,它们唯一的任务就是从首领那里复制消息,保持与首领一致的状态。如果首领发生崩渍,其中的一个跟随者会被提升为新首领。
+
+### 3. 生产者
+
+- 默认情况下生产者在把消息均衡地分布到在主题的所有分区上,而并不关心特定消息会被写到那个分区;
+- 如果指定消息键,则通过对消息键的散列来实现分区;
+- 也可以通过消息键和分区器来实现把消息直接写到指定的分区,这个需要自定义分区器,需要实现 Partitioner 接口,并重写其中的 partition 方法。
+
+### 4. 消费者
+
+消费者是**消费者群组**的一部分。也就是说,会有一个或者多个消费者共同读取一个主题,群组保证每个分区只能被一个消费者使用。
+
+**一个分区只能被同一个消费者群组里面的一个消费者读取,但可以被不同消费者群组里面的多个消费者读取。多个消费者群组可以共同读取同一个主题,彼此之间互不影响**。
+
+
+
+### 5.broker和集群
+
+一个独立的 kafka 服务器被称为 broker。broker 接收来自生产者的消息,为消息设置偏移量,并提交消息到磁盘保存。broker 为消费者提供服务,对读取分区的请求做出响应,返回已经提交到磁盘的消息。
+
+broker 是集群的组成部分。每一个集群都有一个 broker 同时充当了集群控制器的角色(自动从集群的活跃成员中选举出来)。控制器负责管理工作,包括将分区分配给 broker 和监控 broker。**在集群中,一个分区从属一个 broker,该 broker 被称为分区的首领**。一个分区可以分配给多个 broker,这个时候会发生分区复制。这种复制机制为分区提供了消息冗余,如果有一个 broker 失效,其他 broker 可以接管领导权。
+
+
+
+更多 kafka 的说明可以参考我的个人笔记:[《Kafka 权威指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/Kafka/%E3%80%8AKafka%E6%9D%83%E5%A8%81%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md#53-%E5%A4%8D%E5%88%B6)
+
+
+
+## 二、项目说明
+
+#### 1.1 项目结构说明
+
+ 本项目提供 kafka 发送简单消息、对象消息、和多消费者组消费消息三种情况下的 sample。
+
+1. kafkaSimpleConsumer 用于普通消息的监听;
+2. kafkaBeanConsumer 用于对象消息监听;
+3. kafkaGroupConsumer 用于多消费者组和多消费者对主题分区消息监听的情况。
+
+
+
+
+
+#### 1.2 主要依赖
+
+```xml
+
+ org.springframework.kafka
+ spring-kafka
+
+
+ org.springframework.kafka
+ spring-kafka-test
+ test
+
+```
+
+
+
+## 二、 整合 kafka
+
+#### 2.1 kafka基本配置
+
+```yaml
+spring:
+ kafka:
+ # 以逗号分隔的地址列表,用于建立与 Kafka 集群的初始连接 (kafka 默认的端口号为 9092)
+ bootstrap-servers: 127.0.0.1:9092
+ producer:
+ # 发生错误后,消息重发的次数。
+ retries: 0
+ #当有多个消息需要被发送到同一个分区时,生产者会把它们放在同一个批次里。该参数指定了一个批次可以使用的内存大小,按照字节数计算。
+ batch-size: 16384
+ # 设置生产者内存缓冲区的大小。
+ buffer-memory: 33554432
+ # 键的序列化方式
+ key-serializer: org.apache.kafka.common.serialization.StringSerializer
+ # 值的序列化方式
+ value-serializer: org.apache.kafka.common.serialization.StringSerializer
+ # acks=0 : 生产者在成功写入消息之前不会等待任何来自服务器的响应。
+ # acks=1 : 只要集群的首领节点收到消息,生产者就会收到一个来自服务器成功响应。
+ # acks=all :只有当所有参与复制的节点全部收到消息时,生产者才会收到一个来自服务器的成功响应。
+ acks: 1
+ consumer:
+ # 自动提交的时间间隔 在 spring boot 2.X 版本中这里采用的是值的类型为 Duration 需要符合特定的格式,如 1S,1M,2H,5D
+ auto-commit-interval: 1S
+ # 该属性指定了消费者在读取一个没有偏移量的分区或者偏移量无效的情况下该作何处理:
+ # latest(默认值)在偏移量无效的情况下,消费者将从最新的记录开始读取数据(在消费者启动之后生成的记录)
+ # earliest :在偏移量无效的情况下,消费者将从起始位置读取分区的记录
+ auto-offset-reset: earliest
+ # 是否自动提交偏移量,默认值是 true,为了避免出现重复数据和数据丢失,可以把它设置为 false,然后手动提交偏移量
+ enable-auto-commit: true
+ # 键的反序列化方式
+ key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ # 值的反序列化方式
+ value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
+ listener:
+ # 在侦听器容器中运行的线程数。
+ concurrency: 5
+
+```
+
+这里需要说明的是:
+
+ 在 spring boot 2.X 版本 auto-commit-interval(自动提交的时间间隔)采用的是值的类型为 Duration ,Duration 是 jdk 1.8 版本之后引入的类,在其源码中我们可以看到对于其字符串的表达需要符合一定的规范,即数字 + 单位,如下的写法 1s ,1.5s, 0s, 0.001S ,1h, 2d 在 yaml 中都是有效的。如果传入无效的字符串,则 spring boot 在启动阶段解析配置文件的时候就会抛出异常。
+
+```java
+public final class Duration
+ implements TemporalAmount, Comparable, Serializable {
+
+ /**
+ * The pattern for parsing.
+ */
+ private static final Pattern PATTERN =
+ Pattern.compile("([-+]?)P(?:([-+]?[0-9]+)D)?" +
+ "(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?", Pattern.CASE_INSENSITIVE);
+
+ ........
+
+}
+```
+
+#### 2.2 KafkaTemplate实现消息发送
+
+```java
+@Component
+@Slf4j
+public class KafKaCustomrProducer {
+
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+
+ public void sendMessage(String topic, Object object) {
+
+ /*
+ * 这里的 ListenableFuture 类是 spring 对 java 原生 Future 的扩展增强,是一个泛型接口,用于监听异步方法的回调
+ * 而对于 kafka send 方法返回值而言,这里的泛型所代表的实际类型就是 SendResult,而这里 K,V 的泛型实际上
+ * 被用于 ProducerRecord producerRecord,即生产者发送消息的 key,value 类型
+ */
+ ListenableFuture> future = kafkaTemplate.send(topic, object);
+
+ future.addCallback(new ListenableFutureCallback>() {
+ @Override
+ public void onFailure(Throwable throwable) {
+ log.info("发送消息失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public void onSuccess(SendResult sendResult) {
+ System.out.println("发送结果:" + sendResult.toString());
+ }
+ });
+ }
+}
+
+```
+
+#### 2.3 @KafkaListener注解实现消息的监听
+
+```java
+@Component
+@Slf4j
+public class KafkaSimpleConsumer {
+
+ // 简单消费者
+ @KafkaListener(groupId = "simpleGroup", topics = Topic.SIMPLE)
+ public void consumer1_1(ConsumerRecord record, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic, Consumer consumer) {
+ System.out.println("消费者收到消息:" + record.value() + "; topic:" + topic);
+ /*
+ * 如果需要手工提交异步 consumer.commitSync();
+ * 手工同步提交 consumer.commitAsync()
+ */
+ }
+}
+```
+
+#### 2.4 测试整合结果
+
+```java
+@Slf4j
+@RestController
+public class SendMsgController {
+
+ @Autowired
+ private KafKaCustomrProducer producer;
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+
+ /***
+ * 发送消息体为基本类型的消息
+ */
+ @GetMapping("sendSimple")
+ public void sendSimple() {
+ producer.sendMessage(Topic.SIMPLE, "hello spring boot kafka");
+ }
+}
+```
+
+
+
+## 三、关于多消费者组的测试
+
+#### 3.1 创建多分区主题
+
+```java
+/**
+ * @author : heibaiying
+ * @description : kafka 配置类
+ */
+@Configuration
+public class KafkaConfig {
+
+ @Bean
+ public NewTopic groupTopic() {
+ // 指定主题名称,分区数量,和复制因子
+ return new NewTopic(Topic.GROUP, 10, (short) 2);
+ }
+
+}
+```
+
+#### 3.2 多消费者组对同一主题的监听
+
+1. 消费者 1-1 监听主题的 0、1 分区
+2. 消费者 1-2 监听主题的 2、3 分区
+3. 消费者 1-3 监听主题的 0、1 分区
+4. 消费者 2-1 监听主题的所有分区
+
+```java
+/**
+ * @author : heibaiying
+ * @description : kafka 消费者组
+ *
+ * 多个消费者群组可以共同读取同一个主题,彼此之间互不影响。
+ */
+@Component
+@Slf4j
+public class KafkaGroupConsumer {
+
+ // 分组 1 中的消费者 1
+ @KafkaListener(id = "consumer1-1", groupId = "group1", topicPartitions =
+ {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
+ })
+ public void consumer1_1(ConsumerRecord record) {
+ System.out.println("consumer1-1 收到消息:" + record.value());
+ }
+
+ // 分组 1 中的消费者 2
+ @KafkaListener(id = "consumer1-2", groupId = "group1", topicPartitions =
+ {@TopicPartition(topic = Topic.GROUP, partitions = {"2", "3"})
+ })
+ public void consumer1_2(ConsumerRecord record) {
+ System.out.println("consumer1-2 收到消息:" + record.value());
+ }
+
+ // 分组 1 中的消费者 3
+ @KafkaListener(id = "consumer1-3", groupId = "group1", topicPartitions =
+ {@TopicPartition(topic = Topic.GROUP, partitions = {"0", "1"})
+ })
+ public void consumer1_3(ConsumerRecord record) {
+ System.out.println("consumer1-3 收到消息:" + record.value());
+ }
+
+ // 分组 2 中的消费者
+ @KafkaListener(id = "consumer2-1", groupId = "group2", topics = Topic.GROUP)
+ public void consumer2_1(ConsumerRecord record) {
+ System.err.println("consumer2-1 收到消息:" + record.value());
+ }
+}
+
+```
+
+#### 3.2 发送消息时候指定主题的具体分区
+
+```java
+/***
+ * 多消费者组、组中多消费者对同一主题的消费情况
+ */
+@GetMapping("sendGroup")
+public void sendGroup() {
+ for (int i = 0; i < 4; i++) {
+ // 第二个参数指定分区,第三个参数指定消息键 分区优先
+ ListenableFuture> future = kafkaTemplate.send(Topic.GROUP, i % 4, "key", "hello group " + i);
+ future.addCallback(new ListenableFutureCallback>() {
+ @Override
+ public void onFailure(Throwable throwable) {
+ log.info("发送消息失败:" + throwable.getMessage());
+ }
+
+ @Override
+ public void onSuccess(SendResult sendResult) {
+ System.out.println("发送结果:" + sendResult.toString());
+ }
+ });
+ }
+}
+```
+
+测试结果:
+
+```yaml
+# 主要看每次发送结果中的 partition 属性,代表四次消息分别发送到了主题的0,1,2,3分区
+发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=1, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 1, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-1@13]
+发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=0, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 0, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-0@19]
+发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=3, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 3, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-3@13]
+发送结果:SendResult [producerRecord=ProducerRecord(topic=spring.boot.kafka.newGroup, partition=2, headers=RecordHeaders(headers = [], isReadOnly = true), key=key, value=hello group 2, timestamp=null), recordMetadata=spring.boot.kafka.newGroup-2@13]
+# 消费者组2 接收情况
+consumer2-1 收到消息:hello group 1
+consumer2-1 收到消息:hello group 0
+consumer2-1 收到消息:hello group 2
+consumer2-1 收到消息:hello group 3
+# 消费者1-1接收情况
+consumer1-1 收到消息:hello group 1
+consumer1-1 收到消息:hello group 0
+# 消费者1-3接收情况
+consumer1-3 收到消息:hello group 1
+consumer1-3 收到消息:hello group 0
+# 消费者1-2接收情况
+consumer1-2 收到消息:hello group 3
+consumer1-2 收到消息:hello group 2
+```
+
+#### 3.4 测试结果
+
+1. 和 kafka 原本的机制一样,多消费者组之间对于同一个主题的消费彼此之间互不影响;
+2. 和 kafka 原本机制不一样的是,这里我们消费者 1-1 和消费 1-3 共同属于同一个消费者组,并且监听同样的分区,按照原本 kafka 的机制,群组保证每个分区只能被同一个消费者组的一个消费者使用,但是按照 spring 的声明方式实现的消息监听,这里被两个消费者都监听到了。
+
+
+
+## 四、序列化与反序列化
+
+用例采用的是第三方 fastjson 将实体类序列化为 json 后发送。实现如下:
+
+```java
+/***
+ * 发送消息体为 bean 的消息
+ */
+@GetMapping("sendBean")
+public void sendBean() {
+ Programmer programmer = new Programmer("xiaoming", 12, 21212.33f, new Date());
+ producer.sendMessage(Topic.BEAN, JSON.toJSON(programmer).toString());
+}
+
+```
+
+```java
+@Component
+@Slf4j
+public class KafkaBeanConsumer {
+
+ @KafkaListener(groupId = "beanGroup",topics = Topic.BEAN)
+ public void consumer(ConsumerRecord record) {
+ System.out.println("消费者收到消息:" + JSON.parseObject(record.value().toString(), Programmer.class));
+ }
+}
+```
+
diff --git a/spring-boot/spring-boot-memcached/README.md b/spring-boot/spring-boot-memcached/README.md
index 99579df..1996007 100644
--- a/spring-boot/spring-boot-memcached/README.md
+++ b/spring-boot/spring-boot-memcached/README.md
@@ -1,5 +1,6 @@
-# spring boot 整合 mecached
-
## 目录
+# spring boot 整合 mecached
+
+## 目录
一、说明
1.1 XMemcached客户端说明
1.2 项目结构说明
@@ -15,149 +16,149 @@
-## 一、说明
-
-### 1.1 XMemcached客户端说明
-
-spring boot 官方并没有提供关于 memcached 的starter,所以我们这里还是采用XMemcached作为客户端进行整合。 XMemcached是基于java nio的memcached高性能客户端,支持完整的memcached协议,支持客户端分布并且提供了一致性哈希(consistent hash)算法的实现。
-
-### 1.2 项目结构说明
-
-memcached的整合配置位于config文件夹下。
-
-
-
-### 1.3 主要依赖
-
-```xml
-
-
- com.googlecode.xmemcached
- xmemcached
- 2.4.5
-
-```
-
-
-
-## 二、spring boot 整合 memcached
-
-#### 2.1 单机配置
-
-```java
-@Bean
-public MemcachedClient memcachedClient() {
- XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
- MemcachedClient memcachedClient = null;
-try {
- memcachedClient = builder.build();
- } catch (IOException e) {
- e.printStackTrace();
-}
- return memcachedClient;
-}
-```
-
-#### 2.2 集群配置
-
-```java
-@Bean
-public MemcachedClient memcachedClientForCluster() {
-
- List addressList = new ArrayList();
- addressList.add(new InetSocketAddress("192.168.200.201", 11211));
- addressList.add(new InetSocketAddress("192.168.200.201", 11212));
- // 赋予权重
- int[] weights = {1, 2};
- XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
- // 设置连接池大小
- builder.setConnectionPoolSize(10);
- // 协议工厂
- builder.setCommandFactory(new TextCommandFactory());
- // 分布策略,一致性哈希KetamaMemcachedSessionLocator或者ArraySessionLocator(默认)
- builder.setSessionLocator(new KetamaMemcachedSessionLocator());
- // 设置序列化器
- builder.setTranscoder(new SerializingTranscoder());
- MemcachedClient memcachedClient = null;
- try {
- memcachedClient = builder.build();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return memcachedClient;
-}
-```
-
-#### 2.3 存储基本类型测试用例
-
-xmemcached单机版本和集群版本注入的实例是相同的。
-
-```java
-/**
- * @author : heibaiying
- * @description : Memcached 操作基本对象
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MemSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
- String value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- memcachedClient.delete("hello");
- value = memcachedClient.get("hello");
- System.out.println("hello=" + value);
- }
-}
-```
-
-#### 2.5 存储实体对象测试用例
-
-```java
-/**
- * @author : heibaiying
- * @description :Memcached 序列化与反序列化
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MemObjectSamples {
-
- @Autowired
- private MemcachedClient memcachedClient;
-
- @Test
- public void operate() throws InterruptedException, MemcachedException, TimeoutException {
- memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
- Programmer programmer = memcachedClient.get("programmer");
- System.out.println("hello ," + programmer.getName());
- memcachedClient.delete("programmer");
- programmer = memcachedClient.get("programmer");
- Assert.assertNull(programmer);
- }
-}
-
-```
-
-
-
-## 附:memcached 基本命令
-
-| 命令 | 格式 | 说明 |
-| --------------- | -------------------------------------------------- | ------------------------------------- |
-| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
-| 新增 add | add key flags exTime length -> value | 只有当key不存在的情况下,才可以插入 |
-| 替换 replace | replace key flags exTime length -> value | 只修改已存在key的value值 |
-| 追加内容append | append key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 前面追加prepend | prepend key flags exTime length -> value | length表示追加的长度而不是总长度 |
-| 查询操作 get | get key | |
-| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
-| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
-| 删除 delete | delete key | 将数据打一个删除标记 |
-| 自增 incr | incr key 增加偏移量 | incr和decr只能操作能转换为数字的Value |
-| 自减 decr | decr key 减少偏移量 | desr不能将数字减少至0以下 |
-| 清库 | flush_all | |
\ No newline at end of file
+## 一、说明
+
+### 1.1 XMemcached客户端说明
+
+spring boot 官方并没有提供关于 memcached 的 starter,所以我们这里还是采用 XMemcached 作为客户端进行整合。 XMemcached 是基于 java nio 的 memcached 高性能客户端,支持完整的 memcached 协议,支持客户端分布并且提供了一致性哈希 (consistent hash) 算法的实现。
+
+### 1.2 项目结构说明
+
+memcached 的整合配置位于 config 文件夹下。
+
+
+
+### 1.3 主要依赖
+
+```xml
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.5
+
+```
+
+
+
+## 二、spring boot 整合 memcached
+
+#### 2.1 单机配置
+
+```java
+@Bean
+public MemcachedClient memcachedClient() {
+ XMemcachedClientBuilder builder = new XMemcachedClientBuilder("192.168.200.201:11211");
+ MemcachedClient memcachedClient = null;
+try {
+ memcachedClient = builder.build();
+ } catch (IOException e) {
+ e.printStackTrace();
+}
+ return memcachedClient;
+}
+```
+
+#### 2.2 集群配置
+
+```java
+@Bean
+public MemcachedClient memcachedClientForCluster() {
+
+ List addressList = new ArrayList();
+ addressList.add(new InetSocketAddress("192.168.200.201", 11211));
+ addressList.add(new InetSocketAddress("192.168.200.201", 11212));
+ // 赋予权重
+ int[] weights = {1, 2};
+ XMemcachedClientBuilder builder = new XMemcachedClientBuilder(addressList, weights);
+ // 设置连接池大小
+ builder.setConnectionPoolSize(10);
+ // 协议工厂
+ builder.setCommandFactory(new TextCommandFactory());
+ // 分布策略,一致性哈希 KetamaMemcachedSessionLocator 或者 ArraySessionLocator(默认)
+ builder.setSessionLocator(new KetamaMemcachedSessionLocator());
+ // 设置序列化器
+ builder.setTranscoder(new SerializingTranscoder());
+ MemcachedClient memcachedClient = null;
+ try {
+ memcachedClient = builder.build();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return memcachedClient;
+}
+```
+
+#### 2.3 存储基本类型测试用例
+
+xmemcached 单机版本和集群版本注入的实例是相同的。
+
+```java
+/**
+ * @author : heibaiying
+ * @description : Memcached 操作基本对象
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MemSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("hello", 0, "Hello,cluster xmemcached");
+ String value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ memcachedClient.delete("hello");
+ value = memcachedClient.get("hello");
+ System.out.println("hello=" + value);
+ }
+}
+```
+
+#### 2.5 存储实体对象测试用例
+
+```java
+/**
+ * @author : heibaiying
+ * @description :Memcached 序列化与反序列化
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MemObjectSamples {
+
+ @Autowired
+ private MemcachedClient memcachedClient;
+
+ @Test
+ public void operate() throws InterruptedException, MemcachedException, TimeoutException {
+ memcachedClient.set("programmer", 0, new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ Programmer programmer = memcachedClient.get("programmer");
+ System.out.println("hello ," + programmer.getName());
+ memcachedClient.delete("programmer");
+ programmer = memcachedClient.get("programmer");
+ Assert.assertNull(programmer);
+ }
+}
+
+```
+
+
+
+## 附:memcached 基本命令
+
+| 命令 | 格式 | 说明 |
+| --------------- | -------------------------------------------------- | ------------------------------------- |
+| 新增 set | set key flags exTime length -> value | 无论什么情况,都可以插入 |
+| 新增 add | add key flags exTime length -> value | 只有当 key 不存在的情况下,才可以插入 |
+| 替换 replace | replace key flags exTime length -> value | 只修改已存在 key 的 value 值 |
+| 追加内容 append | append key flags exTime length -> value | length 表示追加的长度而不是总长度 |
+| 前面追加 prepend | prepend key flags exTime length -> value | length 表示追加的长度而不是总长度 |
+| 查询操作 get | get key | |
+| 检查更新 cas | cas key flags exTime length version -> value | 版本正确才更新 |
+| 详细获取 gets | gets key | 返回的最后一个数代表 key 的 CAS 令牌 |
+| 删除 delete | delete key | 将数据打一个删除标记 |
+| 自增 incr | incr key 增加偏移量 | incr 和 decr 只能操作能转换为数字的 Value |
+| 自减 decr | decr key 减少偏移量 | desr 不能将数字减少至 0 以下 |
+| 清库 | flush_all | |
diff --git a/spring-boot/spring-boot-mongodb/README.md b/spring-boot/spring-boot-mongodb/README.md
index 7344b5b..fc66938 100644
--- a/spring-boot/spring-boot-mongodb/README.md
+++ b/spring-boot/spring-boot-mongodb/README.md
@@ -1,5 +1,6 @@
-# spring boot 整合 mongodb
-
## 目录
+# spring boot 整合 mongodb
+
+## 目录
一、说明
1.1 用例结构
1.2 项目主要依赖
@@ -12,183 +13,183 @@
-## 一、说明
-
-#### 1.1 用例结构
-
-1. 本用例提供mongdb的简单整合用例;
-2. 提供用MongoTemplate的方式操作mongdb,见测试用例MongoOriginalTests.java
-3. 提供基于spring data jpa 的方式操作mongodb(推荐),见测试用例MongoJPATests.java
-
-
-
-#### 1.2 项目主要依赖
-
-```xml
-
- org.springframework.boot
- spring-boot-starter-data-mongodb
-
-```
-
-
-
-## 二、整合 mongodb
-
-#### 2.1 在application.yml 中配置mongodb数据源
-
-```yaml
-spring:
- data:
- mongodb:
- database: spring
- uri: mongodb://192.168.0.108:27017
-```
-
-#### 2.2 基于MongoTemplate实现对mongodb的操作
-
-```java
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MongoOriginalTests {
-
- @Autowired
- private MongoTemplate mongoTemplate;
-
- @Test
- public void insert() {
- // 单条插入
- mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
- List programmers = new ArrayList();
- // 批量插入
- programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
- programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
- mongoTemplate.insert(programmers, Programmer.class);
- }
-
- // 条件查询
- @Test
- public void select() {
- Criteria criteria = new Criteria();
- criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
- Query query = new Query(criteria);
- Programmer one = mongoTemplate.findOne(query, Programmer.class);
- System.out.println(one);
- }
-
-
- // 更新数据
- @Test
- public void MUpdate() {
- UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
- System.out.println("更新记录数:" + updateResult.getModifiedCount());
- }
-
- // 删除指定数据
- @Test
- public void delete() {
- DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
- System.out.println("影响记录数:" + result.getDeletedCount());
- System.out.println("是否成功:" + result.wasAcknowledged());
- }
-
-}
-```
-
-#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
-
-1.新建查询结构,查询方法按照支持的关键字命名
-
-```java
-public interface ProgrammerRepository extends MongoRepository {
-
- void deleteAllByName(String name);
-
- Programmer findAllByName(String names);
-
- Programmer findByNameAndAge(String name, int age);
-
-}
-```
-
-2.测试
-
-```java
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MongoJPATests {
-
- @Autowired
- private ProgrammerRepository repository;
-
- @Test
- public void insert() {
- // 单条插入
- repository.save(new Programmer("python", 23, 21832.34f, new Date()));
- // 批量插入
- List programmers = new ArrayList();
- programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
- programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
- repository.saveAll(programmers);
- }
-
- // 条件查询
- @Test
- public void select() {
- Programmer java = repository.findByNameAndAge("java", 21);
- Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
- }
-
-
- // 更新数据
- @Test
- public void MUpdate() {
- repository.save(new Programmer("Go", 8, 500.21f, new Date()));
- Programmer go = repository.findAllByName("Go");
- Assert.assertEquals(go.getAge(), 8);
- }
-
- // 删除指定数据
- @Test
- public void delete() {
- repository.deleteAllByName("python");
- Optional python = repository.findById("python");
- Assert.assertFalse(python.isPresent());
- }
-
-}
-```
-
-查询方法支持的关键字如下,更多命名规范可以参见Spring Data MongoDB官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
-
-| Keyword | Sample | Logical result |
-| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
-| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
-| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
-| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
-| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
-| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
-| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
-| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
-| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
-| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
-| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
-| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
-| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
-| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
-| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
-| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
-| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
-| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
-| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
-| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
-| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
-| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
-| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
-| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
-| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
-| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
-| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
-| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
-| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |
-
+## 一、说明
+
+#### 1.1 用例结构
+
+1. 本用例提供 mongdb 的简单整合用例;
+2. 提供用 MongoTemplate 的方式操作 mongdb,见测试用例 MongoOriginalTests.java
+3. 提供基于 spring data jpa 的方式操作 mongodb(推荐),见测试用例 MongoJPATests.java
+
+
+
+#### 1.2 项目主要依赖
+
+```xml
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+```
+
+
+
+## 二、整合 mongodb
+
+#### 2.1 在application.yml 中配置mongodb数据源
+
+```yaml
+spring:
+ data:
+ mongodb:
+ database: spring
+ uri: mongodb://192.168.0.108:27017
+```
+
+#### 2.2 基于MongoTemplate实现对mongodb的操作
+
+```java
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MongoOriginalTests {
+
+ @Autowired
+ private MongoTemplate mongoTemplate;
+
+ @Test
+ public void insert() {
+ // 单条插入
+ mongoTemplate.insert(new Programmer("xiaoming", 12, 5000.21f, new Date()));
+ List programmers = new ArrayList();
+ // 批量插入
+ programmers.add(new Programmer("xiaohong", 21, 52200.21f, new Date()));
+ programmers.add(new Programmer("xiaolan", 34, 500.21f, new Date()));
+ mongoTemplate.insert(programmers, Programmer.class);
+ }
+
+ // 条件查询
+ @Test
+ public void select() {
+ Criteria criteria = new Criteria();
+ criteria.andOperator(where("name").is("xiaohong"), where("age").is(21));
+ Query query = new Query(criteria);
+ Programmer one = mongoTemplate.findOne(query, Programmer.class);
+ System.out.println(one);
+ }
+
+
+ // 更新数据
+ @Test
+ public void MUpdate() {
+ UpdateResult updateResult = mongoTemplate.updateMulti(query(where("name").is("xiaoming")), update("age", 35), Programmer.class);
+ System.out.println("更新记录数:" + updateResult.getModifiedCount());
+ }
+
+ // 删除指定数据
+ @Test
+ public void delete() {
+ DeleteResult result = mongoTemplate.remove(query(where("name").is("xiaolan")), Programmer.class);
+ System.out.println("影响记录数:" + result.getDeletedCount());
+ System.out.println("是否成功:" + result.wasAcknowledged());
+ }
+
+}
+```
+
+#### 2.3 使用 data jpa 方式操作mongodb (推荐使用)
+
+1.新建查询结构,查询方法按照支持的关键字命名
+
+```java
+public interface ProgrammerRepository extends MongoRepository {
+
+ void deleteAllByName(String name);
+
+ Programmer findAllByName(String names);
+
+ Programmer findByNameAndAge(String name, int age);
+
+}
+```
+
+2.测试
+
+```java
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MongoJPATests {
+
+ @Autowired
+ private ProgrammerRepository repository;
+
+ @Test
+ public void insert() {
+ // 单条插入
+ repository.save(new Programmer("python", 23, 21832.34f, new Date()));
+ // 批量插入
+ List programmers = new ArrayList();
+ programmers.add(new Programmer("java", 21, 52200.21f, new Date()));
+ programmers.add(new Programmer("Go", 34, 500.21f, new Date()));
+ repository.saveAll(programmers);
+ }
+
+ // 条件查询
+ @Test
+ public void select() {
+ Programmer java = repository.findByNameAndAge("java", 21);
+ Assert.assertEquals(java.getSalary(), 52200.21f, 0.01);
+ }
+
+
+ // 更新数据
+ @Test
+ public void MUpdate() {
+ repository.save(new Programmer("Go", 8, 500.21f, new Date()));
+ Programmer go = repository.findAllByName("Go");
+ Assert.assertEquals(go.getAge(), 8);
+ }
+
+ // 删除指定数据
+ @Test
+ public void delete() {
+ repository.deleteAllByName("python");
+ Optional python = repository.findById("python");
+ Assert.assertFalse(python.isPresent());
+ }
+
+}
+```
+
+查询方法支持的关键字如下,更多命名规范可以参见 Spring Data MongoDB 官方文档[Query Methods](https://docs.spring.io/spring-data/mongodb/docs/2.1.3.RELEASE/reference/html/#mongodb.repositories.queries):
+
+| Keyword | Sample | Logical result |
+| ------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `After` | `findByBirthdateAfter(Date date)` | `{"birthdate" : {"$gt" : date}}` |
+| `GreaterThan` | `findByAgeGreaterThan(int age)` | `{"age" : {"$gt" : age}}` |
+| `GreaterThanEqual` | `findByAgeGreaterThanEqual(int age)` | `{"age" : {"$gte" : age}}` |
+| `Before` | `findByBirthdateBefore(Date date)` | `{"birthdate" : {"$lt" : date}}` |
+| `LessThan` | `findByAgeLessThan(int age)` | `{"age" : {"$lt" : age}}` |
+| `LessThanEqual` | `findByAgeLessThanEqual(int age)` | `{"age" : {"$lte" : age}}` |
+| `Between` | `findByAgeBetween(int from, int to)` | `{"age" : {"$gt" : from, "$lt" : to}}` |
+| `In` | `findByAgeIn(Collection ages)` | `{"age" : {"$in" : [ages…]}}` |
+| `NotIn` | `findByAgeNotIn(Collection ages)` | `{"age" : {"$nin" : [ages…]}}` |
+| `IsNotNull`, `NotNull` | `findByFirstnameNotNull()` | `{"firstname" : {"$ne" : null}}` |
+| `IsNull`, `Null` | `findByFirstnameNull()` | `{"firstname" : null}` |
+| `Like`, `StartingWith`, `EndingWith` | `findByFirstnameLike(String name)` | `{"firstname" : name} (name as regex)` |
+| `NotLike`, `IsNotLike` | `findByFirstnameNotLike(String name)` | `{"firstname" : { "$not" : name }} (name as regex)` |
+| `Containing` on String | `findByFirstnameContaining(String name)` | `{"firstname" : name} (name as regex)` |
+| `NotContaining` on String | `findByFirstnameNotContaining(String name)` | `{"firstname" : { "$not" : name}} (name as regex)` |
+| `Containing` on Collection | `findByAddressesContaining(Address address)` | `{"addresses" : { "$in" : address}}` |
+| `NotContaining` on Collection | `findByAddressesNotContaining(Address address)` | `{"addresses" : { "$not" : { "$in" : address}}}` |
+| `Regex` | `findByFirstnameRegex(String firstname)` | `{"firstname" : {"$regex" : firstname }}` |
+| `(No keyword)` | `findByFirstname(String name)` | `{"firstname" : name}` |
+| `Not` | `findByFirstnameNot(String name)` | `{"firstname" : {"$ne" : name}}` |
+| `Near` | `findByLocationNear(Point point)` | `{"location" : {"$near" : [x,y]}}` |
+| `Near` | `findByLocationNear(Point point, Distance max)` | `{"location" : {"$near" : [x,y], "$maxDistance" : max}}` |
+| `Near` | `findByLocationNear(Point point, Distance min, Distance max)` | `{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}` |
+| `Within` | `findByLocationWithin(Circle circle)` | `{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}` |
+| `Within` | `findByLocationWithin(Box box)` | `{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}` |
+| `IsTrue`, `True` | `findByActiveIsTrue()` | `{"active" : true}` |
+| `IsFalse`, `False` | `findByActiveIsFalse()` | `{"active" : false}` |
+| `Exists` | `findByLocationExists(boolean exists)` | `{"location" : {"$exists" : exists }}` |
+
diff --git a/spring-boot/spring-boot-mybatis/README.md b/spring-boot/spring-boot-mybatis/README.md
index 5fba829..1126da4 100644
--- a/spring-boot/spring-boot-mybatis/README.md
+++ b/spring-boot/spring-boot-mybatis/README.md
@@ -1,5 +1,6 @@
-# spring boot 整合 mybatis
-
## 目录
+# spring boot 整合 mybatis
+
+## 目录
一、说明
1.1 项目结构
1.2 项目主要依赖
@@ -12,250 +13,250 @@
-## 一、说明
-
-#### 1.1 项目结构
-
-1. 项目查询用的表对应的建表语句放置在resources的sql文件夹下;
-
-2. 关于mybatis sql的写法提供两种方式:
-
- xml 写法:对应的类为ProgrammerMapper.java 和 programmerMapper.xml,用MybatisXmlTest进行测试;
-
- 注解写法:对应的类为Programmer.java ,用MybatisAnnotationTest进行测试。
-
-
-
-#### 1.2 项目主要依赖
-
-需要说明的是按照spring 官方对应自定义的starter 命名规范的推荐:
-
-- 官方的starter命名:spring-boot-starter-XXXX
-- 其他第三方starter命名:XXXX-spring-boot-starte
-
-所以mybatis的starter命名为mybatis-spring-boot-starter,如果有自定义starter需求,也需要按照此命名规则进行命名。
-
-```xml
-
-
- org.mybatis.spring.boot
- mybatis-spring-boot-starter
- 1.3.2
-
-
-
- mysql
- mysql-connector-java
- 8.0.13
-
-
- org.projectlombok
- lombok
- true
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-```
-
-spring boot 与 mybatis 版本的对应关系:
-
-| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
-| --------------------------- | ------------------------------------------------------------ | ------------- |
-| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
-| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
-| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
-| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
-
-## 二、整合 mybatis
-
-#### 2.1 在application.yml 中配置数据源
-
-spring boot 2.x 版本默认采用Hikari作为数据库连接池,Hikari是目前java平台性能最好的连接池,性能好于druid。
-
-```yaml
-spring:
- datasource:
- url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
- username: root
- password: root
- driver-class-name: com.mysql.cj.jdbc.Driver
-
- # 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
- # spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
- type: com.zaxxer.hikari.HikariDataSource
- hikari:
- # 池中维护的最小空闲连接数
- minimum-idle: 10
- # 池中最大连接数,包括闲置和使用中的连接
- maximum-pool-size: 20
- # 此属性控制从池返回的连接的默认自动提交行为。默认为true
- auto-commit: true
- # 允许最长空闲时间
- idle-timeout: 30000
- # 此属性表示连接池的用户定义名称,主要显示在日志记录和JMX管理控制台中,以标识池和池配置。 默认值:自动生成
- pool-name: custom-hikari
- #此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
- max-lifetime: 1800000
- # 数据库连接超时时间,默认30秒,即30000
- connection-timeout: 30000
- # 连接测试sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
- connection-test-query: SELECT 1
-
-# mybatis 相关配置
-mybatis:
- # 指定 sql xml 文件的位置
- mapper-locations: classpath*:mappers/*.xml
- configuration:
- # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
- # oracle数据库建议配置为JdbcType.NULL, 默认是Other
- jdbc-type-for-null: 'null'
- # 是否打印sql语句 调试的时候可以开启
- log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-```
-
-#### 2.2 xml方式的sql语句
-
-新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
-
-```java
-@Mapper
-public interface ProgrammerMapper {
-
- void save(Programmer programmer);
-
- Programmer selectById(int id);
-
- int modify(Programmer programmer);
-
- void delete(int id);
-}
-```
-
-```xml
-
-
-
-
-
- insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
-
-
-
-
-
- update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
-
-
-
- delete from programmer where id = #{id}
-
-
-
-```
-
-测试类
-
-```java
-/***
- * @description: xml Sql测试类
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MybatisXmlTest {
-
- @Autowired
- private ProgrammerMapper mapper;
-
- @Test
- public void save() {
- mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
- mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
- }
-
- @Test
- public void modify() {
- mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
- }
-
- @Test
- public void selectByCondition() {
- Programmer programmers = mapper.selectById(1);
- System.out.println(programmers);
- }
-
- @Test
- public void delete() {
- mapper.delete(2);
- Programmer programmers = mapper.selectById(2);
- Assert.assertNull(programmers);
- }
-}
-```
-
-#### 2.3 注解方式的sql语句
-
-```java
-@Mapper
-public interface ProgrammerDao {
-
- @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
- void save(Programmer programmer);
-
- @Select("select * from programmer where name = #{id}")
- Programmer selectById(int id);
-
- @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
- int modify(Programmer programmer);
-
- @Delete(" delete from programmer where id = #{id}")
- void delete(int id);
-}
-```
-
-测试类
-
-```java
-/***
- * @description: 注解Sql测试类
- */
-@RunWith(SpringRunner.class)
-@SpringBootTest
-public class MybatisAnnotationTest {
-
- @Autowired
- private ProgrammerDao programmerDao;
-
- @Test
- public void save() {
- programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
- programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
- }
-
- @Test
- public void modify() {
- programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
- }
-
- @Test
- public void selectByCondition() {
- Programmer programmers = programmerDao.selectById(1);
- System.out.println(programmers);
- }
-
- @Test
- public void delete() {
- programmerDao.delete(3);
- Programmer programmers = programmerDao.selectById(3);
- Assert.assertNull(programmers);
- }
-}
-
-```
-
+## 一、说明
+
+#### 1.1 项目结构
+
+1. 项目查询用的表对应的建表语句放置在 resources 的 sql 文件夹下;
+
+2. 关于 mybatis sql 的写法提供两种方式:
+
+ xml 写法:对应的类为 ProgrammerMapper.java 和 programmerMapper.xml,用 MybatisXmlTest 进行测试;
+
+ 注解写法:对应的类为 Programmer.java ,用 MybatisAnnotationTest 进行测试。
+
+
+
+#### 1.2 项目主要依赖
+
+需要说明的是按照 spring 官方对应自定义的 starter 命名规范的推荐:
+
+- 官方的 starter 命名:spring-boot-starter-XXXX
+- 其他第三方 starter 命名:XXXX-spring-boot-starte
+
+所以 mybatis 的 starter 命名为 mybatis-spring-boot-starter,如果有自定义 starter 需求,也需要按照此命名规则进行命名。
+
+```xml
+
+
+ org.mybatis.spring.boot
+ mybatis-spring-boot-starter
+ 1.3.2
+
+
+
+ mysql
+ mysql-connector-java
+ 8.0.13
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+```
+
+spring boot 与 mybatis 版本的对应关系:
+
+| MyBatis-Spring-Boot-Starter | [MyBatis-Spring](http://www.mybatis.org/spring/index.html#Requirements) | Spring Boot |
+| --------------------------- | ------------------------------------------------------------ | ------------- |
+| **1.3.x (1.3.1)** | 1.3 or higher | 1.5 or higher |
+| **1.2.x (1.2.1)** | 1.3 or higher | 1.4 or higher |
+| **1.1.x (1.1.1)** | 1.3 or higher | 1.3 or higher |
+| **1.0.x (1.0.2)** | 1.2 or higher | 1.3 or higher |
+
+## 二、整合 mybatis
+
+#### 2.1 在application.yml 中配置数据源
+
+spring boot 2.x 版本默认采用 Hikari 作为数据库连接池,Hikari 是目前 java 平台性能最好的连接池,性能好于 druid。
+
+```yaml
+spring:
+ datasource:
+ url: jdbc:mysql://127.0.0.1:3306/mysql?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
+ username: root
+ password: root
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
+ # 如果不想配置对数据库连接池做特殊配置的话,以下关于连接池的配置就不是必须的
+ # spring-boot 2 默认采用高性能的 Hikari 作为连接池 更多配置可以参考 https://github.com/brettwooldridge/HikariCP#configuration-knobs-baby
+ type: com.zaxxer.hikari.HikariDataSource
+ hikari:
+ # 池中维护的最小空闲连接数
+ minimum-idle: 10
+ # 池中最大连接数,包括闲置和使用中的连接
+ maximum-pool-size: 20
+ # 此属性控制从池返回的连接的默认自动提交行为。默认为 true
+ auto-commit: true
+ # 允许最长空闲时间
+ idle-timeout: 30000
+ # 此属性表示连接池的用户定义名称,主要显示在日志记录和 JMX 管理控制台中,以标识池和池配置。 默认值:自动生成
+ pool-name: custom-hikari
+ #此属性控制池中连接的最长生命周期,值 0 表示无限生命周期,默认 1800000 即 30 分钟
+ max-lifetime: 1800000
+ # 数据库连接超时时间,默认 30 秒,即 30000
+ connection-timeout: 30000
+ # 连接测试 sql 这个地方需要根据数据库方言差异而配置 例如 oracle 就应该写成 select 1 from dual
+ connection-test-query: SELECT 1
+
+# mybatis 相关配置
+mybatis:
+ # 指定 sql xml 文件的位置
+ mapper-locations: classpath*:mappers/*.xml
+ configuration:
+ # 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。
+ # oracle 数据库建议配置为 JdbcType.NULL, 默认是 Other
+ jdbc-type-for-null: 'null'
+ # 是否打印 sql 语句 调试的时候可以开启
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+```
+
+#### 2.2 xml方式的sql语句
+
+新建 ProgrammerMapper.java 和 programmerMapper.xml,及其测试类
+
+```java
+@Mapper
+public interface ProgrammerMapper {
+
+ void save(Programmer programmer);
+
+ Programmer selectById(int id);
+
+ int modify(Programmer programmer);
+
+ void delete(int id);
+}
+```
+
+```xml
+
+
+
+
+
+ insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})
+
+
+
+
+
+ update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}
+
+
+
+ delete from programmer where id = #{id}
+
+
+
+```
+
+测试类
+
+```java
+/***
+ * @description: xml Sql 测试类
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MybatisXmlTest {
+
+ @Autowired
+ private ProgrammerMapper mapper;
+
+ @Test
+ public void save() {
+ mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
+ mapper.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
+ }
+
+ @Test
+ public void modify() {
+ mapper.modify(new Programmer(1, "xiaohong", 112, 347.34f, new Date()));
+ }
+
+ @Test
+ public void selectByCondition() {
+ Programmer programmers = mapper.selectById(1);
+ System.out.println(programmers);
+ }
+
+ @Test
+ public void delete() {
+ mapper.delete(2);
+ Programmer programmers = mapper.selectById(2);
+ Assert.assertNull(programmers);
+ }
+}
+```
+
+#### 2.3 注解方式的sql语句
+
+```java
+@Mapper
+public interface ProgrammerDao {
+
+ @Insert("insert into programmer (name, age, salary, birthday) VALUES (#{name}, #{age}, #{salary}, #{birthday})")
+ void save(Programmer programmer);
+
+ @Select("select * from programmer where name = #{id}")
+ Programmer selectById(int id);
+
+ @Update("update programmer set name=#{name},age=#{age},salary=#{salary},birthday=#{birthday} where id=#{id}")
+ int modify(Programmer programmer);
+
+ @Delete(" delete from programmer where id = #{id}")
+ void delete(int id);
+}
+```
+
+测试类
+
+```java
+/***
+ * @description: 注解 Sql 测试类
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class MybatisAnnotationTest {
+
+ @Autowired
+ private ProgrammerDao programmerDao;
+
+ @Test
+ public void save() {
+ programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
+ programmerDao.save(new Programmer("xiaominng", 12, 3467.34f, new Date()));
+ }
+
+ @Test
+ public void modify() {
+ programmerDao.modify(new Programmer(1, "xiaolan", 21, 347.34f, new Date()));
+ }
+
+ @Test
+ public void selectByCondition() {
+ Programmer programmers = programmerDao.selectById(1);
+ System.out.println(programmers);
+ }
+
+ @Test
+ public void delete() {
+ programmerDao.delete(3);
+ Programmer programmers = programmerDao.selectById(3);
+ Assert.assertNull(programmers);
+ }
+}
+
+```
+
diff --git a/spring-boot/spring-boot-rabbitmq/README.md b/spring-boot/spring-boot-rabbitmq/README.md
index 9a26bfd..f2d8eb7 100644
--- a/spring-boot/spring-boot-rabbitmq/README.md
+++ b/spring-boot/spring-boot-rabbitmq/README.md
@@ -1,5 +1,6 @@
-# spring boot 整合 rabbitmq
-
## 目录
+# spring boot 整合 rabbitmq
+
+## 目录
一、 项目结构说明
二、关键依赖
三、公共模块(rabbitmq-common)
@@ -16,391 +17,391 @@
-## 一、 项目结构说明
-
-1.1 之前关于spring 整合 rabbitmq 我们采用的是单项目的方式,为了使得用例更具有实际意义,这里采用maven多模块的构建方式,在spring-boot-rabbitmq下构建三个子模块:
-
-1. rabbitmq-common 是公共模块,用于存放公共的接口、配置和bean,被rabbitmq-producer和rabbitmq-consumer在pom.xml中引用;
-2. rabbitmq-producer 是消息的生产者模块;
-3. rabbitmq-consumer是消息的消费者模块。
-
-1.2 关于rabbitmq安装、交换机、队列、死信队列等基本概念可以参考我的手记[《RabbitMQ实战指南》读书笔记](https://github.com/heibaiying/LearningNotes/blob/master/notes/%E4%B8%AD%E9%97%B4%E4%BB%B6/RabbitMQ/%E3%80%8ARabbitMQ%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0.md),里面有详细的配图说明。
-
-
-
-
-
-## 二、关键依赖
-
-在父工程的项目中统一导入依赖rabbitmq的starter(spring-boot-starter-amqp),父工程的pom.xml如下
-
-```xml
-
-
-
- 4.0.0
- pom
-
-
- rabbitmq-consumer
- rabbitmq-producer
- rabbitmq-common
-
-
-
- org.springframework.boot
- spring-boot-starter-parent
- 2.1.1.RELEASE
-
-
- com.heibaiying
- spring-boot-rabbitmq
- 0.0.1-SNAPSHOT
- spring-boot-rabbitmq
- RabbitMQ project for Spring Boot
-
-
- 1.8
-
-
-
-
- org.springframework.boot
- spring-boot-starter-amqp
-
-
- org.springframework.boot
- spring-boot-starter-web
-
-
-
- org.projectlombok
- lombok
- true
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
-
-```
-
-
-
-## 三、公共模块(rabbitmq-common)
-
-- bean 下为公共的实体类。
-- constant 下为公共配置,用静态常量引用。(这里我使用静态常量是为了方便引用,实际中也可以按照情况,抽取为公共配置文件)
-
-
-
-```java
-package com.heibaiying.constant;
-
-/**
- * @author : heibaiying
- * @description : rabbit 公用配置信息
- */
-public class RabbitInfo {
-
- // queue 配置
- public static final String QUEUE_NAME = "spring.boot.simple.queue";
- public static final String QUEUE_DURABLE = "true";
-
- // exchange 配置
- public static final String EXCHANGE_NAME = "spring.boot.simple.exchange";
- public static final String EXCHANGE_TYPE = "topic";
-
- // routing key
- public static final String ROUTING_KEY = "springboot.simple.*";
-}
-
-```
-
-
-
-## 四、服务消费者(rabbitmq-consumer)
-
-
-
-#### 4.1 消息消费者配置
-
-```yaml
-spring:
- rabbitmq:
- addresses: 127.0.0.1:5672
- # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
- # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
- username: guest
- password: guest
- virtual-host: /
- listener:
- simple:
- # 为了保证信息能够被正确消费,建议签收模式设置为手工签收,并在代码中实现手工签收
- acknowledge-mode: manual
- # 侦听器调用者线程的最小数量
- concurrency: 10
- # 侦听器调用者线程的最大数量
- max-concurrency: 50
-```
-
-#### 4.2 使用注解@RabbitListener和@RabbitHandler创建消息监听者
-
-1. 使用注解创建的交换机、队列、和绑定关系会在项目初始化的时候自动创建,但是不会重复创建;
-2. 这里我们创建两个消息监听器,分别演示消息是基本类型和消息是对象时的配置区别。
-
-```java
-/**
- * @author : heibaiying
- * @description : 消息是对象的消费者
- */
-
-@Component
-@Slf4j
-public class RabbitmqBeanConsumer {
-
- @RabbitListener(bindings = @QueueBinding(
- value = @Queue(value = RabbitBeanInfo.QUEUE_NAME, durable = RabbitBeanInfo.QUEUE_DURABLE),
- exchange = @Exchange(value = RabbitBeanInfo.EXCHANGE_NAME, type = RabbitBeanInfo.EXCHANGE_TYPE),
- key = RabbitBeanInfo.ROUTING_KEY)
- )
- @RabbitHandler
- public void onMessage(@Payload Programmer programmer, @Headers Map headers, Channel channel) throws Exception {
- log.info("programmer:{} ", programmer);
- Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
- channel.basicAck(deliveryTag, false);
- }
-}
-```
-
-```java
-@Component
-@Slf4j
-public class RabbitmqConsumer {
-
- @RabbitListener(bindings = @QueueBinding(
- value = @Queue(value = RabbitInfo.QUEUE_NAME, durable = RabbitInfo.QUEUE_DURABLE),
- exchange = @Exchange(value = RabbitInfo.EXCHANGE_NAME, type = RabbitInfo.EXCHANGE_TYPE),
- key = RabbitInfo.ROUTING_KEY)
- )
- @RabbitHandler
- public void onMessage(Message message, Channel channel) throws Exception {
- MessageHeaders headers = message.getHeaders();
- // 获取消息头信息和消息体
- log.info("msgInfo:{} ; payload:{} ", headers.get("msgInfo"), message.getPayload());
- // DELIVERY_TAG 代表 RabbitMQ 向该Channel投递的这条消息的唯一标识ID,是一个单调递增的正整数
- Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
- // 第二个参数代表是否一次签收多条,当该参数为 true 时,则可以一次性确认 DELIVERY_TAG 小于等于传入值的所有消息
- channel.basicAck(deliveryTag, false);
- }
-
-}
-```
-
-
-
-## 五、 消息生产者(rabbitmq-producer)
-
-
-
-#### 5.1 消息生产者配置
-
-```yaml
-spring:
- rabbitmq:
- addresses: 127.0.0.1:5672
- # RabbitMQ 默认的用户名和密码都是guest 而虚拟主机名称是 "/"
- # 如果配置其他虚拟主机地址,需要预先用管控台或者图形界面创建 图形界面地址 http://主机地址:15672
- username: guest
- password: guest
- virtual-host: /
- # 是否启用发布者确认 具体确认回调实现见代码
- publisher-confirms: true
- # 是否启用发布者返回 具体返回回调实现见代码
- publisher-returns: true
- # 是否启用强制消息 保证消息的有效监听
- template.mandatory: true
-
-server:
- port: 8090
-```
-
-#### 5.2 创建消息生产者
-
-```java
-/**
- * @author : heibaiying
- * @description : 消息生产者
- */
-@Component
-@Slf4j
-public class RabbitmqProducer {
-
- @Autowired
- private RabbitTemplate rabbitTemplate;
-
- public void sendSimpleMessage(Map headers, Object message,
- String messageId, String exchangeName, String key) {
- // 自定义消息头
- MessageHeaders messageHeaders = new MessageHeaders(headers);
- // 创建消息
- Message