2.4 开发小技巧
2.4.1 lombok
lombok能简化JavaBean的开发
在pom文件里添加依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
并在idea中下载插件
以前的getset方法我们不用写了,通过注解@Data,它就可以在编译的时候自动生成getset方法了
@NoArgsConstructor//为JavaBean添加无参构造 @AllArgsConstructor//为JavaBean添加有参构造 @Data @ToString public class Pet { private String name; }
Slf4j
简化日志开发
添加@Slf4j注解既有此功能
2.4.2 dev-tools
项目或者页面修改以后:Ctrl+F9;
修改后的页面效果就会立马显示出来
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency>
2.4.3 Spring Initailizr
这个功能能帮我们快速构建SpringBoot项目
如下:
选中想要开发的项目需要的组件
目录结构直接给我们创建好了,而且SpringBoot的启动程序也写好了
我们只需要关注逻辑代码即可~
三、SpringBoot2核心功能
3.1 配置文件
3.1.1 properties
就是正常的配置文件,和前面学的一样
3.1.2 yaml
3.1.简介
YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:“Yet Another Markup Language”(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
这个我太熟悉了啊,当时在专科的时候参加云计算竞赛,要写容器编排。我搁那天天练这个啊,当时也不知道格式啥的,就是死记硬背。
我想说的是,这个很重要,后面学ansible的时候也会用到这个,给爷狠学!!!
基本语法
key: value;kv之间有空格
大小写敏感
使用缩进表示层级关系
缩进不允许使用tab,只允许空格
缩进的空格数不重要,只要相同层级的元素左对齐即可
'#'表示注释
字符串无需加引号,如果要加,’'与""表示字符串内容 会被 转义/不转义
数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
1
对象:键值对的集合。map、hash、set、object
行内写法: k: {k1:v1,k2:v2,k3:v3}#或k: k1: v1 k2: v2 k3: v3
1
数组:一组按次序排列的值。array、list、queue
行内写法: k: [v1,v2,v3]#或者k: - v1 - v2 - v3
1
示例
person: userName: zs boss: true birth: 2019/12/9 age: 18# interests: [唱,跳,rap] interests: - 唱 - 跳 - rap# 集合也可以跟数组一样用-的形式来表示# 也可以写成[]的形式 animal: [Cat,Dog]# map的两种写法# score:# english: 99# math: 80# score: {english:90,math:78}# 这里面的冒号后面不用跟空格,因为这是json的表示方法# 也可以写成键值对的形式{k:V,K:V} score: {english:90,math:78} salarys: - 9999.98 - 9999.97 pet: name: dog weight: 99.97 allPets: sick:# 以下这两种都表示对象,分别是k:v的形式和{}的形式 - {name: dog,weight: 34} - name: cat weight: 23 health: - {name: A,weight: 10} - {name: B,weight: 20}
访问测试
配置提示
自定义的类和配置文件绑定一般没有提示。
导入一个依赖即可~
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build>
之后就能愉快的自动提示了
3.2 Web开发
3.2.1 静态资源访问
只要静态资源放在类路径下: called /static (or /public or /resources or /META-INF/resources
访问 : 当前项目根路径/ + 静态资源名
原理: 静态映射/**
请求进来,先去找Controller看能不能处理。不能处理的所有请求又都交给静态资源处理器。静态资源也找不到则响应404页面
启动成功后,直接访问 当前项目根路径/ + 静态资源名
改变默认的静态资源路径
改变为AAA文件为静态资源文件夹
webjar
WebJars是一个很神奇的东西,可以让大家以jar包的形式来使用前端的各种框架、组件。
完成依赖添加
<dependency> <groupId>org.webjars</groupId> <artifactId>jq ery</artifactId> <version>3.5.1</version> </dependency>
进行测试
可以尝试CTRL+f9快速编译
记得重启之后在访问,便可访问web静态资源了
3.2.2 欢迎页支持
静态资源路径下 index.html
可以配置静态资源路径
但是不可以配置静态 资源的访问前缀。否则导致 index.html不能被默认访问
就是把index页面放到静态资源目录下,他会自动访问这个页面
如果访问不了,记得clean一下(Maven插件clean功能)
3.2.3 自定义Favicon
favicon.ico 放在静态资源目录下即可更换网站图标
设置静态资源访问路径会导致 Favicon 功能失效
3.2.4 请求映射
@xxxMapping Rest风格支持使用HTTP请求方式动词来表示对资源的操作 以前访问资源路径: /getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 存用户 现在访问资源路径: /user
GET-获取用户
DELETE-删除用户
PUT-修改用户
POST-保存用户
核心Filter; HiddenHttpMethodFilter
SpringBoot中手动开启
#开启rest风格在springboot里要手动开启 spring: mvc: hiddenmethod: filter: enabled: true
表单
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 测试REST风格 <form action="/user" method="get"> <input value="REST-GET 提交" type="submit"> </form> <form action="/user" method="post"> <input value="REST-POST 提交" type="submit"> </form> 表单method=post, 隐藏域 _method=put <form action="/user" method="post"> <input name="_method" type="hidden" value="put"> <input value="REST-put 提交" type="submit"> </form> <form action="/user" method="post"> <input name="_method" type="hidden" value="DELETE"> <input value="REST-delete 提交" type="submit"> </form> </body> </html>
3.2.5 普通参数与基本注解
基本注解:
@PathVariablev 获取路径变量
@RequestHeader 获取请求头
@RequestParams 获得请求参数
@CookieValue 获取Cookie的值
@RequestBody 获取请求体
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title></head><body>测试REST风格<form action="/user" method="get"> <input value="REST-GET 提交" type="submit"></form><form action="/user" method="post"> <input value="REST-POST 提交" type="submit"></form><form action="/user" method="post"> <input name="_method" type="hidden" value="put"> <input value="REST-put 提交" type="submit"></form><form action="/user" method="post"> <input name="_method" type="hidden" value="DELETE"> <input value="REST-delete 提交" type="submit"></form><hr>测试基本注解<ur> <a href="car/3/owner/zs?age=18&interest=girl&interest=boy">car/{id}}/owner/{username}</a> <li>@PathVariable(路径变量)</li> <li>@RequestHeader(获取请求头)</li> <li>@RequestParam(获取请求参数)</li> <li>@CookieValue(获取cookie)</li> <li>@RequestAttribute(获取request域属性)</li> <li>@RequestBody(获取请求体)</li> <li>@MatrixVariable(矩阵变量)</li></ur><form action="/save" method="post"> 测试@RequestBody注解<br> 用户名:<input name="UserName"><br> 邮箱:<input name="email"><br> <input type="submit" value="提交"></form></body></html> 1 @GetMapping用于将HTTP get请求映射到特定处理程序的方法注解 具体来说,@GetMapping是一个组合注解,是@RequestMapping(method = RequestMethod.GET)的缩写。 @PostMapping用于将HTTP post请求映射到特定处理程序的方法注解 具体来说,@PostMapping是一个组合注解,是@RequestMapping(method = RequestMethod.POST)的缩写
package com.caq.boot.controller;import org.springframework.web.bind.annotation.*;import sun.management.Agent;import java.util.HashMap;import java.util.List;import java.util.Map;@RestControllerpublic class ParameterController { @GetMapping("/car/{id}/owner/{username}") public Map<String,Object> getCar(@PathVariable("id") Integer id, @PathVariable("username") String username, @PathVariable Map<String,String> pv, @RequestHeader("user-Agent") String userAgent, @RequestHeader Map<String,String> header, @RequestParam("age") Integer age, @RequestParam("interest") List<String> interest, @RequestHeader Map<String,String> param ){ /* @CookieValue("_ga") 传入一个Map<String,String>可以接受所有的参数 @PathVariable带key了就拿特点的,不带了就拿所有的 @RequestHeader同上 */ HashMap<String, Object> map = new HashMap<>(); map.put("id",id); map.put("username",username);// map.put("pv",pv);// map.put("userAgent",userAgent);// map.put("header",header);// map.put("age",age);// map.put("interest",interest); map.put("param",param); map.put("_ga",_ga); return map; } @PostMapping("/save") public Map postMethod(@RequestBody String content){ HashMap<String, Object> map = new HashMap<>(); map.put("content",content); return map; }}
3.2.6 视图解析流程
目标方法处理的过程中,所有数据都会被放在ModelAndViewContainer 里面。包括数据和视图地址
方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer
任何目标方法执行完成以后都会返回 ModelAndView(数据和视图地址)
processDispatchResult 处理派发结果(页面改如何响应)
视图解析:
返回值以 forward: 开始: new InternalResourceView(forwardUrl); --> 转发request.getRequestDispatcher(path).forward(request, response);
返回值以 redirect: 开始:new RedirectView() --》 render就是重定向
返回值是普通字符串: new ThymeleafView()—>
3.2.7 Thymeleaf
为什么用Thymeleaf?
JSP不好用,Thymeleaf好用它适用于SpringBoot模块。它使用html作为模板页,通过html一些特殊标签语法代表其含义,不破坏html的结构
Thymeleaf是一款现代化、服务端Java模板引擎
Thymeleaf是Springboot官方支持的模板引擎,有着动静分离等独有特点
Thymeleaf的主要目标是为您的开发工作流程带来优雅自然的模板-HTML可以在浏览器中正确显示,也可以作为静态原型工作,从而可以在开发团队中加强协作。
Thymeleaf拥有适用于Spring Framework的模块,与您喜欢的工具的大量集成以及插入您自己的功能的能力,对于现代HTML5 JVM Web开发而言,Thymeleaf是理想的选择——尽管它还有很多工作要做。
基本语法
表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接
片段表达式 ~{…} jsp:include作用,引入公共页面片段
标签 作用 示例
th:id 替换id <input th:id="${user.id}"/>
th:text 文本替换 <p text:="${user.name}">bigsai</p>
th:utext 支持html的文本替换 <p utext:="${htmlcontent}">content</p>
th:object 替换对象 <div th:object="${user}"></div>
th:value 替换值 <input th:value="${user.name}" >
th:each 迭代 <tr th:each="student:${user}" >
th:href 替换超链接 <a th:href="@{index.html}">超链接</a>
th:src 替换资源 <script type="text/javascript" th:src="@{index.js}"></script>
2. thymeleaf使用
我们只需要给request域中放一些数据,然后跳转到页面我们就能通过thymeleaf来取得这些值
引入依赖包
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
控制层代码
package com.caq.boot.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @Controller public class ViewTestController { @GetMapping("/thymeleaf") public String forwardTest(Model model){ //model中的数据会被放在请求域中 request.setAttribute("a",aa) model.addAttribute("message","你好thymeleaf"); model.addAttribute("link","http://www.baidu.com"); return "success"; } }
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="${message}">哈哈</h1> <h2> <a href="www.jd.com" th:href="${link}">去百度</a> <a href="www.jd.com" th:href="@{link}">去百度</a> </h2> </body> </html>
补充:
假如在这个页面上有一个请求,url 为 “showAgain”
那么访问全 url 就是:http://localhost:8080/first/showAgain 相对路径,就是show.html 同级目录
假如在这个页面上有一个请求,url 为 “/showAgain”
那么访问全 url 就是 http://localhost:8080/showAgain 绝对路径,就是从根目录 拼接 url
@GetMapping("/responsive_table") public String responsive_table(){ return "table/responsive_table"; }
3.2.8 SpringBoot实战
学了这么多我们来用SpringBoot构建一个后台管理系统
我们只关注业务逻辑的实现,前端代码展示不关注
1. SpringBoot项目创建
选中web开发常用的组件
thymeleaf、web-starter、devtools、lombok
2. 静态资源处理
自动配置好,我们只需要把所有静态资源放到static文件夹下即可
3. 路径构建
th:action="@{/login}"
4. 模板抽取
因为left side和header section部分所有的页面都是一样的,所以我们抽取出来一个公共部分,需要的这段代码的地方直接引用公共代码,这样让我们的代码更加简介。
怎么抽取呢?
我们用thymeleaf,fragment类似于JSP的tag,在html中文件中,可以将多个地方出现的元素块用fragment包起来使用。
th:insert:保留自己的主标签,保留th:fragment的主标签。
th:replace:不要自己的主标签,保留th:fragment的主标签。
th:include:保留自己的主标签,不要th:fragment的主标签。(官方3.0后不推荐)
<div th:insert="footer :: copy"></div> <div th:replace="footer :: copy"></div> <div th:include="footer :: copy"></div>
结果为:
<div> <footer> the content of footer </footer> </div> <footer> the content of footer </footer> <div> the content of footer </div>
5. 页面跳转
如果账户密码匹配了就把账号密码封装到User对象中然后把对象保存到session域,以便后面的页面根据是否登录做判断。
密码匹配保存对象重定向至新页面
不配通过thymeleaf传入渲染后的数据到view层提示账号或密码错误之后返回到登录页
@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"; } }
3.2.9 拦截器
SpringBoot中的拦截器和我们javaweb阶段学的filter很像
拦截器就是用来拦截请求,根据拦截规则进行放行
拦截器的使用步骤
编写一个拦截器实现HandlerInterceptor接口
拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
指定拦截规则(如果是拦截所有,静态资源也会被拦截)
1.编写一个拦截器实现HandlerInterceptor接口
package com.caq.admin.interceptor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * 登录检查 * 配置拦截器要拦截哪些请求 * 把配置放在容器中 */ @Slf4j public class LoginInterceptor implements HandlerInterceptor { /** * 目标方法执行前 * @param request * @param response * @param handler * @return * @throws Exception */ @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","Please login"); request.setAttribute("msg","Please Login!!!"); request.getRequestDispatcher("/").forward(request,response); return false; } /** * 目标方法执行后 * @param request * @param response * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle执行{}",modelAndView); } /** * 页面渲染之后 * @param request * @param response * @param handler * @param ex * @throws Exception */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("afterCompletion{}执行异常()",ex); } }
2.拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
指定拦截规则(如果是拦截所有,静态资源也会被拦截)
ackage com.caq.admin.config; import com.caq.admin.interceptor.LoginInterceptor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * 编写一个拦截器实现HandlerInterceptor接口 * 拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors) * 指定拦截规则(如果是拦截所有,静态资源也会被拦截) */ @Configuration public class AdminWebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**")//所有请求都会被拦截,包括静态资源 .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); } }
3.测试
我们访问main页面进行测试,原来写的拦截规则就可以去掉了,因为拦截器会帮我们自动拦截。
为了测试拦截器的常用方法的执行顺序我们在访问main页面的时候进行日志打印,查看我们访问main页面时所执行的方法
@GetMapping("/main.html") public String mainPage(HttpSession session,Model model){ log.info("当前方法是:{}","mainPage"); // //是否登录,拦截器,过滤器 // Object loginUser = session.getAttribute("loginUser"); // if (loginUser != null){ return "main"; // }else { // model.addAttribute("msg","请重新登陆"); // return "login"; // } }
测试一:直接访问main页面
我们直接访问main页面进行测试,发现拦截器帮我们拦截了下来并提示我们需要先登录才能访问
测试二:登录之后,查看拦截器方法的执行顺序
preHandle在目标方法执行前执行
目标方法执行
postHandle在目标方法执行后执行
afterCompletion在页面渲染后执行
3.2.10 文件上传功能
MultipartFile headerImg 允许上传单个文件
MultipartFile[] photos 允许上传多个文件
表单代码
<form method="post" action="/upload" enctype="multipart/form-data"> <input type="file" name="file"><br> <input type="submit" value="提交"> </form>
控制层代码
package com.caq.admin.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.multipart.MultipartFile; /** * 文件上传测试 */ @Slf4j @Controller public class FormTestController { @GetMapping("/form_layouts") public String form_layouts(){ return "form/form_layouts"; } @PostMapping("/upload") public String upload(@RequestParam("email") String email, @RequestParam("username") String username, @RequestPart("headerImg")MultipartFile headerImg, @RequestPart("photos")MultipartFile[] photos){ log.info("上传的信息:email={},username={}.headerImg={},photos={}",email,username,headerImg.getSize(),photos.length); return "main"; } }
测试结果为
3.2.11 异常处理
官网给出的解释如下:
默认情况下,Spring Boot提供/error处理所有错误的映射
对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
我们自定义错误页的时候,可以通过thymeleaf把错误信息打印到错误页面
<section class="error-wrapper text-center"> <h1><img alt="" src="images/404-error.png"></h1> <h2 th:text="${status}">page not found</h2> <h3 th:text="${message}">We Couldn’t Find This Page</h3> <a class="back-btn" th:href="@{/main.html}"> Back To Home</a> </section>
error/下的4xx,5xx页面会被自动解析
error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找 4xx.html;如果都没有就触发白页
3.2.12 Web原生组件注入
简单API的使用,不用过多说明。会用即可
使用Servlet API
@ServletComponentScan(“com.caq.admin”) 指定原生的servlet组件都放在哪里
@WebServlet(urlPatterns = “/my”) 直接响应,没有经过spring的拦截器
@WebListener
@WebFilter(urlPatterns = {"/css/","/images/"})
只有结合这几个注解开发,我们的原生组件才能注入过来
日常开发推荐以上这种方式
使用RegistrationBean
@Configuration 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(myFilter); filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*")); return filterRegistrationBean; } @Bean public ServletListenerRegistrationBean myListener(){ MySwervletContextListener mySwervletContextListener = new MySwervletContextListener(); return new ServletListenerRegistrationBean(mySwervletContextListener); } }
3.2.13 嵌入式Servlet容器
默认支持的webServer
Tomcat, Jetty, or Undertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
<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>
原理
SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
ServletWebServerApplicationContext 启动的时候寻找 ServletWebServerFactory``(Servlet 的web服务器工厂---> Servlet 的web服务器)
SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
底层直接会有一个自动配置类。ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包。(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
3.2.14 SpringBoot原理套路
引入场景starter – xxxxAutoConfiguration --导入xxx组件 - 绑定xxxProperties – 绑定配置文件项
3.3 数据访问
3.3.1 数据源的自动配置
我们发现只要导入jdbc场景,SpringBoot就会自动给我们导入HiKariDS
HiKariDS它是性能最好的数据源
导入JDBC场景
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>
导入jdbc之后并没有导入数据库的驱动,是因为官方不知道我们要导入什么数据库
之后数据库的版本要和驱动版本对应
有两种修改方式
重新声明版本(maven的属性的就近优先原则)
直接依赖引入具体版本(maven的就近依赖原则)
配置文件
spring: datasource: url: jdbc:mysql://localhost:3306/mp username: root password: root driver-class-name: com.mysql.jdbc.Driver
测试
@Slf4j @SpringBootTest class AdminSystemApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Test void contextLoads() { Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class); log.info("总数有"+aLong); } } 2022-02-19 11:50:03.332 INFO 7200 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2022-02-19 11:50:03.804 INFO 7200 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2022-02-19 11:50:04.120 INFO 7200 --- [ main] c.caq.admin.AdminSystemApplicationTests : Started AdminSystemApplicationTests in 4.476 seconds (JVM running for 6.428) 2022-02-19 11:50:04.521 INFO 7200 --- [ main] c.caq.admin.AdminSystemApplicationTests : 总数有5
3.3.2 使用Druid数据源
修改默认的数据源步骤:
导入第三方数据源
设置配置类
测试
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.17</version> </dependency>
@Configuration public class MyDataSourceConfig { //把组件的东西和配置文件的东西进行绑定 @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); return druidDataSource; } }
@Slf4j @SpringBootTest class AdminSystemApplicationTests { @Autowired JdbcTemplate jdbcTemplate; @Autowired DataSource dataSource; @Test void contextLoads() { Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class); log.info("总数有"+aLong); log.info("数据源类型:{}",dataSource.getClass()); } } ........... 2022-02-19 12:12:09.644 INFO 16124 --- [ main] c.caq.admin.AdminSystemApplicationTests : 总数有5 2022-02-19 12:12:09.644 INFO 16124 --- [ main] c.caq.admin.AdminSystemApplicationTests : 数据源类型:class com.alibaba.druid.pool.DruidDataSource
开启druid监控功能
步骤:
配置监控页,开启监控功能
sql查询进行测试
@Configuration public class MyDataSourceConfig { //把组件的东西和配置文件的东西进行绑定 @ConfigurationProperties("spring.datasource") @Bean public DataSource dataSource() throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); //加入监控功能 druidDataSource.setFilters("stat"); return druidDataSource; } /** * 配置druid的监控页 * @return */ @Bean public ServletRegistrationBean statViewServlet(){ StatViewServlet statViewServlet = new StatViewServlet(); ServletRegistrationBean<StatViewServlet> registrationBean = new ServletRegistrationBean<>(statViewServlet,"/druid/*"); return registrationBean; } }
@Autowired JdbcTemplate jdbcTemplate; @ResponseBody @GetMapping("/sql") public String queryFormDb() { Long aLong = jdbcTemplate.queryForObject("select count(*) from user", Long.class); return aLong.toString(); }
使用官方starter方式
使用官方的stater方式之后,我们就不用写配置类了。所有的功能设置我们都可以由配置文件来完成
所有功能开启参照如下步骤即可:
引入依赖
设置配置文件
测试
引入druid-starter
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>
设置配置文件
写配置文件这种方式简化了我们去写配置类往spring容器中注入组件这种方式,因为它们有自动配置功能…
spring: datasource: url: jdbc:mysql://localhost:3306/mp username: root password: root driver-class-name: com.mysql.jdbc.Driver druid: aop-patterns: com.caq.admin.* # 监控SpringBean filters: stat,wall # 底层开启功能,stat(sql监控),wall(防火墙) stat-view-servlet: # 配置监控页功能 enabled: true login-username: admin login-password: admin reset-enable: false web-stat-filter: enabled: true url-pattern: /* exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*' filter: stat: # 对上面filters里面的stat的详细配置 slow-sql-millis: 1000 log-slow-sql: true enabled: true wall: enabled: true config: drop-table-allow: false
测试
功能都被完整的开启了
3.3.3 整合Mybatis操作
引入依赖:
<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency>
原生操作
创建实体类
写mybatis主配置文件
写接口
写mapper映射文件
获得sqlsession
测试
SpringBoot整合Mybatis
释放了mybatis-config.xml文件,通过yml文件中的指定来替换。数据库映射mapper.xml文件通过在接口上写@Mapper注解和方法上写注解的方式实现
yml文件
mybatis: # 通过yml配置文件的方式解放了mybatis-config.xml文件 configuration: map-underscore-to-camel-case: true #开启驼峰命名
Mapper接口
package com.caq.admin.mapper; import com.caq.admin.bean.Account; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import java.util.List; //通过写@Mapper注解的方式释放了写mapper.xml映射文件的方式 @Mapper public interface AccountMapper { @Select("select * from account where id = #{id}") Account test(Long id); }
package com.caq.admin.service; import com.caq.admin.bean.Account; import com.caq.admin.mapper.AccountMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountService { @Autowired AccountMapper accountMapper; public Account getAcc(Long id){ return accountMapper.test(id); } }
//
service层
package com.caq.admin.service; import com.caq.admin.bean.Account; import com.caq.admin.mapper.AccountMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class AccountService { @Autowired AccountMapper accountMapper; public Account getAcc(Long id){ return accountMapper.test(id); } }
controller层
@Controller @Slf4j public class IndexController { @Autowired AccountService accountService; @ResponseBody @GetMapping("/account") public Account getAccount(@RequestParam("id") Long id){ return accountService.getAcc(id); } }
测试
3.3.4 整合Mybatis-Plus完成CRUD
引入mp依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency>
编写mysql数据源信息
spring: datasource: username: root password: root url: jdbc:mysql://localhost:3306/mybatis-plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
编写实体类
package com.pyy.mp.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; private String name; private Integer age; private String email; }
编写Mapper接口继承BaseMapper
@Repository和@Controller、@Service、@Component的作用差不多,都是把对象交给spring管理。
@Repository用在持久层的接口上,这个注解是将接口的一个实现类交给spring管理。
package com.pyy.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.pyy.mp.pojo.User; import org.springframework.stereotype.Repository; @Repository public interface UserMapper extends BaseMapper<User> { }
在我们学习MP的时候,直接写个mapper就好了。但是在我们的web开发阶段,我们将Mapper分成service接口和实现类。面向接口编程使程序更灵活,逻辑更清楚
面向接口编程是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。(这个概念的理解很重要,先记着后面实践的时候会越来越清晰!)
package com.caq.admin.service; import com.baomidou.mybatisplus.extension.service.IService; import com.caq.admin.bean.User; public interface UserService extends IService<User> { } package com.caq.admin.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.caq.admin.bean.User; import com.caq.admin.mapper.UserMapper; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> { }
在SpringBoot启动类中添加MapperScan注解扫描Mapper类
因为MP是第三方技术,要和Spring整合的话需要交给Spring来管理。所以一定要加@MapperScan注解在SB启动类上
package com.pyy.mp; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @MapperScan("com.pyy.mp") @SpringBootApplication public class MpApplication { public static void main(String[] args) { SpringApplication.run(MpApplication.class, args); } }
测试
package com.caq.admin.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.caq.admin.bean.User; import com.caq.admin.service.impl.UserServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @Controller public class testController { // @Autowired // UserService userService; // @Autowired // UserMapper userMapper; @Autowired UserServiceImpl userServiceImpl; @GetMapping("/") public String test1(@RequestParam(value = "pn",defaultValue = "1") Integer pn, Model model) { // List<User> list = userService.list(null); // model.addAttribute("users",list); // List<User> list = userMapper.selectList(null); // model.addAttribute("users",list); List<User> list = userServiceImpl.list(null); // model.addAttribute("users",list); // 分页数据 Page<User> userPage = new Page<>(pn,2); // 分页查询的结果 IPage<User> page = userServiceImpl.page(userPage, null); long current = page.getCurrent(); long pages = page.getPages(); long total = page.getTotal(); List<User> records = page.getRecords(); model.addAttribute("page",page); return "a"; }
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <table> <thead> <tr> <th>#</th> <th>id</th> <th>name</th> <th>age</th> <th>email</th> <th>操作</th> </tr> </thead> <tbody> <tr th:each="user,stat:${page.records}"> <td th:text="${stat.count}">序号</td> <td th:text="${user.id}">id</td> <td th:text="${user.name}">name</td> <td th:text="${user.age}">age</td> <td th:text="${user.email}">email</td> <td> <a th:href="@{/user/delete/{id}(id=${user.id})}" type="button">删除</button> </td> </tr> </tbody> </table> 当前第[[${page.current}]]页 总计 [[${page.pages}]]页 共[[${page.total}]]条记录 <ul> <li><a href="#">← 前一页</a></li> <li th:each="num:${#numbers.sequence(1,page.pages)}"> <a th:href="@{/(pn=${num})}">[[${num}]]</a> </li> <li><a href="#">下一页 → </a></li> </ul> <!-- </body>th:class="${num == page.current?'active':''}" --> </html>
为了提高访问速度,我写了个简陋的前端页面,但是实现了RD功能
完成CRUD功能需要很多thymeleaf知识点,不是我们学习重点,所以现写到RD功能
3.4 单元测试
3.4.1 Junit5的变化
Junit5是SpringBoot2.2.0版本后开始引入的默认单元测试默认库
以前:
@SpringBootTest + @RunWith(SpringTest.class)
SpringBoot整合Junit以后。
编写测试方法:@Test标注(注意需要使用junit5版本的注解)
Junit类具有Spring的功能,@Autowired、比如 @Transactional 标注测试方法,测试完成后自动回滚
3.4.2 Junit5常用注解
@Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
@ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
@RepeatedTest :表示方法可重复执行,下方会有详细介绍
@DisplayName :为测试类或者测试方法设置展示名称
@BeforeEach :表示在每个单元测试之前执行
@AfterEach :表示在每个单元测试之后执行
@BeforeAll :表示在所有单元测试之前执行
@AfterAll :表示在所有单元测试之后执行
@Tag :表示单元测试类别,类似于JUnit4中的@Categories
@Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
@Timeout :表示测试方法运行如果超过了指定时间将会返回错误
@ExtendWith :为测试类或测试方法提供扩展类引用
import org.junit.jupiter.api.Test; //注意这里使用的是jupiter的Test注解!!
public class TestDemo { @Test @DisplayName("第一次测试") public void firstTest() { System.out.println("hello world"); }