十、SeaTa分布式事务
添加依赖
<!-- Seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata 完成分布式事务的两个相关依赖(Seata会自动使用其中的资源) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
消费者只添加
<!-- Seata和SpringBoot整合依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
添加配置信息
seata:
#定义事务分组,方便与其他项目区分
tx-service-group: csmall_group
service:
vgroup-mapping:
#分组配置按照默认的配置seata
csmall_group: default
grouplist:
#配置seata服务器地址和端口号
default: localhost:8091
事务触发者(消费者)添加注解 :谁是TM就添加注解
//Global全局 Transactional事务
//方法添加注解后,会设置为分部式事务的起点,当前模块就是TM事务管理器
//所有远程调用操作数据库的功能都在同一个事务中,体现事务原子性
@GlobalTransactional
@Override
public void buy() {
OrderAddDTO orderAddDTO = new OrderAddDTO();
orderAddDTO.setMoney(20);
orderAddDTO.setCommodityCode("PC100");
orderAddDTO.setUserId("UU100");
orderAddDTO.setCount(100);
log.info("新增订单信息为:{}",orderAddDTO);
//dubbo调用,完成订单的增加
dubbooderService.orderAdd(orderAddDTO);
}
十一、Sentinel框架
主要用于限流
<!--Spring Cloud Sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置路径,和spring cloud nacos处于同一路径
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel提供的运行状态仪表盘
port: 8721 #执行限流的端口号,每个项目需设置不同端口号,例如cart模块设置为8722
添加注解,添加在controller中。在方法运行前不会被检测到,必须执行一次后才可以被检测到
@SentinelResource("减少商品库存数")
public JsonResult reduceCommodityCount(
StockReduceCountDTO stockReduceCountDTO){
// 调用业务逻辑层
stockService.reduceCommodityCount(stockReduceCountDTO);
return JsonResult.ok("库存减少完成!");
}
十二、Gateway网关
添加网关依赖
<!-- SpringGateway的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Nacos依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 网关负载均衡依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
配置文件,配置网关路由
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
# 网关也是微服务项目的一部分,所以也要注册到Nacos
server-addr: localhost:8848
gateway:
# routes就是路由的意思,这是属性是一个数组类型,其中的值都是数组元素
routes:
# 数组元素配置中,-开头表示一个数组元素的开始,后面所有内容都是这个元素的内容
# id表示当前路由的名称,和任何之前出现过的名字没有任何关联,唯一的要求就是不要后之后的id重复
- id: gateway-beijing
# 下面的配置是路由的目标,也就是目标的服务器名称
# lb是LoadBalance的缩写,beijing是服务器名称
uri: lb://beijing
# predicates是断言的意思,就是满足某个条件时,去执行某些操作的设置
predicates:
# predicates也是一个数组,配置断言的内容
# 这个断言的意思就是如果访问的路径是/bj/开头(**表示任何路径),
# 就去访问上面定好的beijing服务器
# ↓ P一定要大写!!!!!!!
- Path=/bj/**
# spring.cloud.gateway.routes[0].uri
# spring.cloud.gateway.routes[0].predicates[0]
routes:
{ 在配置中添加数组后是这个样子
{"id":"gateway-beijing","uri":"lb://beijing","predicates":{"Path=/bj/**"}},
}
动态路由配置:
不需要再繁琐的添加路由配置
gateway:
#开启网关动态路由配置
#匹配规则:在网关端口号后先编写注册在nacos的名称再编写路径
#eg:localhost:9081/bj/show -> localhost:9000/beijing/bj/show
discovery:
locator:
enabled: true
十二、Spring Data框架和ES搜索
类似mybaits,数据源整合框架,此框架用作ES搜索引擎的数据源配置
<!-- Spring Data Elasticsearch 整合依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
#es ip位置和端口号
spring.elasticsearch.rest.uris=http://localhost:9200
#设置日志门槛
logging.level.cn.tedu.search=debug
#SpringDataElasticsearch框架中有一个框架日志需要设置日志门槛
logging.level.org.elasticsearch.client.RestClient=debug
添加数据库的实体类
@Data
@Accessors(chain = true) //支持链式set赋值
@AllArgsConstructor //自动生成全参构造方法
@NoArgsConstructor //自动生成无参构造方法
//@Document 对应ES框架的一个实体类
//indexName指定ES索引名 例如http中的(/questions),没有的话会自动创建
@Document(indexName = "items")
public class Item implements Serializable {
//SpringData通过Id注解标记当前实体类主键
@Id
private Long id;
//标记分词,定义分词器
@Field(type = FieldType.Text,
analyzer = "ik_max_word",
searchAnalyzer = "ik_max_word")
private String title;
//FieldType.Keyword不分词按整体保存
@Field(type = FieldType.Keyword)
private String category;
@Field(type = FieldType.Keyword)
private String brand;
@Field(type = FieldType.Double)
private Double price;
//index=false不添加索引
@Field(type = FieldType.Keyword,index=false)
private String imgPath;
}
编写Repository层,等同于mapper层的mapper
@Repository
//等同于mapper层的mapper,这是SpringData的命名规范,也必须 extends ElasticsearchRepository
public interface ItemRepository extends ElasticsearchRepository<Item, Long> {
// ItemRepository接口要继承SpringData框架提供的父接口ElasticsearchRepository
// 一旦继承,当前接口就可以编写连接ES操作数据的代码了,
// 继承了父接口后,SpringData会根据我们泛型中编写的Item找到对应的索引
// 会对这个索引自动生成基本的增删改查方法,我们自己无需再编写
// ElasticsearchRepository<[要操作的\关联的实体类名称],[实体类主键的类型]>
//SpringData实现自定义查询
//ES数据库没有SQL语句
//query(查询)等价SQL的select
// Item/Items:确定要查询的实体类(对应的索引),不带s是查询单个对象的,带s的查集合
// By(通过/根据):标识开始设置查询条件的单词,等价于sql中的where
// Title:要查询的字段,可以是Item实体类中声明的任何字段
// Matches(匹配):执行查询条件的操作,Matches是匹配字符串类型的关键字,支持分词等价sql中的like
Iterable<Item> queryItemsByTitleMatches(String title);
//多条件查询(与)
Iterable<Item> queryItemsByTitleMatchesAndBrandMatches(String title, String brand);
//条件查询结果排序
//多条件查询(或)
Iterable<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(String title, String brand);
//分页查询
Page<Item> queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(String title, String brand, Pageable pageable);
}
public interface ItemRepository extends ElasticsearchRepository<Item, Long>
//需要继承springdata给定的父接口可以直接使用基本的增删改查方法
//要实现的实体类,主键类型
实例测试
//执行单增
@Test
void addOne() {
Item item = new Item()
.setId(1L)
.setTitle("小米卷纸")
.setCategory("生活")
.setBrand("罗技")
.setPrice(10.0)
.setImgPath("/xioami.jpg");
//利用自带的添加方法添加
itemRepository.save(item);
System.out.println("添加OK");
}
//单查
@Test
void getOne() {
//optional类型只能放一个元素,类似List但list能放多个
Optional<Item> byId = itemRepository.findById(1L);
//把他从Optional里取出来
Item item = byId.get();
System.out.println(item);
}
//批量增
@Test
void addList() {
//实例化一个list对保存到es数据库中
List<Item> list = new ArrayList<>();
list.add(new Item(2L, "罗技激光有线游戏鼠标", "鼠标"
, "罗技", 9.9, "/2.jpg"));
list.add(new Item(3L, "雷蛇机械无线办公键盘", "键盘"
, "雷蛇", 119.9, "/3.jpg"));
list.add(new Item(4L, "微软有线静音办公鼠标", "鼠标"
, "微软", 29.9, "/4.jpg"));
list.add(new Item(5L, "罗技机械有线背光键盘", "键盘"
, "罗技", 236.0, "/5.jpg"));
itemRepository.saveAll(list);
System.out.println("批量添加OK");
}
//全查
@Test
void getAll() {
Iterable<Item> items = itemRepository.findAll();
for (Item item : items) {
System.out.println(item);
}
System.out.println("------------------------");
items.forEach(item -> System.out.println(item));
}
//单查
@Test
void queryOne() {
//查询es中items索引数据,title中字段包含"游戏"的分词数据
Iterable<Item> items = itemRepository.queryItemsByTitleMatches("键盘");
items.forEach(item -> System.out.println(item));
}
//多条件查询
@Test
void queryTwo() {
//查询ES中,items索引里,title字段包含"游戏",品牌是"罗技"的数据
Iterable<Item> items = itemRepository
.queryItemsByTitleMatchesAndBrandMatches("游戏", "罗技");
items.forEach(item -> System.out.println(item));
}
//多条件查询结果排序
@Test
void queryTwoorderby() {
//查询ES中,items索引里,title字段包含"游戏",品牌是"罗技"的数据
Iterable<Item> items = itemRepository
.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc("游戏", "罗技");
items.forEach(item -> System.out.println(item));
}
// 自定义分页查询
@Test
void queryPage(){
int page=2; // 要查询的页码,写1表示查询第一页
int pageSize=2; // 每页条数的设置
Page<Item> pages=itemRepository
.queryItemsByTitleMatchesOrBrandMatchesOrderByPriceDesc(
"游戏","罗技", PageRequest.of(page-1,pageSize));
pages.forEach(item -> System.out.println(item));
// pages对象包含的分页信息输出
System.out.println("总页数:"+pages.getTotalPages());
System.out.println("总条数:"+pages.getTotalElements());
System.out.println("当前页:"+(pages.getNumber()+1));
System.out.println("每页条数:"+pages.getSize());
System.out.println("是否是首页:"+pages.isFirst());
System.out.println("是否是末页:"+pages.isLast());
}
十三、Pagehelper分页
添加依赖
<!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
在业务中的使用
Paperhelper框架完成分页的功能原理是sql语句后自动添加limit。
//Mapper层
//查所有
List<LogMg> selectAll();
//业务层接口
//查所有
List<LogMg> selectAll( Integer page, Integer pageSize);
page分页只需要包裹住业务层即可不需要关注mapper,controller层只需要调用业务接口就可以。
//分页查询所有订单信息的方法
//参数page是页码,pageSize是每页条数
public List<LogMg> getAllOrdersByPage(Integer page, Integer pageSize) {
//pagehepler框架实现分页最核心代码,是要编写在执行查询数据代码之前
PageHelper.startPage(page, pageSize);
//上面设置好分页执行条件,下面的查询在执行时,SQL会自动追加limit关键字
List<LogMg> list = orderMapper.findAllOrders();
return list;
}
十四、Kafka消息队列
添加依赖
<!--Kafka整合SpringBoot=依赖-->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
添加配置
spring:
kafka:
#定义kafka的位置
bootstrap-servers: localhost:9092
#consumer.group-id 是kafka框架必需配置,是话题分组,来区分不同项目消息
#这个分组名称会在发送时自动前缀在话题名称前
consumer:
group-id: csmall
@EnableDubbo
@SpringBootApplication
//启动kafka
@EnableKafka
//为了测试Kafka添加Spring自带的定时任务,向Kafka发送消息
@EnableScheduling
public class CsmallCartWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallCartWebapiApplication.class, args);
}
}
测试
消息产生
//这个类要实现周期运行代码(定时任务)
@Component
public class Producer {
//直接从spring中获取kafkaTemplate对象
//这个对象会在springboot启动时,根据配置自动生成
//kafkaTemplate<话题类型,消息类型>
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
int i = 0;
//实现每隔15秒,发送以此话题
@Scheduled(fixedRate = 15000)
public void sendMessage() {
Cart cart = new Cart();
cart.setId(i++);
cart.setUserId("UU100");
cart.setCommodityCode("PC100");
cart.setPrice(RandomUtils.nextInt(90) + 10);
cart.setCount(RandomUtils.nextInt(10) + 1);
//将cart转换为JSON格式发送
//{"id":"1","userId":"UU100"....}
Gson gson = new Gson();
String json = gson.toJson(cart);
System.out.println("要发送的JSON消息为"+json);
//kafka发送
kafkaTemplate.send("001",json);
}
}
消息接受
//要接受Kafka消息必须是spring中装载了的类
@Component
public class Consumer {
//Springkafka使用了监听机制
//我们指定了一个话题,只要这个话题发送了信息,这个监听器就会通知我们
//topics="创建的话题"
@KafkaListener(topics = "001")
//方法的参数和返回值是话题指定的不能修改<话题类型,消息类型>
public void received(ConsumerRecord<String, String> record) {
//record由监听器自动赋值
String json = record.value();
Gson gson = new Gson();
// 需要转的值,转的类型
Cart cart = gson.fromJson(json, Cart.class);
System.out.println("接受消息" + cart);
}
}
十五、RabbitMQ消息队列
添加依赖
<!--rabbitmq依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
创建交换机队列key
@Slf4j
@Configuration
//配置Rabbit交换机,路由key和队列关系
//交换机,队列是实体对象,key是他俩绑定关系
public class RabbitConfig {
public static final String STOCK_EX = "stock_ex";
public static final String STOCK_ROUT = "stock_rout";
public static final String STOCK_QUEUE = "stock_queue";
//创建交换机
@Bean
public DirectExchange stockDirectExchange() {
return new DirectExchange(STOCK_EX);
}
//创建队列
@Bean
public Queue stockQueue(){
return new Queue(STOCK_QUEUE);
}
//绑定将队列绑定到交换机,和绑定的key
@Bean
public Binding stockBinding(){
return BindingBuilder
.bind(stockQueue()).to(stockDirectExchange())
.with(STOCK_ROUT);
}
}
添加到消息队列
//秒杀成功记录添加到消息队列
//success信息与秒杀sku信息大部分相同,赋值
Success success = new Success();
BeanUtils.copyProperties(seckillOrderAddDTO.getSeckillOrderItemAddDTO(), success);
success.setUserId(userId);
success.setOrderSn(orderAddVO.getSn());
success.setSeckillPrice(seckillOrderAddDTO.getSeckillOrderItemAddDTO().getPrice());
rabbitTemplate.convertAndSend(
RabbitMqComponentConfiguration.SECKILL_EX,
RabbitMqComponentConfiguration.SECKILL_QUEUE,
success
);
接受
@Slf4j
@Component
//创建监听器
@RabbitListener(queues = RabbitConfig.STOCK_QUEUE)
public class RabbitMQConsumer {
//唯一的注解,监听到后执行的方法
@RabbitHandler
public void process(Stock stock){
System.out.println("消息接受完成:"+stock);
log.info("消息接受完成:{}",stock);
}
}