SpringBoot2-day2

常见参数注解使用:
@PathVariable 路径变量
@RequestHeader 获取请求头
@RequestParam 获取请求参数 (指url问号后的参数)
@CookieValue 获取Cookie值
@RequestBody 获取请求头【POST】
@RequestAttribute 获取request域属性
@MatrixVariable 矩阵变量
@ModelAttribute

	@RestController //相当于@Controller+@ResponseBody两个注解的结合,返回Json数据不需要在方法前面加@ResponseBody注解
public class ParameterTestController {


    //  car/2/owner/zhangsan
    @GetMapping("/car/{id}/owner/{username}")
    public Map<String,Object> getCar(@PathVariable("id") Integer id,
                                     @PathVariable("username") String name,
                                     @PathVariable Map<String,String> pv,
                                     @RequestHeader("User-Agent") String userAgent,
                                     @RequestHeader Map<String,String> header,
                                     @RequestParam("age") Integer age,
                                     @RequestParam("inters") List<String> inters,
                                     @RequestParam Map<String,String> params,
                                     @CookieValue("_ga") String _ga,
                                     @CookieValue("_ga") Cookie cookie){

        Map<String,Object> map = new HashMap<>();

//        map.put("id",id);
//        map.put("name",name);
//        map.put("pv",pv);
//        map.put("userAgent",userAgent);
//        map.put("headers",header);
        map.put("age",age);
        map.put("inters",inters);
        map.put("params",params);
        map.put("_ga",_ga);
        System.out.println(cookie.getName()+"===>"+cookie.getValue());
        return map;
    }


    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
        Map<String,Object> map = new HashMap<>();
        map.put("content",content);
        return map;
    }
}

如果cookie被禁用了,session里面的内容怎么使用?
在页面开发中,session.set(a,b) —> jsessionid ----> cookie ----> 每次发送请求携带

如果禁用cookie,使用Url重写:/abc;jsessionid=xxxx 把cookie的值使用矩阵变量的方式进行传递

需要先手动开启矩阵变量
矩阵变量必须有url路径变量才能被解析

@Configuration(proxyBeanMethods = false)
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {

        UrlPathHelper urlPathHelper = new UrlPathHelper();
        // 不移除;后面的内容。矩阵变量功能就可以生效
        urlPathHelper.setRemoveSemicolonContent(false);
        configurer.setUrlPathHelper(urlPathHelper);
    }
}

创建返回WebMvcConfigurerBean:

@Configuration(proxyBeanMethods = false)
public class WebConfig{
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
        return new WebMvcConfigurer() {
                        @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                // 不移除;后面的内容。矩阵变量功能就可以生效
                urlPathHelper.setRemoveSemicolonContent(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }
        }
    }
}

矩阵变量必须有url路径变量才能被解析
@MaxtrixVariable 的使用

@RestController
public class ParameterTestController {

    ///cars/sell;low=34;brand=byd,audi,yd
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
                        @MatrixVariable("brand") List<String> brand,
                        @PathVariable("path") String path){
        Map<String,Object> map = new HashMap<>();

        map.put("low",low);
        map.put("brand",brand);
        map.put("path",path);
        return map;
    }

    // /boss/1;age=20/2;age=10

    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
                    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
        Map<String,Object> map = new HashMap<>();

        map.put("bossAge",bossAge);
        map.put("empAge",empAge);
        return map;

    }

}

视图解析

Springboot默认不支持jsp,需要引入第三方模板引擎技术实现页面渲染。

Thymeleaf介绍

Thymeleaf是用来开发Web和独立环境项目的服务器端的Java模版引擎

Spring官方支持的服务的渲染模板中,并不包含jsp。而是Thymeleaf和Freemarker等,而Thymeleaf与SpringMVC的视图技术,及SpringBoot的自动化配置集成非常完美,几乎没有任何成本,你只用关注Thymeleaf的语法即可。

Thymeleaf语法

设置属性值:

设置单个值

<input type="submit" value="subscribe!" th:attr="value=#{subscribe.submit}"/>

设置多个值

<img src="../../images/logo.png" th:attr="src=@{/images/logo.png},title=#{logo}"

以上两个的代替写法 th:xxxx

<input type="submit" value="subscribe!" th:value="#{subscribe:submit}"/>
<form action="subscribe.html" th:action="@{/subscribe}">  
</form>

Themeleaf使用

  1. 引入Starter
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
</dependency>

自动配好的策略:

  • 所有thymeleaf的配置值都在ThymeleafProperties
  • 配置好了SpringTemplateEngine
  • 配置好了ThymeleafViewResolve
  • 只需要直接开发页面 页面放在templates下
public static final String DEFAULT_PREFIX = "classpath:/templates/";//网页放在这路径下

public static final String DEFAULT_SUFFIX = ".html";//xxx.html

测试:

在Template路径下新建html页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}">哈哈</h1>
<h2>
    <a href="www.baidu.com" th:href="${link}">去豆瓣1</a> <br/>
    <a href="www.baidu.com" th:href="@{link}">去豆瓣2</a> <br/>

</h2>
</body>
</html>

在Controller下新建ViewTestController类:

package com.yutou.boot05web01.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ViewTestController {

    @GetMapping("/yutou")//通过localhost:8080/yutou访问
    public String yutou(Model model){

        //model中的数据会被放到请求域中 request.setAttribute("a",aa)
        model.addAttribute("msg","你好 yutou");
        model.addAttribute("link","http://www.douban.com");
        return "success";
    }
}

web后台管理系统基本功能

登录:

@Controller
public class IndexController {

    //来登录页
    @GetMapping(value = {"/","login"})
    public String loginPage(){

        return "login";
    }
    @PostMapping("/login")
    public String main(User user, HttpSession session, Model model){

        if (StringUtils.hasLength(user.getUserName()) && "123456".equals(user.getPassword())){
            //把登录成功的用户保存起来
            session.setAttribute("loginUser",user);
            //登录成功重定向到main.html,防止表单重复提交
            return "redirect:/main.html";
        }else {
            model.addAttribute("msg","账户密码错误");
            //回到登录页面
            return "login";
        }

    }

    //去main页面
    @GetMapping("/main.html")
    public String mainPage(HttpSession session,Model model){
        //是否登录,拦截器:过滤器
        Object loginUser = session.getAttribute("loginUser");
        if (loginUser != null){
            return "main";
        }else {
            //回到登录页面
            model.addAttribute("msg","请重新登录");
            return "login";
        }

    }
}

login.html页面:

 <input type="text" name="userName" class="form-control" placeholder="用户名" autofocus>
 <input type="password" name="password" class="form-control" placeholder="密码">

抽取公共页面:

common.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:fragment="commonheader">
    <!--common-->
    <link href="css/style.css" th:href="@{/css/style.css}" rel="stylesheet">
    <link href="css/style-responsive.css" th:href="@{/css/style-responsive.css}" rel="stylesheet">

    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
    <script src="js/html5shiv.js" th:src="@{/js/html5shiv.js}"></script>
    <script src="js/respond.min.js" th:src="@{/js/respond.min.js}"></script>
    <![endif]-->
</head>
<body>
<!-- left side start-->
<div id="leftmenu" class="left-side sticky-left-side">
	...
    <!-- header section start-->
    <div th:fragment="headmenu" class="header-section">
          ...
        </div>
    <!-- header section end-->

<div id="commonscript">
    <!-- Placed js at the end of the document so the pages load faster -->
    <script th:src="@{/js/jquery-1.10.2.min.js}"></script>
    <script th:src="@{/js/jquery-ui-1.9.2.custom.min.js}"></script>
    <script th:src="@{/js/jquery-migrate-1.2.1.min.js}"></script>
    <script th:src="@{/js/bootstrap.min.js}"></script>
    <script th:src="@{/js/modernizr.min.js}"></script>
    <script th:src="@{/js/jquery.nicescroll.js}"></script>
    <script th:src="@{/js/scripts.js}"></script>
</div>
<!--common scripts for all pages-->

</div>
</body>
</html>

将四个公共的部分抽取出来,分别为commonheader,leftmenu,headmenu,commonscript

在其他有这些公共部分的页面用以下语句代替:

<div th:include="common :: commonheader"></div>
<div th:replace="common :: #leftmenu"></div>
<div th:replace="common :: headmenu"></div>
<div th:replace="common :: #commonscript"></div>

表格内容的遍历

 @GetMapping("/dynamic_table")
    public String dynamic_table(Model model){
        //表格内容的遍历
        List<User> users = Arrays.asList(new User("zhangsan", "123456"),
                new User("list", "123444"),
                new User("haha", "aaaaa"),
                new User("hehe", "aaddd")
        );
        model.addAttribute("users",users);
        return "table/dynamic_table";
    }

页面表格:

<thead>
      <tr>
           <th>#</th>
           <th>用户名</th>
           <th>密码</th>
      </tr>
</thead>
<tbody>
      <tr class="gradeX" th:each="user,stats:${users}">
      <td th:text="${stats.count}">Trident</td>
      <td th:text="${user.userName}"> Internet</td>
      <td >[[${user.password}]]</td>
</tr>
</tfoot>

拦截器

  1. 编写一个拦截器实现HandlerInterceptor
  2. 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
  3. 指定拦截规则【如果是拦截所有,静态资源也会被拦截】
//登录检查
//1.配置好拦截器需要拦截哪些请求
//2.把这些配置放在容器中
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
    //目标方法执行之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        log.info("拦截的请求路径是()",requestURI);

        //登录检查逻辑
        HttpSession session = request.getSession();

        Object loginUser = session.getAttribute("loginUser");

        if (loginUser != null){
            //放行
            return true;
        }
        //拦截住,未登录,跳转到登录页
        session.setAttribute("msg","请先登录");
        response.sendRedirect("/");
//        request.getRequestDispatcher("/").forward(request,response);
        return false;
    }


    //目标方法执行完成以后
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.info("postHandler执行",modelAndView);

}

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.info("afterCompletion执行异常",ex);
    }
}
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .addPathPatterns("/**") //所有请求都被拦截,包括静态资源
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
}

文件上传

文件上传Controller类:

//文件上传测试
@Controller
@Slf4j
public class FormTestController {

    @GetMapping("/form_layouts")
    public String form_layout(){
        return "form/form_layouts";
    }
    //Mul
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
                         @RequestParam("username") String username,
                         @RequestPart("headerImg")MultipartFile headerImg,
                         @RequestPart("photos") MultipartFile[] photos) throws Exception{
        log.info("上传的信息:email={},username={},header={}},photos={}}",
                email,username,headerImg.getSize(),photos.length);
        if (!headerImg.isEmpty()){
            //保存到文件服务器
            String originalFilename = headerImg.getOriginalFilename();
            headerImg.transferTo(new File("G:\\test" + originalFilename));
        }

        if (photos.length > 0){
            for (MultipartFile photo : photos) {
                if (!photo.isEmpty()){
                    String originalFilename1 = photo.getOriginalFilename();
                    photo.transferTo(new File("G:\\test1" + originalFilename1) );
                }
            }
        }


        return "main";
    }
}

测试日志为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sxr8adW9-1619598587767)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423104921250.png)]

默认配置上传的文件大小不超过1M,在application.properties中修改:

spring.servlet.multipart.max-file-size=10MB #上传文件大小为10M
spring.servlet.multipart.max-request-size=100MB #最大为100M

html页面为:

<form role="form" th:action="@{/upload}" method="post" enctype="multipart/form-data">
                            <div class="form-group">
                                <label for="exampleInputEmail1">邮箱</label>
                                <input type="email" name="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
                            </div>
                            <div class="form-group">
                                <label for="exampleInputPassword1">名字</label>
                                <input type="text" name="username" class="form-control" id="exampleInputPassword1" placeholder="Password">
                            </div>
                            <div class="form-group">
                                <label for="exampleInputFile">头像</label>
                                <input type="file" name="headerImg" id="exampleInputFile">
                            </div>
                            <div class="form-group">
                                <label for="exampleInputFile">生活照</label>
                                <input type="file" name="photos" multiple>
                            </div>
                            <div class="checkbox">
                                <label>
                                    <input type="checkbox"> Check me out
                                </label>
                            </div>
                            <button type="submit" class="btn btn-primary">提交</button>
                        </form>

错误处理

默认规则

  • 默认情况下,Springboot提供/error处理所有错误的映射
  • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个"whitelabel"错误视图,以HTML格式呈现相同的数据。
  • 要对其进行自定义,添加view解析为error
  • 要完全替换默认行为,可以实现errorController并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
  • error/下的4xx,5xx页面会被自动解析

定制错误处理逻辑

  • 自定义错误页 error/404.html
  • @ControllerAdvice+@ExceptionHandler处理异常
  • 实现 HandlerExceptionResolver处理异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfBrPFuT-1619598587773)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423114308490.png)]

不知道为什么显示不了。。

Web原生组件注入(Servlet、Filter、Listener)

使用Servlet API

@ServletComponentScan(basePackages = "com.yutou.admin")//指定原生Servlet组件都放在那里
@WebServlet(urlPatterns = "/my")//效果:直接响应,没有Spring的拦截器

Filter:

@WebFilter(urlPatterns = {"/css/*","/images/*"})

Listener:

@WebListener

使用RegistConfig

//(proxyBeanMethods = false)保证依赖的组件始终是单实例的
@Configuration(proxyBeanMethods = true)
public class MyRegistConfig {

    @Bean
    public ServletRegistrationBean myServlet(){
        MyServlet myServlet = new MyServlet();

        return new ServletRegistrationBean(myServlet,"/my","/my02");
    }
    @Bean
    public FilterRegistrationBean myFilter(){
        MyFilter myFilter = new MyFilter();
//        return new FilterRegistrationBean(myFilter,myServlet());
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
        return filterRegistrationBean;
    }

}

定制化原理

定制化的常见方式

  • 修改配置文件

  • xxxxCustomizer;

  • 编写自定义的配置类:xxxConfiguration + @Bean替换,增加容器中默认组件,视图解析器

  • web应用 实现WebMvcConfigurer 即可定制化web功能 + @Bean给容器中再扩展一些组件

    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer
    
  • @EnableWebMvc + WebMvcConfigurer ——@Bean 可以全面接管SpringMVC,所有规则全部自己重新配置,实现定制和扩展功能

    • 原理
    • 1、WebMvcAutoConfiguration默认的Spring MVC的自动配置功能类、静态资源、欢迎页等
    • 2、一旦使用@EnableWebMvc 会@Import(DelegatingWebMvcConfiguration.class)
    • 3、DelegatingWebMvcConfiguration的作用,只保证SpringMvc最基本的使用
      • 把所有系统中的WebMvcConfigurer拿过来。所有功能的定制都是这些WebMvcConfigurer合起来一起生效
      • 自动配置了一些非常底层的组件。RequestMappingHandlerMapping这些组件依赖的组件都是从容器中获取
      • public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
    • 4、WebMvcConfiguration里面的配置要能生效必须@ConditionalOnMisiingBean(WebMvcConfigurationSupport.class)
    • 5、@EnableWebMvc 导致了 WebMvcAutoConfiguration 没有生效

原理分析套路:

场景starter + XXXAutoConfiguration + 导入xxx组件 + 绑定xxxProperties + 绑定配置文件项

数据访问

SQL

1、数据源的自动配置

导入JDBC场景

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BehQeHxh-1619598587774)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210423153835869.png)]

数据库驱动?为什么导入JDBC场景,官方不导入驱动?官方不知道我们接下来要要操作哪种

导入Mysql的驱动 默认版本

数据库的版本要和驱动的版本对应

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

想要修改版本:

1、直接依赖引入具体版本(maven的就近依赖原则)

2、重新声明版本(maven的属性就近优先原则)

<properties>
	<java.version>1.8</java.version>
    <mysql.version>5.1.49</mysql.version>
</properties>

2、分析自动配置

  • DataSourceAutoConfiguration:数据源的自动配置

    • 修改数据源的相关配置:spring.datasource
  • DataSourceTransactionManagerAutoConfiguration:事务管理器的自动配置

  • JdbcTemplateAutoConfiguration:JdbcTemplate的自动配置,可以来对数据库进行crud

  • JndiDataSourceAutoConfiguration:jndi的自动配置

  • XADataSourceAutoConfiguration:分布式事务相关的

3、修改配置项

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: root123
    driver-class-name: com.mysql.jdbc.Driver

druid

druid数据源starter整合方式:

1、引入druid-starter

<dependency>
     <groupId>com.alibaba</groupId>
     <artifactId>druid-spring-boot-starter</artifactId>
     <version>1.1.17</version>
</dependency>

2、分析自动配置

DruidSpringAopConfiguration.class 监控SpringBean的配置

3、配置application.yaml文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: root123
    driver-class-name: com.mysql.jdbc.Driver

    #监控页的配置
    druid:
      aop-patterns: com.yutou.admin.* #监控springBean
      filters: stat,wall,slf4j # 底层开启功能,stat(sql监控),wall(防火墙)


      stat-view-servlet: # 配置监控页功能
        enabled: true
        login-username: admin
        login-password: admin
        reset-enable: false

      web-stat-filter: # 监控web
        enabled: true
        url-pattern: /*
        exclusions: '*.js,*.gif,*.png,*.ico,/druid/*'

      filter:
        stat: # 对上面filters里面的stat的详细配置
          slow-sql-millis: 1000
          log-slow-sql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

整合MyBatis操作

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.4</version>
</dependency>
1、配置模式
  • 全局配置文件
  • SqlSessionFactory:自动配置好了
  • SqlSession:自动配置了SqlSessionTemplate组合了SqlSession
  • Mapper:只要我们写的操作Mybatis的接口标准 @Mapper就会自动扫描进来
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class,MybatisLauageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration(){}

@ConfigurationProperties(profix = "mybatis")
public class MybatisProperties

可以修改配置文件中mybatis开始的所有;

# 配置mybatis规则
mybatis:
  config-location: classpath:mybatis/mybatis-config.xml #全局配置文件的位置
  mapper-locations: classpath:mybatis/mapper/*.xml #SQL映射文件位置
  
  
Mapper接口--->绑定xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yutou.admin.mapper.AccountMapper">
    <select id="getAcct" resultType="com.yutou.admin.Bean.Account">
        select * from tbl_user where id=#{id}
    </select>

</mapper>

配置 private Configuration;mybatis.configuration下面的所有,就是相当于改mybatis全局配置文件中的值

# 配置mybatis规则
mybatis:
#  config-location: classpath:mybatis/mybatis-config.xml
  mapper-locations: classpath:mybatis/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
可以不写全局,配置文件,所有配置文件的配置都放在configuration配置项中即可
  • 导入mybatis官方starter
  • 编写mapper接口,标准@Mapper注解
  • 编写sql映射文件并绑定mapper接口
  • 在application.yaml中指定Mapper配置文件的位置
注解
@Service
public class CityService {

    @Autowired
    CityMapper cityMapper;

    public City getById(Long id){
        return cityMapper.getByid(id);
    }

}
@ResponseBody
@GetMapping("/city")
public City getCityById(@RequestParam("id") Long id){
    return cityService.getById(id);
}
@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")
    public City getByid(Long id);

}
2、注解和配置混合版:

注解和配置混合(如果SQL语句很长的话):

@Service
public class CityService {

    @Autowired
    CityMapper cityMapper;


    public void saveCity(City city) {
        cityMapper.insert(city);
    }
}
@Mapper
public interface CityMapper {

    @Select("select * from city where id=#{id}")

    public void insert(City city);
}
@Autowired
CityService cityService;

@ResponsBody
@PostMapping("/city")
public City saveCity(City city){
    cityService.saveCity(city);
    return city;
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yutou.admin.mapper.CityMapper">
    
    <insert id="insert" useGenerateKey="ture" keyProperty="id">
        insert into city(`name`,`state`,`country`) values(#{name},#{state},#{country})
    </insert>

</mapper>

最佳实战:

  • 引入mybatis-starter
  • 配置application.yaml中,指定mapper-location位置即可
  • 编写Mapper接口并标注@Mapper注解
  • 简单方法直接注解
  • 复杂方法编写mapper.xml进行绑定映射
  • @MapperScan(“com.yutou.admin.mappper”)简化,其他的接口就可以不用标注@Mapper注解

整合Mybatis-Plus 完成CRUD

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

自动配置:

  • MybatisPlusAutoConfiguration,MybatisPlusProperties配置项绑定,mybatis-plus;xxx就是对mybatis-plus的定制
  • SqlSessionFactory自动配置好,底层是容器中默认的数据源
  • mapperLocations自动配置好。有默认值。classpath*:/mapper/**/*.xml;任意包的类路径下的所有mapper文件下任意路径下的所有xml都是sql映射文件,建议以后sql映射文件,放在mapper下。
  • 容器中也自动配置好了SqlSessionTemplate
  • @Mapper标注的接口也会被自动扫描;建议直接@MapperScan(“com.yutou.admin.mapper”)批量扫描就行

优点:

  • 只需要我们的Mapper继承BaseMapper就可以拥有CRUD能力

整合阿里云Redis环境

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

自动配置:

  • RedisAutoConfiguration 自动配置类,RedisProperties 属性类 —>spring.redis.xxx是对redis的配置
  • 连接工厂是准备好的。LettuceConnectionConfiguration、JedisConnectionConfiguration
  • 自动注入了RedisTemplate<Object, Object>
  • 自动注入了StringRedisTemplate;k:v都是String
  • 底层只要我们使用StringRedisTemplate、RedisTemplate就可以操作redis

单元测试

JUnit5

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUXUO12h-1619598587776)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428110835933.png)]

现在版本:

@SpringBootTest
class Boot05WebAdminApplicationTests{
    @Test
    void contextLoads(){
        
    }
}

以前:

@SpringBootTest + @RunWith(SpringTest.class)

如果要兼容junit4需要自行引入:

<dependency>
	<groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
    	<exclusion>
        	<groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

SpringBoot整合Junit以后:

  • 编写测试方法:@Test标注(注意需要使用junit5版本的注解)
  • Junit类具有Spring的功能,@Autowired,比如@Transactional标注测试方法,测试完成后自动回滚

JUnit5常用注解:

JUnit5的注解和Junit4的注解有所变化

  • @Test:表示方法是测试方法,但是与JUnit4的@Test不同,它的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
  • @ParameterizedTest:表示方法是参数化测试
  • @RepeatedTest:表示方法可重复执行
  • @DisplayName:为测试类或者测试方法设置展示名称
  • @BeforeEach:表示在每个单元测试之前执行
  • @AfterEach:表示在每个单元测试之后执行
  • @BeforeAll:表示在所有单元测试之前执行
  • @AfterAll:表示在所有单元测试之后执行
  • @Tag:表示单元测试类别,类似于JUnit4中的@Categories
  • @Disabled:表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
  • @Timeout:表示测试方法运行如果超过了指定时间将会返回错误
  • @ExtendWith:为测试类或测试方法提供扩展类引用
@DisplayName("junit5功能测试类")
public class Junit5Test {

    @DisplayName("测试displayName注解")
    @Test
    void testDisplayName1(){
        System.out.println(1);
    }

    @Disabled
    @DisplayName("测试注解2")
    @Test
    void testDisplayName2(){
        System.out.println(2);
    }

    @RepeatedTest(5)
    @Test
    void test3(){
        System.out.println(5);
    }

    //规定方法的超时时间
    @Timeout(value = 500,unit = TimeUnit.MICROSECONDS)
    @Test
    void testTimeOut() throws InterruptedException{
        Thread.sleep(600);
     }

    @BeforeEach
    void testBeforeEach(){
        System.out.println("测试就要开始了...");
    }
    @AfterEach
    void testAfterEach(){
        System.out.println("测试结束了...");
    }

    @BeforeAll
    static void testBeforeAll(){
        System.out.println("所有测试就要开始了...");
    }

    @AfterAll
    static void testAfterAll(){
        System.out.println("所有测试类就要结束了...");
    }
}

断言

断言是测试方法中的核心部分,用来对测试需要满足的条件进行验证,这些断言方法都是org.junit.jupiter.api.Assertions的静态方法。JUnit 5 内置的断言可以分成如下几个类别:

所有测试运行结束以后,会有一个详细的测试报告

简单断言

用来对单个值进行简单的验证。如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sArHifT5-1619598587777)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428134549070.png)]

//前面断言失败后面的代码都不会执行
@DisplayName("测试简单断言")
@Test
void testSimpleAssertions(){
    int cal = cal(3,2);
    //相等
    Assertions.assertEquals(5,cal,"业务逻辑计算失败");
    Object object1 = new Object();
    Object object2 = new Object();
    assertSame(object1,object2,"两个对象不一样");
}
数组断言
@Test
@DisplayName("array assertion")
void array(){
    assertArrayEquals(new int[]{2,1}, new int[]{1,2});
}
组合断言

assertAll 方法接受多个 org.junit.jupiter.api.Exexcutable 函数式接口的实例作为要验证的断言,可以通过Lamda表达式很容易的提供这些断言

@Test
@DisplayName("组合断言")
//断言全部需要成功
void all(){
    assertAll("test",
            ()-> assertTrue(true && true,"结果不为true"),
            ()-> assertEquals(1 , 1,"结果不是1"));
}
异常断言
@DisplayName("异常断言")
@Test
void testException(){
    //断定业务逻辑一定出现异常
    assertThrows(ArithmeticException.class,()->{
        int i = 10 / 0;},"业务逻辑居然正常运行");
}
超时断言
快速失败
@DisplayName("快速失败")
@Test
void testPail(){
    if (2 ==  2){
        fail("测试失败");
    }
}

前置条件

JUnit5中的前置条件(Assumption)类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止,前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要。

@DisplayName("测试前置条件")
@Test
void testassumption(){
    Assumptions.assumeTrue(false,"结果不是true");
    System.out.println("11111");
}

嵌套测试

JUnit5可以通过Java中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起,在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制

@DisplayName("嵌套测试")
public class TestAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("")
    void isInstantiateWithNew(){
        new Stack<>();
        //在嵌套测试的情况下,外层的Test不能驱动内层的Before(After)Each/ALL之类的方法提前/之后
        assertNull(stack);
    }

    @Nested
    @DisplayName("whenNew")
    class whenNew{

        @BeforeEach
        void createNewStack(){
            stack = new Stack<>();
        }


        @Test
        @DisplayName("is empty")
        void isEmpty(){
            assertTrue(stack.isEmpty());
        }

        @Test
        @DisplayName("throws")
        void throwsExceptionWhenPopped(){
            assertThrows(EmptyStackException.class, stack::pop);
        }

        @Test
        @DisplayName("throws peeked")
        void throwsExceptionWhenPeeked(){
            assertThrows(EmptyStackException.class, stack::peek);
        }

        @Nested
        @DisplayName("after pushing")
        class AfterPushing{
            String anElement = "an element";

            @BeforeEach
            void pushAnElement(){
                stack.push(anElement);
            }
            //内存的Test可以驱动外层的Before(After)Each/ALL之类的方法提前/之后运行
            @Test
            @DisplayName("it is no longer empty")
            void isNotEmpty(){
                assertFalse(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when popped and is empty")
            void returnElementWhenPoped(){
                assertEquals(anElement, stack.pop());
                assertTrue(stack.isEmpty());
            }

            @Test
            @DisplayName("returns the element when peeked and is not empty")
            void returnElementWhenPeeked(){
                assertEquals(anElement, stack.peek());
                assertFalse(stack.isEmpty());
            }
        }
    }
}

参数化测试

参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利。

利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码。

@ValueSource:为参数化代码指定入参来源,支持八大基础类以及String类型,Class类型

@NullSource:表示为参数化参数提供一个null的入参

@EnumSource:表示为参数化测试提供一个枚举入参

@CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

@MethodSource:表示读取指定方法的返回值作为参数化测试入参

@ParameterizedTest
@DisplayName("参数化测试")
@ValueSource(ints = {1,2,3,4,5})
void testParameterized(int i){
    System.out.println(i);
}
@ParameterizedTest
@DisplayName("参数化测试")
@MethodSource("stringProvider")
void testParameterized2(String i){
    System.out.println(i);
}

static Stream<String> stringProvider(){
    return Stream.of("apple","banana","orange");
}

指标监控

SpringBoot Actuator

未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等,SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

1.x和2.x不同

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnzkgQo1-1619598587778)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210428154801429.png)]

如何使用?

访问 htttp://localhost:8080/actuator/**

# management:是所有actuator的配置
management:
  endpoints:
    enabled-by-default: true #默认开启所有监控端点
    web:
      exposure:
        include: '*' # 以web方式暴露所有端点
Health EndPoint

健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状态,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状态的集合。

重要的几点:

  • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
  • 很多的健康检查默认已经自动配置好了,比如:数据库、redies等
  • 可以很容易的添加自定义的健康检查机制
Metrics Endpoint

提供详细的、层级的、空间指标信息,这些信息可以被pull或者被push方法得到:

  • 通过Metrics对接多种监控系统
  • 简化核心Metrics开发
  • 添加自定义Metrics或者扩展已经Metrics
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qtayu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值