SpringCloud2️⃣项目搭建:服务拆分 & 远程调用
1、相关概念
- 服务拆分:将传统的单体应用,根据业务功能拆分为多个服务。
- 不重复造轮子:不同微服务,不要重复开发相同业务。
- 独立:微服务数据独立,不要访问其它微服务的数据库
- 面向服务:将微服务的业务暴露为接口,供其它微服务调用。
- 远程调用
- 单体应用:所有业务功能集中在一个项目。可直接通过 service 调用其它模块。
- 微服务:每个服务作为独立的业务模块,独立部署。无法直接调用,需要通过 HTTP 请求调用。
2、微服务项目
示例:搭建一个简单的微服务项目,实现以下功能
-
环境
- Spring Cloud:Hoxton.SR10
- Spring Boot:2.3.x
-
要求
-
搭建用户和订单 2 个微服务,使用各自的数据库。
-
实现查询功能,对外暴露 RestFUL 接口。
-
实现服务之间的调用。
-
2.1、数据库
建立 2 个数据库,模拟多台远程数据库。
- 用户数据库:
cloud_user
- 订单数据库:
cloud_order
在 2 台数据库下分别建表,自行插入数据。
-
tb_user
:用户(id, name, password)CREATE TABLE `tb_user` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', `password` varchar(18) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'
-
tb_order
:订单(id, name, price, user_id)CREATE TABLE `tb_order` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', `name` VARCHAR(36) COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品名', `price` DOUBLE NOT NULL COMMENT '价格', `user_id` BIGINT NOT NULL COMMENT '关联用户ID', PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='订单表'
2.2、Spring Boot 项目
导入
cloud-demo
项目,注意以下问题
- Spring Boot 版本兼容问题。
- 项目结构
cloud-demo
:父项目,负责管理依赖和配置。user-service
:用户服务order-service
:订单服务
- 配置:修改数据库配置(URL,账号密码)
2.2.1、entity
entity 与数据库表对应
-
User
@Data public class User { private Long id; private String name; private String password; }
-
Order
@Data public class Order { private Long id; private String name; private Long price; private Long userId; }
2.2.2、DAO
DAO 与数据库交互
Hint:为便于演示,以注解形式编写 SQL。
-
UserMapper:根据 ID 查询用户
public interface UserMapper { @Select("SELECT * FROM tb_user WHERE id = #{id}") User getUserById(Long id); }
-
OrderMapper:根据 ID 查询用户
public interface OrderMapper { @Select("SELECT * FROM tb_order WHERE id = #{id}") Order getOrderById(Long id); }
2.2.3、service
servcie 处理业务逻辑
-
UserService
@Service public class UserService { @Resource private UserMapper userMapper; public User getUserById(Long id) { return userMapper.getUserById(id); } }
-
OrderService
@Service public class OrderService { @Resource private OrderMapper orderMapper; public Order queryOrderById(Long id) { return orderMapper.getOrderById(id); }
2.2.4、接口
Controller 对外暴露接口
-
UserController
@RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; @GetMapping("/{id}") public User queryUserById(@PathVariable("id") Long id) { return userService.getUserById(id); } }
-
OrderController
@RestController @RequestMapping("order") public class OrderController { @Resource private OrderService orderService; @GetMapping("{id}") public Order queryOrderById(@PathVariable("id") Long id) { return orderService.queryOrderById(id); } }
2.2.5、测试
-
用户
-
订单:只有 userId,没有用户详情
3、远程调用
3.1、说明
3.1.1、跨模块调用
假设模块 A 要调用模块 B 的功能
-
单体应用(本地调用):定义成员变量,调用 ServiceB 的方法。
@Service public class ServiceA { @Resource private ServiceB serviceB; public void test() { // 调用ServiceB的方法 } }
-
微服务应用(远程调用):发起 HTTP 请求,调用 微服务 B 提供的接口。
@Service public class ServiceA { public void test() { // 调用服务B的接口 } }
3.1.2、服务调用关系
在一个业务中,服务调用关系有 2 个角色
- 服务提供者:提供接口,供其它微服务调用。
- 服务消费者:调用其它微服务提供的接口。
角色是相对业务而言的,即一个微服务既可以是提供者,也可以是消费者。
3.2、case
需求:根据 orderId 查询完整 order 信息,包括关联用户详情。
- 查询 order
- 向 user 服务发起 HTTP 请求(根据 id 查询用户)
- 将返回结果拼接到 orderVO 中,返回
3.2.1、RestTemplate
作用:在一个服务中执行 HTTP 请求,调用另一个服务的接口。
配置 RestTemplate
-
创建一个
@Configuration
配置类(或使用已有配置类) -
使用
@Bean
,向 Spring 容器中注册@Configuration public class RestTemplateConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
3.2.2、OrderVO
VO 用于接口和页面之间的交互
-
定义 OrderVO
@Data public class OrderVO extends Order { private User user; }
-
定义 entityToVo():将 entity 转换为 VO
(通常定义在工具类中,此处为简化可定义在 VO 中)
public void entityToVo(Order order) { this.setId(order.getId()); this.setName(order.getName()); this.setPrice(order.getPrice()); this.setUserId(order.getUserId()); }
3.2.3、OrderService
OrderVO 继承自 Order,
因此原先定义的返回 Order 的方法,可直接返回 OrderVO
-
订单:查询 order,转换为 VO
-
用户:向 user 服务发起 HTTP 请求,将返回结果拼接到 orderVO 中
-
返回 VO
@Service public class OrderService { @Resource private OrderMapper orderMapper; @Resource private RestTemplate restTemplate; public Order queryOrderById(Long id) { // 1.1、订单entity Order order = orderMapper.getOrderById(id); if (order == null) { return null; } // 1.2、订单entity转VO OrderVO orderVO = new OrderVO(); orderVO.entityToVo(order); // 2.1、关联用户 String url = "http://localhost:8081/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class); // 2.2、拼接关联用户 if (user != null) { orderVO.setUser(user); } // 3.返回 return orderVO; } }
3.2.4、测试接口
查询结果中包含关联用户详情。