《阿里巴巴开发手册》以及网上各种博客或多或少都有提到诸如DTO、DO、BO、PO、VO等等，也提倡对实体类进行分层。至于为什么要分层，它们的理由是“避免暴露内部设计细节，只展示必要的字段”，但我个人最大的感受其实是“解耦”。我曾遇到一件无奈的事，接口已经开发完毕，前后端也联合好了，结果产品临时要大改，Service层的逻辑基本要推倒重来，连查的表都不一样了。好在得益于DTO和VO的隔离，并没有影响到其它层，前端甚至完全不知道后端全部重写了，Swagger文档也和原来一模一样...不过个人觉得没必要去扣这些概念，比如BO、PO是啥我也记不清。一般来说，POJO分为三类即可：客户端/前端传入的DTO与数据库字段映射的DO返回给客户端/前端的VO也可以这样理解VO 是后端返给前端展示用的DTO 是前端传过来数据转换成后端的数据比如controller层来的各字段的数据转换成DTO，一个各个方法之间传递数据的都可以用DTODO 其实就是数据库存数据时候的实体一般都是直接使用Spring提供的BeanUtils进行数据拷贝，但它有两个问题：没有提供额外的映射关系，所以两个实体类字段名必须完全一致不支持深拷贝市面上有很多比Spring的BeanUtils更强大的转换工具，比如MapStruct啥的
IDEA创建测试类spring Boot中单元测试类写在src/test/java目录下，可以手动创建具体测试类，也可以通过IDEA自动创建测试类，如下图：（注：点选并打开相应代码界面，再点击菜单栏的Navigate）注解解释@SpringBootTest：获取启动类，加载配置，寻找主配置启动类（被 @SpringBootApplication 注解的） @RunWith(SpringRunner.class)：让JUnit运行Spring的测试环境,获得Spring环境的上下文的支持@AutoConfigureMockMvc：用于自动配置MockMvc,配置后MockMvc类可以直接注入,相当于new MockMvc @Before:初始化方法 ,对于每一个测试方法都要执行一次
1.Nacos配置管理Nacos除了可以做注册中心，同样可以做配置管理来使用。1.1.统一配置管理当微服务部署的实例越来越多，达到数十、数百时，逐个修改微服务配置就会让人抓狂，而且很容易出错。我们需要一种统一配置管理方案，可以集中管理所有实例的配置。Nacos一方面可以将配置集中管理，另一方可以在配置变更时，及时通知微服务，实现配置的热更新。1.1.1.在nacos中添加配置文件如何在nacos中管理配置呢？然后在弹出的表单中，填写配置信息：注意：项目的核心配置，需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。1.1.2.从微服务拉取配置微服务要拉取nacos中管理的配置，并且与本地的application.yml配置合并，才能完成项目启动。但如果尚未读取application.yml，又如何得知nacos地址呢？因此spring引入了一种新的配置文件：bootstrap.yaml文件，会在application.yml之前被读取，流程如下：1）引入nacos-config依赖首先，在user-service服务中，引入nacos-config的客户端依赖： <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>2）添加bootstrap.yaml然后，在user-service中添加一个bootstrap.yaml文件，内容如下：spring: application: name: userservice # 服务名称 profiles: active: dev #开发环境，这里是dev cloud: nacos: server-addr: localhost:8848 # Nacos地址 config: file-extension: yaml # 文件后缀名这里会根据spring.cloud.nacos.server-addr获取nacos地址，再根据${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}作为文件id，来读取配置。本例中，就是去读取userservice-dev.yaml：3）读取nacos配置在user-service中的UserController中添加业务逻辑，读取pattern.dateformat配置：+6+9+完整代码：package cn.itcast.user.web; import cn.itcast.user.pojo.User; import cn.itcast.user.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Value("${pattern.dateformat}") private String dateformat; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat)); } // ...略 }在页面访问，可以看到效果：1.2.配置热更新我们最终的目的，是修改nacos中的配置后，微服务中无需重启即可让配置生效，也就是配置热更新。要实现配置热更新，可以使用两种方式：1.2.1.方式一在@Value注入的变量所在类上添加注解@RefreshScope：1.2.2.方式二使用@ConfigurationProperties注解代替@Value注解。在user-service服务中，添加一个类，读取patterrn.dateformat属性：package cn.itcast.user.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }在UserController中使用这个类代替@Value：完整代码：package cn.itcast.user.web; import cn.itcast.user.config.PatternProperties; import cn.itcast.user.pojo.User; import cn.itcast.user.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Slf4j @RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @Autowired private PatternProperties patternProperties; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat())); } // 略 }1.3.配置共享其实微服务启动时，会去nacos读取多个配置文件，例如：[spring.application.name]-[spring.profiles.active].yaml，例如：userservice-dev.yaml[spring.application.name].yaml，例如：userservice.yaml而[spring.application.name].yaml不包含环境，因此可以被多个环境共享。下面我们通过案例来测试配置共享1）添加一个环境共享配置我们在nacos中添加一个userservice.yaml文件：2）在user-service中读取共享配置在user-service服务中，修改PatternProperties类，读取新添加的属性：在user-service服务中，修改UserController，添加一个方法：3）运行两个UserApplication，使用不同的profile修改UserApplication2这个启动项，改变其profile值：这样，UserApplication(8081)使用的profile是dev，UserApplication2(8082)使用的profile是test。启动UserApplication和UserApplication2访问http://localhost:8081/user/prop，结果：访问http://localhost:8082/user/prop，结果：可以看出来，不管是dev，还是test环境，都读取到了envSharedValue这个属性的值。4）配置共享的优先级当nacos、服务本地同时出现相同属性时，优先级有高低之分：1.4.搭建Nacos集群Nacos生产环境下一定要部署为集群状态，部署方式参考课前资料中的文档：2.Feign远程调用先来看我们以前利用RestTemplate发起远程调用的代码：存在下面的问题：•代码可读性差，编程体验不统一•参数复杂URL难以维护Feign是一个声明式的http客户端，官方地址：https://github.com/OpenFeign/feign其作用就是帮助我们优雅的实现http请求的发送，解决上面提到的问题。2.1.Feign替代RestTemplateFegin的使用步骤如下：1）引入依赖我们在order-service服务的pom文件中引入feign的依赖：<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>2）添加注解在order-service的启动类添加注解开启Feign的功能：3）编写Feign的客户端在order-service中新建一个接口，内容如下：package cn.itcast.order.client; import cn.itcast.order.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }这个客户端主要是基于SpringMVC的注解来声明远程调用的信息，比如：服务名称：userservice请求方式：GET请求路径：/user/{id}请求参数：Long id返回值类型：User这样，Feign就可以帮助我们发送http请求，无需自己使用RestTemplate来发送了。4）测试修改order-service中的OrderService类中的queryOrderById方法，使用Feign客户端代替RestTemplate：是不是看起来优雅多了。5）总结使用Feign的步骤：① 引入依赖② 添加@EnableFeignClients注解③ 编写FeignClient接口④ 使用FeignClient中定义的方法代替RestTemplate2.2.自定义配置Feign可以支持很多的自定义配置，如下表所示：类型作用说明feign.Logger.Level修改日志级别包含四种不同的级别：NONE、BASIC、HEADERS、FULLfeign.codec.Decoder响应结果的解析器http远程调用的结果做解析，例如解析json字符串为java对象feign.codec.Encoder请求参数编码将请求参数编码，便于通过http请求发送feign. Contract支持的注解格式默认是SpringMVC的注解feign. Retryer失败重试机制请求失败的重试机制，默认是没有，不过会使用Ribbon的重试一般情况下，默认值就能满足我们使用，如果要自定义时，只需要创建自定义的@Bean覆盖默认Bean即可。下面以日志为例来演示如何自定义配置。2.2.1.配置文件方式基于配置文件修改feign的日志级别可以针对单个服务：feign: client: config: userservice: # 针对某个微服务的配置 loggerLevel: FULL # 日志级别 也可以针对所有服务：feign: client: config: default: # 这里用default就是全局配置，如果是写服务名称，则是针对某个微服务的配置 loggerLevel: FULL # 日志级别 而日志的级别分为四种：NONE：不记录任何日志信息，这是默认值。BASIC：仅记录请求的方法，URL以及响应状态码和执行时间HEADERS：在BASIC的基础上，额外记录了请求和响应的头信息FULL：记录所有请求和响应的明细，包括头信息、请求体、元数据。2.2.2.Java代码方式也可以基于Java代码来修改日志级别，先声明一个类，然后声明一个Logger.Level的对象：public class DefaultFeignConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; // 日志级别为BASIC } }如果要全局生效，将其放到启动类的@EnableFeignClients这个注解中：@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class) 如果是局部生效，则把它放到对应的@FeignClient这个注解中：@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class) 2.3.Feign使用优化Feign底层发起http请求，依赖于其它的框架。其底层客户端实现包括：•URLConnection：默认实现，不支持连接池•Apache HttpClient ：支持连接池•OKHttp：支持连接池因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。这里我们用Apache的HttpClient来演示。1）引入依赖在order-service的pom文件中引入Apache的HttpClient依赖： <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>2）配置连接池在order-service的application.yml中添加配置：feign: client: config: default: # default全局的配置 loggerLevel: BASIC # 日志级别，BASIC就是基本的请求和响应信息 httpclient: enabled: true # 开启feign对HttpClient的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数接下来，在FeignClientFactoryBean中的loadBalance方法中打断点：Debug方式启动order-service服务，可以看到这里的client，底层就是Apache HttpClient：总结，Feign的优化：1.日志级别尽量用basic2.使用HttpClient或OKHttp代替URLConnection① 引入feign-httpClient依赖② 配置文件开启httpClient功能，设置连接池参数2.4.最佳实践所谓最近实践，就是使用过程中总结的经验，最好的一种使用方式。自习观察可以发现，Feign的客户端与服务提供者的controller代码非常相似：feign客户端：UserController：有没有一种办法简化这种重复的代码编写呢？2.4.1.继承方式一样的代码可以通过继承来共享：1）定义一个API接口，利用定义方法，并基于SpringMVC注解做声明。2）Feign客户端和Controller都集成改接口优点：简单实现了代码共享缺点：服务提供方、服务消费方紧耦合参数列表中的注解映射并不会继承，因此Controller中必须再次声明方法、参数列表、注解2.4.2.抽取方式将Feign的Client抽取为独立模块，并且把接口有关的POJO、默认的Feign配置都放到这个模块中，提供给所有消费者使用。例如，将UserClient、User、Feign的默认配置都抽取到一个feign-api包中，所有微服务引用该依赖包，即可直接使用。2.4.3.实现基于抽取的最佳实践1）抽取首先创建一个module，命名为feign-api：项目结构：在feign-api中然后引入feign的starter依赖<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>然后，order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中2）在order-service中使用feign-api首先，删除order-service中的UserClient、User、DefaultFeignConfiguration等类或接口。在order-service的pom文件中中引入feign-api的依赖：<dependency> <groupId>cn.itcast.demo</groupId> <artifactId>feign-api</artifactId> <version>1.0</version> </dependency>修改order-service中的所有与上述三个组件有关的导包部分，改成导入feign-api中的包3）重启测试重启后，发现服务报错了：这是因为UserClient现在在cn.itcast.feign.clients包下，而order-service的@EnableFeignClients注解是在cn.itcast.order包下，不在同一个包，无法扫描到UserClient。4）解决扫描包问题方式一：指定Feign应该扫描的包：@EnableFeignClients(basePackages = "cn.itcast.feign.clients")方式二：指定需要加载的Client接口：@EnableFeignClients(clients = {UserClient.class})3.Gateway服务网关Spring Cloud Gateway 是 Spring Cloud 的一个全新项目，该项目是基于 Spring 5.0，Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关，它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。3.1.为什么需要网关Gateway网关是我们服务的守门神，所有微服务的统一入口。网关的核心功能特性：请求路由权限控制限流架构图：权限控制：网关作为微服务入口，需要校验用户是是否有请求资格，如果没有则进行拦截。路由和负载均衡：一切请求都必须先经过gateway，但网关不处理业务，而是根据某种规则，把请求转发到某个微服务，这个过程叫做路由。当然路由的目标服务有多个时，还需要做负载均衡。限流：当请求流量过高时，在网关中按照下流的微服务能够接受的速度来放行请求，避免服务压力过大。在SpringCloud中网关的实现包括两种：gatewayzuulZuul是基于Servlet的实现，属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux，属于响应式编程的实现，具备更好的性能。3.2.gateway快速入门下面，我们就演示下网关的基本路由功能。基本步骤如下：创建SpringBoot工程gateway，引入网关依赖编写启动类编写基础配置和路由规则启动网关服务进行测试1）创建gateway服务，引入依赖创建服务：引入依赖： <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>2）编写启动类package cn.itcast.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }3）编写基础配置和路由规则创建application.yml文件，内容如下：server: port: 10010 # 网关端口 spring: application: name: gateway # 服务名称 cloud: nacos: server-addr: localhost:8848 # nacos地址 gateway: routes: # 网关路由配置 - id: user-service # 路由id，自定义，只要唯一即可 # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址 uri: lb://userservice # 路由的目标地址 lb就是负载均衡，后面跟服务名称 predicates: # 路由断言，也就是判断请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配，只要以/user/开头就符合要求我们将符合Path 规则的一切请求，都代理到 uri参数指定的地址。本例中，我们将 /user/**开头的请求，代理到lb://userservice，lb是负载均衡，根据服务名拉取服务列表，实现负载均衡。4）重启测试重启网关，访问http://localhost:10010/user/1时，符合/user/**规则，请求转发到uri：http://userservice/user/1，得到了结果：5）网关路由的流程图整个访问的流程如下：总结：网关搭建步骤：创建项目，引入nacos服务发现和gateway依赖配置application.yml，包括服务基本信息、nacos地址、路由路由配置包括：路由id：路由的唯一标示路由目标（uri）：路由的目标地址，http代表固定地址，lb代表根据服务名负载均衡路由断言（predicates）：判断路由的规则，路由过滤器（filters）：对请求或响应做处理接下来，就重点来学习路由断言和路由过滤器的详细知识3.3.断言工厂我们在配置文件中写的断言规则只是字符串，这些字符串会被Predicate Factory读取并处理，转变为路由判断的条件例如Path=/user/**是按照路径匹配，这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的，像这样的断言工厂在SpringCloudGateway还有十几个:名称说明示例After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]Cookie请求必须包含某些cookie- Cookie=chocolate, ch.pHeader请求必须包含某些header- Header=X-Request-Id, \d+Host请求必须是访问某个host（域名）- Host=.somehost.org,.anotherhost.orgMethod请求方式必须是指定方式- Method=GET,POSTPath请求路径必须符合指定规则- Path=/red/{segment},/blue/**Query请求参数必须包含指定参数- Query=name, Jack或者- Query=nameRemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24Weight权重处理 我们只需要掌握Path这种路由工程就可以了。3.4.过滤器工厂GatewayFilter是网关中提供的一种过滤器，可以对进入网关的请求和微服务返回的响应做处理：3.4.1.路由过滤器的种类Spring提供了31种不同的路由过滤器工厂。例如：名称说明AddRequestHeader给当前请求添加一个请求头RemoveRequestHeader移除请求中的一个请求头AddResponseHeader给响应结果中添加一个响应头RemoveResponseHeader从响应结果中移除有一个响应头RequestRateLimiter限制请求的流量3.4.2.请求头过滤器下面我们以AddRequestHeader 为例来讲解。需求：给所有进入userservice的请求添加一个请求头：Truth=itcast is freaking awesome!只需要修改gateway服务的application.yml文件，添加路由过滤即可：spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器 - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头当前过滤器写在userservice路由下，因此仅仅对访问userservice的请求有效。3.4.3.默认过滤器如果要对所有的路由都生效，则可以将过滤器工厂写到default下。格式如下：spring: cloud: gateway: routes: - id: user-service uri: lb://userservice predicates: - Path=/user/** default-filters: # 默认过滤项 - AddRequestHeader=Truth, Itcast is freaking awesome! 3.4.4.总结过滤器的作用是什么？① 对路由的请求或响应做加工处理，比如添加请求头② 配置在路由下的过滤器只对当前路由的请求生效defaultFilters的作用是什么？① 对所有路由都生效的过滤器3.5.全局过滤器上一节学习的过滤器，网关提供了31种，但每一种过滤器的作用都是固定的。如果我们希望拦截请求，做自己的业务逻辑则没办法实现。3.5.1.全局过滤器作用全局过滤器的作用也是处理一切进入网关的请求和微服务响应，与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义，处理逻辑是固定的；而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。public interface GlobalFilter { /** * 处理当前请求，有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理 * * @param exchange 请求上下文，里面可以获取Request、Response等信息 * @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束 */ Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain); }在filter中编写自定义逻辑，可以实现下列功能：登录状态判断权限校验请求限流等3.5.2.自定义全局过滤器需求：定义全局过滤器，拦截请求，判断请求的参数是否满足下面条件：参数中是否有authorization，authorization参数值是否为admin如果同时满足则放行，否则拦截实现：在gateway中定义一个过滤器：package cn.itcast.gateway.filters; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Order(-1) @Component public class AuthorizeFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取请求参数 MultiValueMap<String, String> params = exchange.getRequest().getQueryParams(); // 2.获取authorization参数 String auth = params.getFirst("authorization"); // 3.校验 if ("admin".equals(auth)) { // 放行 return chain.filter(exchange); } // 4.拦截 // 4.1.禁止访问，设置状态码 exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); // 4.2.结束处理 return exchange.getResponse().setComplete(); } }3.5.3.过滤器执行顺序请求进入网关会碰到三类过滤器：当前路由的过滤器、DefaultFilter、GlobalFilter请求路由后，会将当前路由过滤器和DefaultFilter、GlobalFilter，合并到一个过滤器链（集合）中，排序后依次执行每个过滤器：排序的规则是什么呢？每一个过滤器都必须指定一个int类型的order值，order值越小，优先级越高，执行顺序越靠前。GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己指定路由过滤器和defaultFilter的order由Spring指定，默认是按照声明顺序从1递增。当过滤器的order值一样时，会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。详细内容，可以查看源码：org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters，然后再加载某个route的filters，然后合并。org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器，与前面的过滤器合并后根据order排序，组织过滤器链3.6.跨域问题3.6.1.什么是跨域问题跨域：域名不一致就是跨域，主要包括：域名不同： www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com域名相同，端口不同：localhost:8080和localhost8081跨域问题：浏览器禁止请求的发起者与服务端发生跨域ajax请求，请求被浏览器拦截的问题解决方案：CORS，这个以前应该学习过，这里不再赘述了。不知道的小伙伴可以查看https://www.ruanyifeng.com/blog/2016/04/cors.html3.6.2.模拟跨域问题找到课前资料的页面文件：放入tomcat或者nginx这样的web服务器中，启动并访问。可以在浏览器控制台看到下面的错误：从localhost:8090访问localhost:10010，端口不同，显然是跨域的请求。3.6.3.解决跨域问题在gateway服务的application.yml文件中，添加下面的配置：spring: cloud: gateway: # 。。。 globalcors: # 全局的跨域处理 add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题 corsConfigurations: '[/**]': allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:8090" allowedMethods: # 允许的跨域ajax的请求方式 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允许在请求中携带的头信息 allowCredentials: true # 是否允许携带cookie maxAge: 360000 # 这次跨域检测的有效期
SpringCloud011.认识微服务随着互联网行业的发展，对服务的要求也越来越高，服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢？1.0.学习目标了解微服务架构的优缺点1.1.单体架构单体架构：将业务的所有功能集中在一个项目中开发，打成一个包部署。单体架构的优缺点如下：优点：架构简单部署成本低缺点：耦合度高（维护困难、升级困难）1.2.分布式架构分布式架构：根据业务功能对系统做拆分，每个业务功能模块作为独立项目开发，称为一个服务。分布式架构的优缺点：优点：降低服务耦合有利于服务升级和拓展缺点：服务调用关系错综复杂分布式架构虽然降低了服务耦合，但是服务拆分时也有很多问题需要思考：服务拆分的粒度如何界定？服务之间如何调用？服务的调用关系如何管理？人们需要制定一套行之有效的标准来约束分布式架构。1.3.微服务微服务的架构特征：单一职责：微服务拆分粒度更小，每一个服务都对应唯一的业务能力，做到单一职责自治：团队独立、技术独立、数据独立，独立部署和交付面向服务：服务提供统一标准的接口，与语言和技术无关隔离性强：服务调用做好隔离、容错、降级，避免出现级联问题微服务的上述特性其实是在给分布式架构制定一个标准，进一步降低服务之间的耦合度，提供服务的独立性和灵活性。做到高内聚，低耦合。因此，可以认为微服务是一种经过良好架构设计的分布式架构方案 。但方案该怎么落地？选用什么样的技术栈？全球的互联网公司都在积极尝试自己的微服务落地方案。其中在Java领域最引人注目的就是SpringCloud提供的方案了。1.4.SpringCloudSpringCloud是目前国内使用最广泛的微服务框架。官网地址：https://spring.io/projects/spring-cloud。SpringCloud集成了各种微服务功能组件，并基于SpringBoot实现了这些组件的自动装配，从而提供了良好的开箱即用体验。其中常见的组件包括：另外，SpringCloud底层是依赖于SpringBoot的，并且有版本的兼容关系，如下：我们课堂学习的版本是 Hoxton.SR10，因此对应的SpringBoot版本是2.3.x版本。1.5.总结单体架构：简单方便，高度耦合，扩展性差，适合小型项目。例如：学生管理系统分布式架构：松耦合，扩展性好，但架构复杂，难度大。适合大型互联网项目，例如：京东、淘宝微服务：一种良好的分布式架构方案①优点：拆分粒度更小、服务更独立、耦合度更低②缺点：架构非常复杂，运维、监控、部署难度提高SpringCloud是微服务架构的一站式解决方案，集成了各种优秀微服务功能组件2.服务拆分和远程调用任何分布式架构都离不开服务的拆分，微服务也是一样。2.1.服务拆分原则这里我总结了微服务拆分时的几个原则：不同微服务，不要重复开发相同业务微服务数据独立，不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口，供其它微服务调用2.2.服务拆分示例以课前资料中的微服务cloud-demo为例，其结构如下：cloud-demo：父工程，管理依赖order-service：订单微服务，负责订单相关业务user-service：用户微服务，负责用户相关业务要求：订单微服务和用户微服务都必须有各自的数据库，相互独立订单服务和用户服务都对外暴露Restful的接口订单服务如果需要查询用户信息，只能调用用户服务的Restful接口，不能查询用户数据库2.2.1.导入Sql语句首先，将课前资料提供的cloud-order.sql和cloud-user.sql导入到mysql中：cloud-user表中初始数据如下：cloud-order表中初始数据如下：cloud-order表中持有cloud-user表中的id字段。2.2.2.导入demo工程用IDEA导入课前资料提供的Demo：项目结构如下：导入后，会在IDEA右下角出现弹窗：点击弹窗，然后按下图选择：会出现这样的菜单：配置下项目使用的JDK：2.3.实现远程调用案例在order-service服务中，有一个根据id查询订单的接口：根据id查询订单，返回值是Order对象，如图：其中的user为null在user-service中有一个根据id查询用户的接口：查询的结果如图：2.3.1.案例需求：修改order-service中的根据id查询订单业务，要求在查询订单的同时，根据订单中包含的userId查询出用户信息，一起返回。因此，我们需要在order-service中 向user-service发起一个http的请求，调用http://localhost:8081/user/{userId}这个接口。大概的步骤是这样的：注册一个RestTemplate的实例到Spring容器修改order-service服务中的OrderService类中的queryOrderById方法，根据Order对象中的userId查询User将查询的User填充到Order对象，一起返回2.3.2.注册RestTemplate首先，我们在order-service服务中的OrderApplication启动类中，注册RestTemplate实例：package cn.itcast.order; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @MapperScan("cn.itcast.order.mapper") @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }2.3.3.实现远程调用修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法：2.4.提供者与消费者在服务调用关系中，会有两个不同的角色：服务提供者：一次业务中，被其它微服务调用的服务。（提供接口给其它微服务）服务消费者：一次业务中，调用其它微服务的服务。（调用其它微服务提供的接口）但是，服务提供者与服务消费者的角色并不是绝对的，而是相对于业务而言。如果服务A调用了服务B，而服务B又调用了服务C，服务B的角色是什么？对于A调用B的业务而言：A是服务消费者，B是服务提供者对于B调用C的业务而言：B是服务消费者，C是服务提供者因此，服务B既可以是服务提供者，也可以是服务消费者。3.Eureka注册中心假如我们的服务提供者user-service部署了多个实例，如图：大家思考几个问题：order-service在发起远程调用的时候，该如何得知user-service实例的ip地址和端口？有多个user-service实例地址，order-service调用时该如何选择？order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？3.1.Eureka的结构和作用这些问题都需要利用SpringCloud中的注册中心来解决，其中最广为人知的注册中心就是Eureka，其结构如下：回答之前的各个问题。问题1：order-service如何得知user-service实例地址？获取地址信息的流程如下：user-service服务实例启动后，将自己的信息注册到eureka-server（Eureka服务端）。这个叫服务注册eureka-server保存服务名称到服务实例地址列表的映射关系order-service根据服务名称，拉取实例地址列表。这个叫服务发现或服务拉取问题2：order-service如何从多个user-service实例中选择具体的实例？order-service从实例列表中利用负载均衡算法选中一个实例地址向该实例地址发起远程调用问题3：order-service如何得知某个user-service实例是否依然健康，是不是已经宕机？user-service会每隔一段时间（默认30秒）向eureka-server发起请求，报告自己状态，称为心跳当超过一定时间没有发送心跳时，eureka-server会认为微服务实例故障，将该实例从服务列表中剔除order-service拉取服务时，就能将故障实例排除了注意：一个微服务，既可以是服务提供者，又可以是服务消费者，因此eureka将服务注册、服务发现等功能统一封装到了eureka-client端因此，接下来我们动手实践的步骤包括：3.2.搭建eureka-server首先大家注册中心服务端：eureka-server，这必须是一个独立的微服务3.2.1.创建eureka-server服务在cloud-demo父工程下，创建一个子模块：填写模块信息：然后填写服务信息：3.2.2.引入eureka依赖引入SpringCloud为eureka提供的starter依赖：<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>3.2.3.编写启动类给eureka-server服务编写一个启动类，一定要添加一个@EnableEurekaServer注解，开启eureka的注册中心功能：package cn.itcast.eureka; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }3.2.4.编写配置文件编写一个application.yml文件，内容如下：server: port: 10086 spring: application: name: eureka-server eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka3.2.5.启动服务启动微服务，然后在浏览器访问：http://127.0.0.1:10086看到下面结果应该是成功了：3.3.服务注册下面，我们将user-service注册到eureka-server中去。1）引入依赖在user-service的pom文件中，引入下面的eureka-client依赖：<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>2）配置文件在user-service中，修改application.yml文件，添加服务名称、eureka地址：spring: application: name: userservice eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka3）启动多个user-service实例为了演示一个服务有多个实例的场景，我们添加一个SpringBoot的启动配置，再启动一个user-service。首先，复制原来的user-service启动配置：然后，在弹出的窗口中，填写信息：现在，SpringBoot窗口会出现两个user-service启动配置：不过，第一个是8081端口，第二个是8082端口。启动两个user-service实例：查看eureka-server管理页面：3.4.服务发现下面，我们将order-service的逻辑修改：向eureka-server拉取user-service的信息，实现服务发现。1）引入依赖之前说过，服务发现、服务注册统一都封装在eureka-client依赖，因此这一步与服务注册时一致。在order-service的pom文件中，引入下面的eureka-client依赖：<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>2）配置文件服务发现也需要知道eureka地址，因此第二步与服务注册一致，都是配置eureka信息：在order-service中，修改application.yml文件，添加服务名称、eureka地址：spring: application: name: orderservice eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka3）服务拉取和负载均衡最后，我们要去eureka-server中拉取user-service服务的实例列表，并且实现负载均衡。不过这些动作不用我们去做，只需要添加一些注解即可。在order-service的OrderApplication中，给RestTemplate这个Bean添加一个@LoadBalanced注解：修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径，用服务名代替ip、端口：spring会自动帮助我们从eureka-server端，根据userservice这个服务名称，获取实例列表，而后完成负载均衡。4.Ribbon负载均衡上一节中，我们添加了@LoadBalanced注解，即可实现负载均衡功能，这是什么原理呢？4.1.负载均衡原理SpringCloud底层其实是利用了一个名为Ribbon的组件，来实现负载均衡功能的。那么我们发出的请求明明是http://userservice/user/1，怎么变成了http://localhost:8081的呢？4.2.源码跟踪为什么我们只输入了service名称就可以访问了呢？之前还要获取ip和端口。显然有人帮我们根据service名称，获取到了服务实例的ip和端口。它就是LoadBalancerInterceptor，这个类会在对RestTemplate的请求进行拦截，然后从Eureka根据服务id获取服务列表，随后利用负载均衡算法得到真实的服务地址信息，替换服务id。我们进行源码跟踪：1）LoadBalancerIntercepor可以看到这里的intercept方法，拦截了用户的HttpRequest请求，然后做了几件事：request.getURI()：获取请求uri，本例中就是 http://user-service/user/8originalUri.getHost()：获取uri路径的主机名，其实就是服务id，user-servicethis.loadBalancer.execute()：处理服务id，和用户请求。这里的this.loadBalancer是LoadBalancerClient类型，我们继续跟入。2）LoadBalancerClient继续跟入execute方法：代码是这样的：getLoadBalancer(serviceId)：根据服务id获取ILoadBalancer，而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。getServer(loadBalancer)：利用内置的负载均衡算法，从服务列表中选择一个。本例中，可以看到获取了8082端口的服务放行后，再次访问并跟踪，发现获取的是8081： 果然实现了负载均衡。3）负载均衡策略IRule在刚才的代码中，可以看到获取服务使通过一个getServer方法来做负载均衡: 我们继续跟入：继续跟踪源码chooseServer方法，发现这么一段代码： 我们看看这个rule是谁： 这里的rule默认值是一个RoundRobinRule，看类的介绍： 这不就是轮询的意思嘛。到这里，整个负载均衡的流程我们就清楚了。4）总结SpringCloudRibbon的底层采用了一个拦截器，拦截了RestTemplate发出的请求，对地址做了修改。用一幅图来总结一下：基本流程如下：拦截我们的RestTemplate请求http://userservice/user/1RibbonLoadBalancerClient会从请求url中获取服务名称，也就是user-serviceDynamicServerListLoadBalancer根据user-service到eureka拉取服务列表eureka返回列表，localhost:8081、localhost:8082IRule利用内置负载均衡规则，从列表中选择一个，例如localhost:8081RibbonLoadBalancerClient修改请求地址，用localhost:8081替代userservice，得到http://localhost:8081/user/1，发起真实请求4.3.负载均衡策略4.3.1.负载均衡策略负载均衡的规则都定义在IRule接口中，而IRule有很多不同的实现类：不同规则的含义如下：内置负载均衡规则类规则描述RoundRobinRule简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。AvailabilityFilteringRule对以下两种服务器进行忽略： （1）在默认情况下，这台服务器如果3次连接失败，这台服务器就会被设置为“短路”状态。短路状态将持续30秒，如果再次连接失败，短路的持续时间就会几何级地增加。 （2）并发数过高的服务器。如果一个服务器的并发连接数过高，配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限，可以由客户端的..ActiveConnectionsLimit属性进行配置。WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长，这个服务器的权重就越小。这个规则会随机选择服务器，这个权重值会影响服务器的选择。ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类，这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。BestAvailableRule忽略那些短路的服务器，并选择并发数较低的服务器。RandomRule随机选择一个可用的服务器。RetryRule重试机制的选择逻辑默认的实现就是ZoneAvoidanceRule，是一种轮询方案4.3.2.自定义负载均衡策略通过定义IRule实现可以修改负载均衡规则，有两种方式：代码方式：在order-service中的OrderApplication类中，定义一个新的IRule：@Bean public IRule randomRule(){ return new RandomRule(); }配置文件方式：在order-service的application.yml文件中，添加新的配置也可以修改规则：userservice: # 给某个微服务配置负载均衡规则，这里是userservice服务 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 注意，一般用默认的负载均衡规则，不做修改。4.4.饥饿加载Ribbon默认是采用懒加载，即第一次访问时才会去创建LoadBalanceClient，请求时间会很长。而饥饿加载则会在项目启动时创建，降低第一次访问的耗时，通过下面配置开启饥饿加载：ribbon: eager-load: enabled: true clients: userservice5.Nacos注册中心国内公司一般都推崇阿里巴巴的技术，比如注册中心，SpringCloudAlibaba也推出了一个名为Nacos的注册中心。5.1.认识和安装NacosNacos是阿里巴巴的产品，现在是SpringCloud中的一个组件。相比Eureka功能更加丰富，在国内受欢迎程度较高。安装方式可以参考课前资料《Nacos安装指南.md》5.2.服务注册到nacosNacos是SpringCloudAlibaba的组件，而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说，并没有太大区别。主要差异在于：依赖不同服务地址不同1）引入依赖在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖：<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency>然后在user-service和order-service中的pom文件中引入nacos-discovery依赖：<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>注意：不要忘了注释掉eureka的依赖。2）配置nacos地址在user-service和order-service的application.yml中添加nacos地址：spring: cloud: nacos: server-addr: localhost:8848注意：不要忘了注释掉eureka的地址3）重启重启微服务后，登录nacos管理页面，可以看到微服务信息：5.3.服务分级存储模型一个服务可以有多个实例，例如我们的user-service，可以有:127.0.0.1:8081127.0.0.1:8082127.0.0.1:8083假如这些实例分布于全国各地的不同机房，例如：127.0.0.1:8081，在上海机房127.0.0.1:8082，在上海机房127.0.0.1:8083，在杭州机房Nacos就将同一机房内的实例 划分为一个集群。也就是说，user-service是服务，一个服务可以包含多个集群，如杭州、上海，每个集群下可以有多个实例，形成分级模型，如图：微服务互相访问时，应该尽可能访问同集群实例，因为本地访问速度更快。当本集群内不可用时，才访问其它集群。例如：杭州机房内的order-service应该优先访问同机房的user-service。5.3.1.给user-service配置集群修改user-service的application.yml文件，添加集群配置：spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ # 集群名称重启两个user-service实例后，我们可以在nacos控制台看到下面结果：我们再次复制一个user-service启动配置，添加属性：-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH配置如图所示：启动UserApplication3后再次查看nacos控制台：5.3.2.同集群优先的负载均衡默认的ZoneAvoidanceRule并不能实现根据同集群优先来实现负载均衡。因此Nacos中提供了一个NacosRule的实现，可以优先从同集群中挑选实例。1）给order-service配置集群信息修改order-service的application.yml文件，添加集群配置：spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ # 集群名称2）修改负载均衡规则修改order-service的application.yml文件，修改负载均衡规则：userservice: ribbon: NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 5.4.权重配置实际部署中会出现这样的场景：服务器设备性能有差异，部分实例所在机器性能较好，另一些较差，我们希望性能好的机器承担更多的用户请求。但默认情况下NacosRule是同集群内随机挑选，不会考虑机器的性能问题。因此，Nacos提供了权重配置来控制访问频率，权重越大则访问频率越高。在nacos控制台，找到user-service的实例列表，点击编辑，即可修改权重：在弹出的编辑窗口，修改权重：注意：如果权重修改为0，则该实例永远不会被访问5.5.环境隔离Nacos提供了namespace来实现环境隔离功能。nacos中可以有多个namespacenamespace下可以有group、service等不同namespace之间相互隔离，例如不同namespace的服务互相不可见5.5.1.创建namespace默认情况下，所有service、data、group都在同一个namespace，名为public：我们可以点击页面新增按钮，添加一个namespace：然后，填写表单：就能在页面看到一个新的namespace：5.5.2.给微服务配置namespace给微服务配置namespace只能通过修改配置来实现。例如，修改order-service的application.yml文件：spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: HZ namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间，填ID重启order-service后，访问控制台，可以看到下面的结果：此时访问order-service，因为namespace不同，会导致找不到userservice，控制台会报错：5.6.Nacos与Eureka的区别Nacos的服务实例分为两种l类型：临时实例：如果实例宕机超过一定时间，会从服务列表剔除，默认的类型。非临时实例：如果实例宕机，不会从服务列表剔除，也可以叫永久实例。配置一个服务实例为永久实例：spring: cloud: nacos: discovery: ephemeral: false # 设置为非临时实例Nacos和Eureka整体结构类似，服务注册、服务拉取、心跳等待，但是也存在一些差异：Nacos与eureka的共同点都支持服务注册和服务拉取都支持服务提供者心跳方式做健康检测Nacos与Eureka的区别Nacos支持服务端主动检测提供者状态：临时实例采用心跳模式，非临时实例采用主动检测模式临时实例心跳不正常会被剔除，非临时实例则不会被剔除Nacos支持服务列表变更的消息推送模式，服务列表更新更及时Nacos集群默认采用AP方式，当集群中存在非临时实例时，采用CP模式；Eureka采用AP方式
一.springBoot启动在Program arguments:输入指令--spring.profiles.active=dev二. springBoot启动在Active Profiles:输入dev三.springBoot启动在Environment variables: 输入spring.profiles.active=dev四.maven启动Environment variables: 输入spring.profiles.active=dev五.maven启动Skip tests下输入spring.profiles.active=dev
开发中，每次对类的修改，都需要重启服务，很浪费时间，影响效率。下面介绍一种springboot热部署的方法在pom文件中引入devtools依赖： <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency>这里多了一个optional选项，是为了防止将devtools依赖传递到其他模块中。当开发者将应用打包运行后，devtools会被自动禁用。同时需要在spring-boot-maven-plugin插件中进行如下的配置： <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> </plugin> </plugins>设置IDEA的配置自动构建项目在高级选项中勾选
学习目标[ ] 能够基于MyBatisPlus完成标准Dao开发[ ] 能够掌握MyBatisPlus的条件查询[ ] 能够掌握MyBatisPlus的字段映射与表名映射[ ] 能够掌握id生成策略控制[ ] 能够理解代码生成器的相关配置一、MyBatisPlus简介1. 入门案例问题导入MyBatisPlus环境搭建的步骤？1.1 SpringBoot整合MyBatisPlus入门程序①：创建新模块，选择Spring初始化，并配置模块相关基础信息 ②：选择当前模块需要使用的技术集（仅保留JDBC）③：手动添加MyBatisPlus起步依赖<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.16</version> </dependency>注意事项1：由于mp并未被收录到idea的系统内置配置，无法直接选择加入注意事项2：如果使用Druid数据源，需要导入对应坐标④：制作实体类与表结构（类名与表名对应，属性名与字段名对应）create database if not exists mybatisplus_db character set utf8; use mybatisplus_db; CREATE TABLE user ( id bigint(20) primary key auto_increment, name varchar(32) not null, password varchar(32) not null, age int(3) not null , tel varchar(32) not null ); insert into user values(null,'tom','123456',12,'12345678910'); insert into user values(null,'jack','123456',8,'12345678910'); insert into user values(null,'jerry','123456',15,'12345678910'); insert into user values(null,'tom','123456',9,'12345678910'); insert into user values(null,'snake','123456',28,'12345678910'); insert into user values(null,'张益达','123456',22,'12345678910'); insert into user values(null,'张大炮','123456',16,'12345678910');public class User { private Long id; private String name; private String password; private Integer age; private String tel; //自行添加getter、setter、toString()等方法 }⑤：设置Jdbc参数（application.yml）spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: root⑥：定义数据接口，继承BaseMapperpackage com.itheima.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.domain.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserDao extends BaseMapper<User> { } ⑦：测试类中注入dao接口，测试功能package com.itheima; import com.itheima.dao.UserDao; import com.itheima.domain.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest public class Mybatisplus01QuickstartApplicationTests { @Autowired private UserDao userDao; @Test void testGetAll() { List<User> userList = userDao.selectList(null); System.out.println(userList); } }2. MyBatisPlus概述问题导入通过入门案例制作，MyBatisPlus的优点有哪些？2.1 MyBatis介绍MyBatisPlus（简称MP）是基于MyBatis框架基础上开发的增强型工具，旨在简化开发、提高效率官网：https://mybatis.plus/ https://mp.baomidou.com/2.2 MyBatisPlus特性无侵入：只做增强不做改变，不会对现有工程产生影响强大的 CRUD 操作：内置通用 Mapper，少量配置即可实现单表CRUD 操作支持 Lambda：编写查询条件无需担心字段写错支持主键自动生成内置分页插件……二、标准数据层开发1. MyBatisPlus的CRUD操作package com.itheima; import com.itheima.dao.UserDao; import com.itheima.domain.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class Mybatisplus01QuickstartApplicationTests { @Autowired private UserDao userDao; @Test void testSave() { User user = new User(); user.setName("黑马程序员"); user.setPassword("itheima"); user.setAge(12); user.setTel("4006184000"); userDao.insert(user); } @Test void testDelete() { userDao.deleteById(1401856123725713409L); } @Test void testUpdate() { User user = new User(); user.setId(1L); user.setName("Tom888"); user.setPassword("tom888"); userDao.updateById(user); } @Test void testGetById() { User user = userDao.selectById(2L); System.out.println(user); } @Test void testGetAll() { List<User> userList = userDao.selectList(null); System.out.println(userList); } } 2. Lombok插件介绍问题导入有什么简单的办法可以自动生成实体类的GET、SET方法？Lombok，一个Java类库，提供了一组注解，简化POJO实体类开发。<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency>常用注解：==@Data==，为当前实体类在编译期设置对应的get/set方法，无参/无参构造方法，toString方法，hashCode方法，equals方法等package com.itheima.domain; import lombok.*; /* 1 生成getter和setter方法：@Getter、@Setter 生成toString方法：@ToString 生成equals和hashcode方法：@EqualsAndHashCode 2 统一成以上所有：@Data 3 生成空参构造： @NoArgsConstructor 生成全参构造： @AllArgsConstructor 4 lombok还给我们提供了builder的方式创建对象,好处就是可以链式编程。 @Builder【扩展】 */ @Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; } 3. MyBatisPlus分页功能问题导入思考一下Mybatis分页插件是如何用的？3.1 分页功能接口3.2 MyBatisPlus分页使用①：设置分页拦截器作为Spring管理的beanpackage com.itheima.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ //1 创建MybatisPlusInterceptor拦截器对象 MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor(); //2 添加分页拦截器 mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return mpInterceptor; } }②：执行分页查询//分页查询 @Test void testSelectPage(){ //1 创建IPage分页对象,设置分页参数 IPage<User> page=new Page<>(1,3); //2 执行分页查询 userDao.selectPage(page,null); //3 获取分页结果 System.out.println("当前页码值："+page.getCurrent()); System.out.println("每页显示数："+page.getSize()); System.out.println("总页数："+page.getPages()); System.out.println("总条数："+page.getTotal()); System.out.println("当前页数据："+page.getRecords()); }3.3 开启MyBatisPlus日志spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC username: root password: root # 开启mp的日志（输出到控制台） mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl3.4 解决日志打印过多问题3.4.1 取消初始化spring日志打印做法：在resources下新建一个logback.xml文件，名称固定，内容如下：<?xml version="1.0" encoding="UTF-8"?> <configuration> </configuration>关于logback参考播客：https://www.jianshu.com/p/75f9d11ae0113.4.2 取消SpringBoot启动banner图标spring: main: banner-mode: off # 关闭SpringBoot启动图标(banner)3.4.3 取消MybatisPlus启动banner图标# mybatis-plus日志控制台输出 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: off # 关闭mybatisplus启动图标三、DQL编程控制1. 条件查询方式MyBatisPlus将书写复杂的SQL查询条件进行了封装，使用编程的形式完成查询条件的组合1.1 条件查询1.1.1 方式一：按条件查询//方式一：按条件查询 QueryWrapper<User> qw=new QueryWrapper<>(); qw.lt("age", 18); List<User> userList = userDao.selectList(qw); System.out.println(userList);1.1.2 方式二：lambda格式按条件查询//方式二：lambda格式按条件查询 QueryWrapper<User> qw = new QueryWrapper<User>(); qw.lambda().lt(User::getAge, 10); List<User> userList = userDao.selectList(qw); System.out.println(userList);1.1.3 方式三：lambda格式按条件查询（推荐）//方式三：lambda格式按条件查询 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.lt(User::getAge, 10); List<User> userList = userDao.selectList(lqw); System.out.println(userList);1.2 组合条件1.2.1 并且关系（and）//并且关系 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //并且关系：10到30岁之间 lqw.lt(User::getAge, 30).gt(User::getAge, 10); List<User> userList = userDao.selectList(lqw); System.out.println(userList);1.2.2 或者关系（or）//或者关系 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //或者关系：小于10岁或者大于30岁 lqw.lt(User::getAge, 10).or().gt(User::getAge, 30); List<User> userList = userDao.selectList(lqw); System.out.println(userList);1.3 NULL值处理问题导入如下搜索场景，在多条件查询中，有条件的值为空应该怎么解决？1.3.1 if语句控制条件追加Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了 Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); if(minAge!=null){ lqw.gt(User::getAge, minAge); } if(maxAge!=null){ lqw.lt(User::getAge, maxAge); } List<User> userList = userDao.selectList(lqw); userList.forEach(System.out::println);1.3.2 条件参数控制Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了 Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //参数1：如果表达式为true，那么查询才使用该条件 lqw.gt(minAge!=null,User::getAge, minAge); lqw.lt(maxAge!=null,User::getAge, maxAge); List<User> userList = userDao.selectList(lqw); userList.forEach(System.out::println);1.3.3 条件参数控制（链式编程）Integer minAge=10; //将来有用户传递进来,此处简化成直接定义变量了 Integer maxAge=null; //将来有用户传递进来,此处简化成直接定义变量了 LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //参数1：如果表达式为true，那么查询才使用该条件 lqw.gt(minAge!=null,User::getAge, minAge) .lt(maxAge!=null,User::getAge, maxAge); List<User> userList = userDao.selectList(lqw); userList.forEach(System.out::println);2. 查询投影-设置【查询字段、分组、分页】2.1 查询结果包含模型类中部分属性/*LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); lqw.select(User::getId, User::getName, User::getAge);*/ //或者 QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("id", "name", "age", "tel"); List<User> userList = userDao.selectList(lqw); System.out.println(userList);2.2 查询结果包含模型类中未定义的属性QueryWrapper<User> lqw = new QueryWrapper<User>(); lqw.select("count(*) as count, tel"); lqw.groupBy("tel"); List<Map<String, Object>> userList = userDao.selectMaps(lqw); System.out.println(userList);3. 查询条件设定问题导入多条件查询有哪些组合？范围匹配（> 、 = 、between）模糊匹配（like）空判定（null）包含性匹配（in）分组（group）排序（order）……3.1 查询条件用户登录（eq匹配）LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //等同于= lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry"); User loginUser = userDao.selectOne(lqw); System.out.println(loginUser);购物设定价格区间、户籍设定年龄区间（le ge匹配 或 between匹配）LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //范围查询 lt le gt ge eq between lqw.between(User::getAge, 10, 30); List<User> userList = userDao.selectList(lqw); System.out.println(userList);查信息，搜索新闻（非全文检索版：like匹配）LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>(); //模糊匹配 like lqw.likeLeft(User::getName, "J"); List<User> userList = userDao.selectList(lqw); System.out.println(userList);统计报表（分组查询聚合函数）QueryWrapper<User> qw = new QueryWrapper<User>(); qw.select("gender","count(*) as nums"); qw.groupBy("gender"); List<Map<String, Object>> maps = userDao.selectMaps(qw); System.out.println(maps);3.2 查询API更多查询条件设置参看 https://mybatis.plus/guide/wrapper.html#abstractwrapper3.3 练习：MyBatisPlus练习题目：基于MyBatisPlus_Ex1模块，完成Top5功能的开发。说明：①：Top5指根据销售量排序（提示：对销售量进行降序排序）②：Top5是仅获取前5条数据（提示：使用分页功能控制数据显示数量）4. 字段映射与表名映射问题导入思考表的字段和实体类的属性不对应，查询会怎么样？4.1 问题一：表字段与编码属性设计不同步在模型类属性上方，使用@TableField属性注解，通过==value==属性，设置当前属性对应的数据库表中的字段关系。4.2 问题二：编码中添加了数据库中未定义的属性在模型类属性上方，使用@TableField注解，通过==exist==属性，设置属性在数据库表字段中是否存在，默认为true。此属性无法与value合并使用。4.3 问题三：采用默认查询开放了更多的字段查看权限在模型类属性上方，使用@TableField注解，通过==select==属性：设置该属性是否参与查询。此属性与select()映射配置不冲突。4.4 问题四：表名与编码开发设计不同步在模型类上方，使用@TableName注解，通过==value==属性，设置当前类对应的数据库表名称。@Data @TableName("tbl_user") public class User { /* id为Long类型，因为数据库中id为bigint类型， 并且mybatis有自己的一套id生成方案，生成出来的id必须是Long类型 */ private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; @TableField(exist = false) //表示online字段不参与CRUD操作 private Boolean online; }四、DML编程控制1. id生成策略控制（Insert）问题导入主键生成的策略有哪几种方式？不同的表应用不同的id生成策略日志：自增（1,2,3,4，……）购物订单：特殊规则（FQ23948AK3843）外卖单：关联地区日期等信息（10 04 20200314 34 91）关系表：可省略id……1.1 id生成策略控制（@TableId注解）名称：@TableId类型：属性注解位置：模型类中用于表示主键的属性定义上方作用：设置当前类中主键属性的生成策略相关属性​ type：设置主键属性的生成策略，值参照IdType枚举值1.2 全局策略配置mybatis-plus: global-config: db-config: id-type: assign_id table-prefix: tbl_id生成策略全局配置表名前缀全局配置2. 多记录操作（批量Delete/Select）问题导入MyBatisPlus是否支持批量操作？2.1 按照主键删除多条记录//删除指定多条数据 List<Long> list = new ArrayList<>(); list.add(1402551342481838081L); list.add(1402553134049501186L); list.add(1402553619611430913L); userDao.deleteBatchIds(list);2.2 根据主键查询多条记录//查询指定多条数据 List<Long> list = new ArrayList<>(); list.add(1L); list.add(3L); list.add(4L); userDao.selectBatchIds(list);3. 逻辑删除（Delete/Update）问题导入在实际环境中，如果想删除一条数据，是否会真的从数据库中删除该条数据？删除操作业务问题：业务数据从数据库中丢弃逻辑删除：为数据设置是否可用状态字段，删除时设置状态字段为不可用状态，数据保留在数据库中3.1 逻辑删除案例①：数据库表中添加逻辑删除标记字段②：实体类中添加对应字段，并设定当前字段为逻辑删除标记字段package com.itheima.domain; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; @Data public class User { private Long id; //逻辑删除字段，标记当前记录是否被删除 @TableLogic private Integer deleted; }③：配置逻辑删除字面值mybatis-plus: global-config: db-config: table-prefix: tbl_ # 逻辑删除字段名 logic-delete-field: deleted # 逻辑删除字面值：未删除为0 logic-not-delete-value: 0 # 逻辑删除字面值：删除为1 logic-delete-value: 1逻辑删除本质：逻辑删除的本质其实是修改操作。如果加了逻辑删除字段，查询数据时也会自动带上逻辑删除字段。4. 乐观锁（Update）问题导入乐观锁主张的思想是什么？业务并发现象带来的问题：秒杀4.1 乐观锁案例①：数据库表中添加锁标记字段②：实体类中添加对应字段，并设定当前字段为逻辑删除标记字段package com.itheima.domain; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableLogic; import com.baomidou.mybatisplus.annotation.Version; import lombok.Data; @Data public class User { private Long id; @Version private Integer version; } ③：配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装package com.itheima.config; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加乐观锁拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } } ④：使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行@Test public void testUpdate() { /*User user = new User(); user.setId(3L); user.setName("Jock666"); user.setVersion(1); userDao.updateById(user);*/ //1.先通过要修改的数据id将当前数据查询出来 //User user = userDao.selectById(3L); //2.将要修改的属性逐一设置进去 //user.setName("Jock888"); //userDao.updateById(user); //1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(3L); //version=3 User user2 = userDao.selectById(3L); //version=3 user2.setName("Jock aaa"); userDao.updateById(user2); //version=>4 user.setName("Jock bbb"); userDao.updateById(user); //verion=3?条件还成立吗？ }五、快速开发-代码生成器问题导入如果只给一张表的字段信息，能够推演出Domain、Dao层的代码？1. MyBatisPlus提供模板Mapper接口模板实体对象类模板2. 工程搭建和基本代码编写第一步：创建SpringBoot工程，添加代码生成器相关依赖，其他依赖自行添加 <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>第二步：编写代码生成器类package com.itheima; import com.baomidou.mybatisplus.generator.AutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; public class Generator { public static void main(String[] args) { //1. 创建代码生成器对象，执行生成代码操作 AutoGenerator autoGenerator = new AutoGenerator(); //2. 数据源相关配置：读取数据库中的信息，根据数据库表结构生成代码 DataSourceConfig dataSource = new DataSourceConfig(); dataSource.setDriverName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC"); dataSource.setUsername("root"); dataSource.setPassword("root"); autoGenerator.setDataSource(dataSource); //3. 执行生成操作 autoGenerator.execute(); } } 3. 开发者自定义配置设置全局配置//设置全局配置 GlobalConfig globalConfig = new GlobalConfig(); globalConfig.setOutputDir(System.getProperty("user.dir")+"/mybatisplus_04_generator/src/main/java"); //设置代码生成位置 globalConfig.setOpen(false); //设置生成完毕后是否打开生成代码所在的目录 globalConfig.setAuthor("黑马程序员"); //设置作者 globalConfig.setFileOverride(true); //设置是否覆盖原始生成的文件 globalConfig.setMapperName("%sDao"); //设置数据层接口名，%s为占位符，指代模块名称 globalConfig.setIdType(IdType.ASSIGN_ID); //设置Id生成策略 autoGenerator.setGlobalConfig(globalConfig);设置包名相关配置//设置包名相关配置 PackageConfig packageInfo = new PackageConfig(); packageInfo.setParent("com.aaa"); //设置生成的包名，与代码所在位置不冲突，二者叠加组成完整路径 packageInfo.setEntity("domain"); //设置实体类包名 packageInfo.setMapper("dao"); //设置数据层包名 autoGenerator.setPackageInfo(packageInfo);策略设置//策略设置 StrategyConfig strategyConfig = new StrategyConfig(); strategyConfig.setInclude("tbl_user"); //设置当前参与生成的表名，参数为可变参数 strategyConfig.setTablePrefix("tbl_"); //设置数据库表的前缀名称，模块名 = 数据库表名 - 前缀名 例如： User = tbl_user - tbl_ strategyConfig.setRestControllerStyle(true); //设置是否启用Rest风格 strategyConfig.setVersionFieldName("version"); //设置乐观锁字段名 strategyConfig.setLogicDeleteFieldName("deleted"); //设置逻辑删除字段名 strategyConfig.setEntityLombokModel(true); //设置是否启用lombok autoGenerator.setStrategy(strategyConfig);说明：在资料中也提供了CodeGenerator代码生成器类，根据实际情况修改后可以直接使用。
https://www.yuque.com/books/share/328bf500-e69a-464c-86d4-acbd961fcfd8?# 《JavaWeb开发》密码：ti3w
学习目标[ ] 基于SpringBoot框架的程序开发步骤[ ] 熟练使用SpringBoot配置信息修改服务器配置[ ] 基于SpringBoot的完成SSM整合项目开发一、SpringBoot简介1. 入门案例问题导入SpringMVC的HelloWord程序大家还记得吗？SpringBoot是由Pivotal团队提供的全新框架，其设计目的是用来简化Spring应用的初始搭建以及开发过程原生开发SpringMVC程序过程1.1 入门案例开发步骤①：创建新模块，选择Spring初始化，并配置模块相关基础信息②：选择当前模块需要使用的技术集③：开发控制器类@RestController @RequestMapping("/books") public class BookController { @GetMapping("/{id}") public String getById(@PathVariable Integer id) { System.out.println("id ==> " + id); return "hello , spring boot! "; } }④：运行自动生成的Application类最简SpringBoot程序所包含的基础文件<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <groupId>com.itheima</groupId> <artifactId>springboot-01-quickstart</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project> @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }Spring程序与SpringBoot程序对比注意事项：基于idea开发SpringBoot程序需要确保联网且能够加载到程序框架结构1.2 基于SpringBoot官网创建项目1.3 SpringBoot项目快速启动① 对SpringBoot项目打包（执行Maven构建指令package）② 执行启动指令java -jar springboot_01_quickstart.jar # 项目的名称根据实际情况修改注意事项：jar支持命令行启动需要依赖maven插件支持，请确认打包时是否具有SpringBoot对应的maven插件。<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>2. SpringBoot概述问题导入学习了SpringBoot入门案例之后，感觉对比SpringMVC哪一个更加方便简洁？SpringBoot是由Pivotal团队提供的全新框架，其设计目的是用来简化Spring应用的初始搭建以及开发过程Spring程序缺点配置繁琐依赖设置繁琐SpringBoot程序优点自动配置起步依赖（简化依赖配置）辅助功能（内置服务器，……）2.1 起步依赖starterSpringBoot中常见项目名称，定义了当前项目使用的所有项目坐标，以达到减少依赖配置的目的<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <groupId>com.itheima</groupId> <artifactId>springboot-01-quickstart</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> </project><project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.5.0</version> <packaging>pom</packaging> <properties> <servlet-api.version>4.0.1</servlet-api.version> ... </properties> </project> parent所有SpringBoot项目要继承的项目，定义了若干个坐标版本号（依赖管理，而非依赖），以达到减少依赖冲突的目的spring-boot-starter-parent（2.5.0）与 spring-boot-starter-parent（2.4.6）共计57处坐标版本不同<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.5.0</version> </parent> <artifactId>spring-boot-starter-parent</artifactId> <packaging>pom</packaging> ... </project>实际开发使用任意坐标时，仅书写GAV中的G和A，V由SpringBoot提供如发生坐标错误，再指定version（要小心版本冲突）<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>${servlet-api.version}</version> </dependency><?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.0</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project> 2.2 辅助功能SpringBoot程序启动@SpringBootApplication public class Springboot01QuickstartApplication { public static void main(String[] args) { SpringApplication.run(Springboot01QuickstartApplication.class, args); } }SpringBoot在创建项目时，采用jar的打包方式SpringBoot的引导类是项目的入口，运行main方法就可以启动项目使用maven依赖管理变更起步依赖项Jetty比Tomcat更轻量级，可扩展性更强（相较于Tomcat），谷歌应用引擎（GAE）已经全面切换为Jetty<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> </dependencies> 二、基础配置1. 配置文件格式问题导入框架常见的配置文件有哪几种形式？1.1 修改服务器端口http://localhost:8080/books/1 >>> http://localhost/books/1SpringBoot提供了多种属性配置方式application.propertiesserver.port=80application.ymlserver: port: 81application.yamlserver: port: 821.2 自动提示功能消失解决方案操作步骤： 1.3 SpringBoot配置文件加载顺序（了解）application.properties > application.yml > application.yaml注意事项：SpringBoot核心配置文件名为applicationSpringBoot内置属性过多，且所有属性集中在一起修改，在使用时，通过提示键+关键字修改属性2. yaml问题导入什么是yaml，和properties有什么区别？YAML（YAML Ain't Markup Language），一种数据序列化格式优点：容易阅读容易与脚本语言交互以数据为核心，重数据轻格式YAML文件扩展名.yml（主流）.yaml2.1 yaml语法规则大小写敏感属性层级关系使用多行描述，每行结尾使用冒号结束使用缩进表示层级关系，同层级左侧对齐，只允许使用空格（不允许使用Tab键）属性值前面添加空格（属性名与属性值之间使用冒号+空格作为分隔）表示注释核心规则：数据前面要加空格与冒号隔开2.2 yaml数组数据数组数据在数据书写位置的下方使用减号作为数据开始符号，每行书写一个数据，减号与数据间空格分隔2.3 yaml数据读取使用@Value读取单个数据，属性名引用方式：${一级属性名.二级属性名……}封装全部数据到Environment对象==自定义对象封装指定数据【常用】==public class Enterprise { private String name; private Integer age; private String tel; private String[] subject; //自行添加getter、setter、toString()等方法 }自定义对象封装数据警告解决方案<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>3. 多环境开发配置问题导入在实际开发中，项目的开发环境、测试环境、生产环境的配置信息是否会一致？如何快速切换？3.1 多环境启动配置yaml文件多环境启动 properties文件多环境启动#主启动配置文件 application.properties spring.profiles.active=pro#环境分类配置文件 application-pro.properties server.port=80#环境分类配置文件 application-dev.properties server.port=81#环境分类配置文件application-test.properties server.port=823.2 多环境启动命令格式带参数启动SpringBootjava –jar springboot.jar --spring.profiles.active=test java –jar springboot.jar --server.port=88 java –jar springboot.jar --server.port=88 --spring.profiles.active=test参数加载优先顺序参看文档：https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config3.3 多环境开发控制Maven与SpringBoot多环境兼容（步骤）①：Maven中设置多环境属性<profiles> <profile> <id>dev_env</id> <properties> <profile.active>dev</profile.active> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>pro_env</id> <properties> <profile.active>pro</profile.active> </properties> </profile> <profile> <id>test_env</id> <properties> <profile.active>test</profile.active> </properties> </profile> </profiles>②：SpringBoot中引用Maven属性③：执行Maven打包指令Maven指令执行完毕后，生成了对应的包，其中类参与编译，但是配置文件并没有编译，而是复制到包中解决思路：对于源码中非java类的操作要求加载Maven对应的属性，解析${}占位符④：对资源文件开启对默认占位符的解析<build> <plugins> <plugin> <artifactId>maven-resources-plugin</artifactId> <configuration> <encoding>utf-8</encoding> <useDefaultDelimiters>true</useDefaultDelimiters> </configuration> </plugin> </plugins> </build>Maven打包加载到属性，打包顺利通过4. 配置文件分类问题导入SpringBoot的配置文件可以放在项目的哪些地方？java –jar springboot.jar --spring.profiles.active=test --server.port=85 --server.servlet.context-path=/heima --server.tomcat.connection-timeout=-1 ... ...SpringBoot中4级配置文件1级： file ：config/application.yml 【最高】2级： file ：application.yml3级：classpath：config/application.yml4级：classpath：application.yml 【最低】作用：1级与2级留做系统打包后设置通用属性3级与4级用于系统开发阶段设置通用属性三、整合第三方技术1. 整合JUnit问题导入回忆一下Spring整合JUnit的步骤？1.1 Spring整合JUnit（复习）1.2 SpringBoot整合JUnit【第一步】添加整合junit起步依赖(可以直接勾选)<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>【第二步】编写测试类，默认自动生成了一个@SpringBootTest class Springboot07JunitApplicationTests { @Autowired private BookService bookService; @Test public void testSave() { bookService.save(); } }2. 基于SpringBoot实现SSM整合问题导入回忆一下Spring整合MyBatis的核心思想？2.1 Spring整合MyBatis（复习）SpringConfig导入JdbcConfig导入MyBatisConfig@Configuration @ComponentScan("com.itheima") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class, MyBatisConfig.class}) public class SpringConfig { }JDBCConfig定义数据源（加载properties配置项：driver、url、username、password）#jdbc.properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_db jdbc.username=root jdbc.password=itheimapublic class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource getDataSource() { DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } } MyBatisConfig定义SqlSessionFactoryBean定义映射配置@Bean public SqlSessionFactoryBean getSqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.itheima.domain"); ssfb.setDataSource(dataSource); return ssfb; }@Bean public MapperScannerConfigurer getMapperScannerConfigurer() { MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.itheima.dao"); return msc; }2.2 SpringBoot整合MyBatisSpringBoot整合Spring（不存在）SpringBoot整合SpringMVC（不存在）SpringBoot整合MyBatis（主要）①：创建新模块，选择Spring初始化，并配置模块相关基础信息②：选择当前模块需要使用的技术集（MyBatis、MySQL）③：设置数据源参数spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC username: root password: root type: com.alibaba.druid.pool.DruidDataSource注意事项：SpringBoot版本低于2.4.3(不含)，Mysql驱动版本大于8.0时，需要在url连接串中配置时区，或在MySQL数据库端配置时区解决此问题jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC④：定义数据层接口与映射配置@Mapper public interface UserDao { @Select("select * from tbl_book where id=#{id}") Book getById(Integer id); }⑤：测试类中注入dao接口，测试功能@SpringBootTest class Springboot08MybatisApplicationTests { @Autowired private BookDao bookDao; @Test public void testGetById() { Book book = bookDao.getById(1); System.out.println(book); } }2.3 案例-SpringBoot实现ssm整合【第一步】创建SpringBoot工程，添加druid依赖 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.6</version> </dependency>【第二步】复制springmvc_11_page工程各种资源(主java类、页面、测试类)【第三步】删除config包中的所有配置，在BookDao接口上加@Mapper注解//todo 3 在BookDao接口上加@Mapper注解，让SpringBoot给接口创建代理对象 @Mapper public interface BookDao { //... }【第四步】将application.properties修改成application.yml，配置端口号和连接参数server: port: 80 # todo 4 配置数据库连接参数 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: root type: com.alibaba.druid.pool.DruidDataSource【第五步】修改BookServiceTest配置类，进行配置// todo 5 修改单元测试类，添加@SpringBootTest主键，修复@Test注解导包 @SpringBootTest public class BookServiceTest { @Autowired private BookService bookService; @Test public void testGetById(){ Book book = bookService.getById(2); //传递参数1会抛出异常 System.out.println(book); } @Test public void testGetAll(){ List<Book> all = bookService.getAll(); System.out.println(all); } }【第六步】在static目录中提供index.html页面，跳转到"pages/books.html" 最后：运行引导类即可访问