Springboot技术要点捡漏

介绍

本文将对Springboot框架开发过程中常用的技术要点进行汇总,包括过滤器与监听器引入文件上传下载集成Thymeleaf模板Thymeleaf模板语法整合Hibernate-validate校验框架集成Spring Data Redis缓存集成Quartz定时任务框架Cron表达式常见用法全局异常实现集成Mybatis框架等方面。

目录

一、项目初始化

1.1、下载项目

Springboot项目初始化很简单,直接去官网下载即可,我这是选的是Maven项目,版本是2.1.13.RELEASE。

1.2、方法验证

1.2.1、引入pom依赖
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
1.2.2、新建TestController类

编写一个getMsg()测试方法,启动DemoApplication。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
public class TestController {

    @RequestMapping("/test")
    @ResponseBody
    public Map<String, Object> getMsg(){
        Map<String, Object> map = new HashMap<>();
        map.put("result", "response msg");
        return map;
    }

}
1.2.3、效果查看

在浏览器中访问http://localhost:8080/test地址,请求响应成功
在这里插入图片描述

二、过滤器与监听器

这两者项目中很常见,我们这里使用注解模式实现

2.1、过滤器Filter

1)在config目录下,新建MyFilter过滤器,这里需要使用到@WebFilter注解。
filterName为过滤器名称,urlPatterns匹配过滤请求类型(这里只配置了上文的/test请求,实际可根据需求配置多个,如{"*.do","*.jsp"}等),然后在doFilter()中编写处理逻辑。

package com.example.demo.config;


import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

/**
 * 自定义过滤器(注解方式)
 */
@WebFilter(filterName = "MyFilter", urlPatterns = {"/test"})
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("********** filter start **********");
        filterChain.doFilter(servletRequest, servletResponse);
        System.out.println("********** filter end **********");
    }

    @Override
    public void destroy() {

    }
}

2)在启动类DemoApplication中增加组件扫描注解@ServletComponentScan
3)启动应用,访问http://localhost:8080/test,看到控制台输出如下日志,说明过滤器已经生效了。

2020-06-09 07:53:05.307  INFO 21184 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-06-09 07:53:05.316  INFO 21184 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 9 ms
********** filter start **********
********** filter end **********

2.2、监听器Listenser

1)在config目录下,新建MyListener监听器,这里需要使用到@WebListener注解,在contextInitialized()中编写处理逻辑。

package com.example.demo.config;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
 *  自定义监听器(注解方式)
 */
@WebListener
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("***** init MyListener *****");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {

    }
}

2)启动应用时看到控制台输出如下日志,说明监听器已经生效了。

2020-06-09 07:55:09.607  INFO 16296 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1182 ms
***** init MyListener *****
2020-06-09 07:55:09.821  INFO 16296 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'

三、集成Thymeleaf

常用的模板比较多,比如freemaker,我们这里使用官网推荐的Thymeleaf模板

3.1、集成步骤

3.1.1、引入pom依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
3.1.2、编写html

templates目录下,创建index.html页面,使用th:text进行文本取值

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
    <!-- th:text取值 -->
    <span th:text="${result}"></span>
</body>
</html>
3.1.3、编写Controller方法
package com.example.demo.controller;

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

@Controller
public class IndexController {

    /**
     * 跳转至index界面
     * @param model
     * @return
     */
    @RequestMapping("/index")
    public String index(Model model){
        model.addAttribute("result","success");
        return "index";
    }
}
3.1.4、效果查看

启动应用,访问http://localhost:8080/index
在这里插入图片描述

3.2、Thymeleaf常用语法

Thymeleaf模板有一套自己的语法格式,包括字符串取值日期格式化条件判断list和map遍历域对象Url处理等,下面我们列举一些实例

3.2.1、字符串取值
	<!-- th:text取值 -->
    1.<span th:text="${result}"></span><hr/>

    <!-- input标签取值 -->
    2.<input type="text" th:value="${result}" /><hr/>

    <!-- 字符串判空 -->
    3.<span th:text="${#strings.isEmpty(result)}"></span><hr/>

    <!-- 获取字符串长度 -->
    4.<span th:text="${#strings.length(result)}"></span><hr/>

    <!-- 字符串截取 -->
    5.<span th:text="${#strings.substring(result,4)}"></span><hr/>
3.2.2、日期格式化
 	<!-- 日期格式化 -->
    6.<span th:text="${#dates.format(date)}"></span><hr/>

    <!-- 日期格式化(指定格式) -->
    7.<span th:text="${#dates.format(date,'yyyy-MM-dd')}"></span><hr/>
3.2.3、条件判断
	<!-- if判断 -->
    8.<span th:if="${language}==php">php是世界上最好的语言</span><hr/>

    <!-- switch判断(等价于if else效果) -->
    9.<span th:switch="${language}">
        <span th:case="php">php是世界上最好的语言</span>
        <span th:case="java">java表示不服</span>
        <span th:case="python">python表示不服</span>
        </span><hr/>
3.2.4、list和map遍历
	<!-- 遍历list -->
    10.<table>
            <tr>
                <th>序号</th>
                <th>名称</th>
                <th>排名</th>
            </tr>
            <!-- obj表示数据对象,v表示状态变量(包括index、count、size等参数) -->
            <tr th:each="obj,v : ${list}">
                <td th:text="${v.index}"></td>
                <td th:text="${obj.name}"></td>
                <td th:text="${obj.sort}"></td>
            </tr>
        </table><hr/>

    <!-- 遍历map -->
    11.<table>
        <tr>
            <th>名称</th>
            <th>排名</th>
        </tr>
        <!-- 需要迭代两次 -->
        <tr th:each="maps : ${map}">
            <td th:each="entry:${maps}" th:text="${entry.value.name}"></td>
            <td th:each="entry:${maps}" th:text="${entry.value.sort}"></td>
        </tr>
    </table><hr/>
3.2.5、域对象
	<!-- 域对象取值方式 -->
    12.Request:<span th:text="${#httpServletRequest.getAttribute('req')}"></span>|
        Session:<span th:text="${session.se}"></span>|
        Application:<span th:text="${application.app}"></span><hr/>
3.2.6、Url处理
	<!-- url处理 -->
    13.<a th:href="@{https://blog.csdn.net}" target="_blank" >绝对路径</a>|
        <a th:href="@{/welcome}" target="_blank" />相对路径</a>|
        <a th:href="@{/welcome(name=python, sort=第二)}" target="_blank" />带参数url</a><hr/>
3.2.7、后台方法
package com.example.demo.controller;

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

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Controller
public class IndexController {

    /**
     * 跳转至index界面
     * @param model
     * @return
     */
    @RequestMapping("/index")
    public String index(Model model, HttpServletRequest req){
        model.addAttribute("result","success");
        model.addAttribute("date",new Date());
        model.addAttribute("language","php");

        //构造list
        List<Language> list = new ArrayList<>();
        list.add(new Language("java","第一"));
        list.add(new Language("python","第二"));
        list.add(new Language("php","第三"));
        model.addAttribute("list",list);

        //构造map
        Map<String, Language> map = new HashMap<>();
        map.put("key1",new Language("java","第一"));
        map.put("key2",new Language("python","第二"));
        map.put("key3",new Language("php","第三"));
        model.addAttribute("map",map);

        //域对象
        req.setAttribute("req","HttpServletRequest对象");
        req.getSession().setAttribute("se","HttpSession对象");
        req.getSession().getServletContext().setAttribute("app","Application对象");

        return "index";
    }

    /**
     * java、python、php
     */
    class Language{
        private String name;//语言名称
        private String sort;//排名

        public Language(String name, String sort) {
            this.name = name;
            this.sort = sort;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getSort() {
            return sort;
        }

        public void setSort(String sort) {
            this.sort = sort;
        }
    }

    /**
     * 跳转至welcome界面
     * @param name
     * @param sort
     * @return
     */
    @RequestMapping("/welcome")
    public String index(String name, String sort){
        System.out.println("name="+name+",sort="+sort);
        return "welcome";
    }
}
3.2.8、效果查看

启动应用,访问http://localhost:8080/index
在这里插入图片描述

四、文件上传

文件上传功能是每个系统中必备的功能,这里会演示上传流程和阈值设置方法

4.1、编写上传页面

templates目录下,创建testUpload.html,并编写上传form表单

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>TestUpload</title>
</head>
<body>
    <!-- 文件上传form表单 -->
    <form action="myUpload" method="post" enctype="multipart/form-data" >
        <input type="file" name="fileName" />
        <input type="submit" value="上传" />
    </form>
</body>
</html>

4.2、Controller方法

创建TestUploadController,编写upload()上传方法

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;

@Controller
public class TestUploadController {

    /**
     * 跳转至上传页面
     * @return
     */
    @RequestMapping("/toUpload")
    public String toUpload(){
        return "/testUpload";
    }

    /**
     * 测试文件上传
     * @param fileName
     * @return
     * @throws IOException
     */
    @RequestMapping("/myUpload")
    @ResponseBody
    public String upload(MultipartFile fileName) throws IOException{
        //1.获取上传文件名
        String originalFilename = fileName.getOriginalFilename();

        //2.将文件保存到对应目录
        File newFileName = new File("d:/" + originalFilename);
        fileName.transferTo(newFileName);

        //3.返回处理状态
        return "success";
    }

}

4.3、效果查看

启动应用,访问http://localhost:8080/toUpload,选择一张图片,点击上传,可以看到图片上传到了D盘中
在这里插入图片描述
在这里插入图片描述

4.4、文件大小阈值设置

通常我们会限制上传文件的大小,这只要在application.properties中配置两个属性即可,第一个表示单个文件上传限制(默认1MB),第二个表示单次请求文件上传限制(默认10MB)

# 单个文件上传限制(默认1MB)
spring.servlet.multipart.max-file-size=50MB
# 单次请求文件上传限制(默认10MB)
spring.servlet.multipart.max-request-size=100MB

五、整合Hibernate-validate

5.1、在请求对象中添加校验注解

后端参数校验也是项目中必不可少的一部分,我们这里使用Hibernate-validate与Thymeleaf整合,编写一个实例。
在model包中新建请求对象UserReq,再给字段添加判断注解即可,比如判空校验长度校验格式校验

package com.example.demo.model;

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

@Data
public class UserReq {

    @NotBlank(message = "用户名必填")
    @Length(min = 6,max = 15,message = "用户名长度为6-15位")
    private String username;

    @NotBlank(message = "密码必填")
    private String password;

    @Min(value = 18,message = "年龄不能小于18岁")
    private int age;

    @Email(message = "邮箱格式不合法")
    private String email;
}

备注:这里使用了lombok注解,需要先引入pom依赖

	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.16.20</version>
	</dependency>

5.2、在控制器中方法中开启校验模式

这里需注意两点,第一,在UserReq对象前面加上@Valid注解即开启校验模式。第二,使用@ModelAttribute("validTip")注解指定校验信息取值方式,validTip是自定义的,这个在html中获取校验信息时会用到。

package com.example.demo.controller;

import com.example.demo.model.UserReq;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.validation.Valid;

@Controller
@Slf4j
public class UserController {


    /**
     * 使用@ModelAttribute("validTip")指定校验失败提示信息key为validTip
     * @param req
     * @return
     */
    @RequestMapping("/addUser")
    public String showPage(@ModelAttribute("validTip") UserReq req){
        return "/user/add";
    }

    /**
     * Valid 注解示开启数据校验
     * BindingResult 返回校验的结果
     **/
    @RequestMapping("/save")
    public String saveUser(@ModelAttribute("validTip") @Valid UserReq req, BindingResult result){
        //如果校验不通过,返回原界面
        if(result.hasErrors()){
            return "/user/add";
        }
        log.info(" valid success");
        return "success";
    }
}

5.3、视图模板回显提示信息

在templates目录下创建user文件夹,再新建add.html模板。可以看到,我们通过th:errors="${validTip.username}方式获取校验提示信息,validTip是我们之前在控制方法中指定的,username为对应字段名。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/save}" method="post">
    姓名:<input type="text" name="username"/><font color="red" th:errors="${validTip.username}"></font><br/>
    密码:<input type="password" name="password" /><font color="red" th:errors="${validTip.password}"></font><br/>
    年龄:<input type="text" name="age" /><font color="red" th:errors="${validTip.age}"></font><br/>
    邮箱:<input type="text" name="email" /><font color="red" th:errors="${validTip.email}"></font><br/>
    <input type="submit" value="提交"/> </form>
</body>
</html>

5.4、效果查看

在这里插入图片描述

六、全局异常实现

6.1、编写异常测试类

这里定义了两个方法,分别为sysExceptionTest()bizExceptionTest()

package com.example.demo.controller;

import com.example.demo.exception.biz.BizException;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestExceptionController {

    /**
     * 系统异常测试方法
     * @return
     */
    @RequestMapping("/sysException")
    public String sysExceptionTest(){
        // 模拟一个空指针异常
        String str = null;
        System.out.println(str.hashCode());
        return "index";
    }

    /**
     * 业务异常测试方法
     * @return
     */
    @RequestMapping("/bizException")
    public String bizExceptionTest(){
        // 自定义异常提示
        if(true){
            throw new BizException("-1","这是一个自定义异常提示");
        }
        return "index";
    }

}

6.2、自定义业务异常类BizException

package com.example.demo.exception;

public class BizException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    protected String errorCode; //错误码
    protected String errorMsg; //错误信息

    public BizException() {
        super();
    }

    public BizException(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg) {
        super(errorCode);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public BizException(String errorCode, String errorMsg, Throwable cause) {
        super(errorCode, cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getMessage() {
        return errorMsg;
    }

    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

6.3、定义全局异常捕获类GlobalException

这里需要注意一点,如果是想将所有异常抛到统一的error界面,则instanceof Exception即可。如果是想将不同异常分别跳转至不同错误页,则需根据具体异常类型判断。

package com.example.demo.exception;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 全局异常类
 */
@Configuration
public class GlobalException implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {

        ModelAndView mv = new ModelAndView();

        //1、所有异常统一抛到error.html界面
        if(e instanceof Exception){
            mv.setViewName("error");
        }

        //2、不同异常类型,跳转至自定义的不同界面
//        if(e instanceof NullPointerException){
//            mv.setViewName("error1");
//        }
//        if(e instanceof ArithmeticException){
//            mv.setViewName("error2");
//        }
//        if(e instanceof ClassCastException){
//            mv.setViewName("error3");
//        }
//        if(e instanceof ArrayIndexOutOfBoundsException){
//            mv.setViewName("error4");
//        }

        //3、将异常信息放入ModelAndView对象返回
        mv.addObject("errorMsg", e.toString());
        return mv;
    }
}

6.4、编写统一异常页面error.html

在templates目录下创建error.html模板,errorMsg为GlobalException中捕获返回的异常信息

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>异常信息界面</title>
</head>
<body>
        哎呀,服务器出错了!!!<br/>
        异常信息:<span th:text="${errorMsg}"></span>
</body>
</html>

6.5、效果查看

1)访问http://localhost:8080/sysException
在这里插入图片描述
2)访问http://localhost:8080/bizException
在这里插入图片描述

七、集成Spring Data Redis

7.1、引入依赖

先引入redis的pom依赖

		<!-- 引入spring data redis -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<!-- 引入jedis -->
		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.9.0</version>
		</dependency>

7.2、创建RedisConfig配置类

初始化redis连接池和连接工厂,并创建redisTemplate操作模板。
说明@ConfigurationProperties(prefix = "spring.redis.pool")@ConfigurationProperties(prefix = "spring.redis")分别对应application.properties中的配置信息,它们会将指定的前缀相同各个配置实例化为一个配置对象。

package com.example.demo.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;

@Configuration
public class RedisConfig {

    /**
     * 连接池配置
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.redis.pool")
    public JedisPoolConfig jedisPoolConfig(){
        return new JedisPoolConfig();
    }

    /**
     *  连接工厂配置
     * @param cfg
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.redis")
    public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig cfg){
        JedisConnectionFactory factory = new JedisConnectionFactory();
        factory.setPoolConfig(cfg);
        return factory;
    }

    /**
     *  RedisTemplate操作模板
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String,Object> redisTemplate(JedisConnectionFactory factory){
        RedisTemplate<String, Object> rt = new RedisTemplate<>();
        rt.setConnectionFactory(factory);
        rt.setKeySerializer(new StringRedisSerializer());//key序列化
        rt.setValueSerializer(new StringRedisSerializer());//value序列化
        return rt;
    }
}

7.3、添加配置信息

在application.properties中添加如下配置

# redis配置信息
# 分别为最小空闲连接、最大空闲连接、最大连接数、ip、端口、指定库(默认0)
spring.redis.pool.min-idle=5
spring.redis.pool.max-idle=10
spring.redis.pool.max-total=20
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=1

7.4、新建一个MovieInfo对象

package com.example.demo.entity;


import lombok.Data;

import java.io.Serializable;

/**
 *  电影实体类
 */
@Data
public class MovieInfo implements Serializable {

    private Long id;    //主键
    private String name;    //名称
    private String role;    //主演
    private String area;    //地区(大陆、香港、美国)

}

7.5、效果查看

在Junit测试类中,使用3种方法进行存/取值
1、字符串存/取

	@Autowired
	private RedisTemplate<String, Object> redisTemplate;

	/**
	 *  redis存值(字符串)
	 */
	@Test
	public void redisSetStr(){
		this.redisTemplate.opsForValue().set("movieName","无间道");
	}

	/**
	 *  redis取值(字符串)
	 */
	@Test
	public void redisGetStr(){
		System.out.println("get str: "+this.redisTemplate.opsForValue().get("movieName"));
	}

2、对象存/取

/**
	 *  redis存值(对象)
	 */
	@Test
	public void redisSetObj(){
		MovieInfo movie = new MovieInfo();
		movie.setId(1L);
		movie.setName("当幸福来敲门");
		movie.setRole("威尔史密斯");
		movie.setArea("美国");
		this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
		this.redisTemplate.opsForValue().set("movie",movie);
	}

	/**
	 *  redis取值(对象)
	 */
	@Test
	public void redisGetObj(){
		this.redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
		MovieInfo movie = (MovieInfo) this.redisTemplate.opsForValue().get("movie");
		System.out.println("get obj: "+movie);
	}

3、Json对象存/取

/**
	 *  redis存值(json格式对象)
	 */
	@Test
	public void redisSetJsonObj(){
		MovieInfo movie = new MovieInfo();
		movie.setId(1L);
		movie.setName("当幸福来敲门");
		movie.setRole("威尔史密斯");
		movie.setArea("美国");
		this.redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(MovieInfo.class));
		this.redisTemplate.opsForValue().set("movie_json",movie);
	}

	/**
	 *  redis取值(json格式对象)
	 */
	@Test
	public void redisGetJsonObj(){
		this.redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(MovieInfo.class));
		MovieInfo movie = (MovieInfo) this.redisTemplate.opsForValue().get("movie_json");
		System.out.println("get json obj: "+movie);
	}

效果如下
在这里插入图片描述

八、集成Quartz定时任务框架

8.1、集成步骤

8.1.1、引入pom依赖
	<!-- 引入Scheduled -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context-support</artifactId>
		</dependency>
		<!-- 引入Quartz -->
		<dependency>
			<groupId>org.quartz-scheduler</groupId>
			<artifactId>quartz</artifactId>
			<version>2.3.0</version>
			<exclusions>
				<exclusion>
					<artifactId>slf4j-api</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<!-- 引入Sprng tx -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
		</dependency>
8.1.2、自定义定时任务

自定义TestJob定时任务

package com.example.demo.job;

import org.quartz.Job;
import org.quartz.JobExecutionContext;

import java.util.Date;

/**
 *  测试job
 */
public class TestJob  implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) {
        System.out.println("定时Job执行 :"+ new Date());
    }
}
8.1.3、扩展AdaptableJobFactory类
package com.example.demo.job;

import org.quartz.spi.TriggerFiredBundle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.scheduling.quartz.AdaptableJobFactory;
import org.springframework.stereotype.Component;

/**
 * 自定义扩展AdaptableJobFactory
 */
@Component("AdaptableJobFactoryExtend")
public class AdaptableJobFactoryExtend extends AdaptableJobFactory {

    //AutowireCapableBeanFactory 可以将一个对象添加到 SpringIOC 容器中, 并且完成该对象注入
    @Autowired
    private AutowireCapableBeanFactory autowireCapableBeanFactory;

    /*** 该方法需要将实例化的任务对象手动的添加到 springIOC 容器中并且完成对 象的注入 */
    @Override
    protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
        Object obj = super.createJobInstance(bundle);
        //将 obj 对象添加 Spring IOC 容器中,并完成注入,否则定时任务调用业务对象会报错
        this.autowireCapableBeanFactory.autowireBean(obj);
        return obj;
    }
}

8.1.4、定义QuartzConfig配置类
package com.example.demo.job;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;

@Configuration
public class QuartzConfig {

    /**
     * 创建Job任务
     * @return
     */
    @Bean
    public JobDetailFactoryBean jobDetailFactoryBean(){
        JobDetailFactoryBean job = new JobDetailFactoryBean();
        job.setJobClass(TestJob.class);//引入自定义 Job 类
        return job;
    }

    /**
     * 创建Cron表达式Trigger触发器
     * @param job
     * @return
     */
    @Bean
    public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean job){
        CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
        trigger.setJobDetail(job.getObject());
        trigger.setCronExpression("0/5 * * * * ?"); //每5秒执行一次
        return trigger;
    }

    /**
     * 创建Scheduler任务调度器
     * @param trigger
     * @return
     */
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean trigger,AdaptableJobFactoryExtend jobExtend){
        SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
        scheduler.setTriggers(trigger.getObject());//放入trigger触发器
        scheduler.setJobFactory(jobExtend);
        return scheduler;
    }
}

8.1.5、效果查看

启动可以看到,每隔5秒会执行一次TestJob任务

2020-06-09 21:56:39.974  INFO 11444 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-09 21:56:39.977  INFO 11444 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 3.166 seconds (JVM running for 4.692)
定时Job执行 :Tue Jun 09 21:56:40 CST 2020
定时Job执行 :Tue Jun 09 21:56:45 CST 2020
定时Job执行 :Tue Jun 09 21:56:50 CST 2020
定时Job执行 :Tue Jun 09 21:56:55 CST 2020

8.2、Cron表达式常见用法

定时任务规则通常会使用Cron表达式配置,上小结中我们也使用到了,这里对它再进行详细说明

8.2.1、简介

Cron 表达式是一个字符串,分为 6 或 7 个域,每一个域代表一个含义

两种语法格式
Seconds Minutes Hours Day Month Week (即 秒 分 时 日 月 周)
Seconds Minutes Hours Day Month Week Year (即 秒 分 时 日 月 周 年)
8.2.2、各域含义
位置时间取值范围支持特殊字符
10-59, - * /
20-59, - * /
30-23, - * /
41-31, - * / L W C
51-12, - * /
61-7, - * ? / L W C
7年(可选)1970-2099, - * /

说明:这里的周(1-7天)是按西方习惯来的,表示从周日开始到下周周六,不是我们习惯的周一到周日,使用时需要注意。

8.2.3、常用特殊字符介绍

1、星号:可用在所有域中,表示对应时间域的每一个时刻,例如,星号在小时域时,表示“每小时”;
2、问号(?):该字符只在日期和周域中使用,它通常指定为“无意义的值”,相当于占位符;(通常日期和周只选择其一设置,因为同时使用会有可能冲突)
3、减号(-):表示范围,如在小时域中使用“8-10”,则表示从 8 到 10 点,即 8,9,10;
4、逗号(,):表示列表值,如在周域中使用“MON,WED,FRI”,则表示星期一,星期三和星期 五;
5、斜杠(/):x/y 表示等步长序列,x 为起始值,y 为增量步长值。如在分钟域中使用 0/15,则 表示为 0,15,30 和 45 秒,而 5/15 在分钟域中表示 5,20,35,50,你也可以使用*/y,它等同于 0/y;

8.2.4、举例

1、@Scheduled(cron="0 0 2 * * *")表示每天凌晨 2 点执行
2、@Scheduled(cron = "0 0 2 10 * ?")每月10号凌晨 2 点执行
3、@Scheduled(cron = "0 0 2 10 1,4,7,10 ?")每个季度的第一个月的10号凌晨 2 点执行
4、@Scheduled(cron = "0 0 2 10 6 ?")每年6月的10号凌晨 2 点执行

九、集成Mybatis

Mybatis框架不用多说了,平时用的很多持久层框架,我们这里结合一个电影demo,实现简单的增删改查操作即可

9.1、引入pom依赖

我们数据库使用Mysql,连接池使用Druid

	<!-- 引入mybatis依赖 -->
		<dependency> <groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.1.1</version>
		</dependency>
		<!-- 引入mysql依赖 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<!-- 引入druid连接池依赖 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>

9.2、配置文件编写

需要配置mysql连接、指定druid数据源和指定sql映射文件位置

# 配置数据库连接
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/testdb?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root

# 指定数据源类型
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

# 开启sql日志输出(两种方式都可以)
logging.level.com.example.demo.mapper=debug
#mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

# 指定sql映射文件位置
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml

9.3、创建电影表

CREATE TABLE `t_movie_info` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(32) DEFAULT NULL COMMENT '名称',
  `role` varchar(32) DEFAULT NULL COMMENT '主演',
  `area` varchar(16) DEFAULT NULL COMMENT '地区(大陆、香港、美国)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='电影信息表';

9.4、entity实体

与数据层对应,集成了lombok插件,无需编写构造方法和get、set方法

package com.example.demo.entity;


import lombok.Data;

/**
 *  电影实体类
 */
@Data
public class MovieInfo {

    private Long id;    //主键
    private String name;    //名称
    private String role;    //主演
    private String area;    //地区(大陆、香港、美国)

}

9.5、model实体

与view层对应
1)新增功能对应请求实体类;

package com.example.demo.model;

import lombok.Data;

/**
 * 新增请求实体
 */
@Data
public class AddMovieReq {

    private String name;    //名称
    private String role;    //主演
    private String area;    //地区(大陆、香港、美国)

}

2)编辑功能对应请求实体类;

package com.example.demo.model;

import lombok.Data;

/**
 * 编辑请求实体
 */
@Data
public class EditMovieReq {

    private Long id;
    private String name;    //名称
    private String role;    //主演
    private String area;    //地区(大陆、香港、美国)

}

9.6、controller层

package com.example.demo.controller;

import com.example.demo.entity.MovieInfo;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;
import com.example.demo.service.IMovieInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Controller
@RequestMapping("/movie")
public class MovieInfoController {

    @Autowired
    private IMovieInfoService movieInfoService;

    /**
     * 页面跳转
     * @param page
     * @return
     */
    @RequestMapping("/{page}")
    public String showPage(@PathVariable String page){
        return "/movie/"+page;
    }

    /**
     * 新增
     * @param req
     * @return
     */
    @RequestMapping("/addSubmit")
    public String addMovie(AddMovieReq req){
        this.movieInfoService.addMovie(req);
        return "redirect:/movie/findMovieInfoList";
    }

    /**
     * 列表查询
     * @param model
     * @return
     */
    @RequestMapping("/findMovieInfoList")
    public String findMovieInfoList(Model model){
        List<MovieInfo> list = this.movieInfoService.findMovieInfoList();
        model.addAttribute("list", list);
        return "/movie/list";
    }

    /**
     * 根据ID查询
     * @param id
     * @param model
     * @return
     */
    @RequestMapping("/findMovieInfoById")
    public String findMovieInfoById(Long id,Model model){
        MovieInfo movieInfo = this.movieInfoService.selectMovieInfoById(id);
        model.addAttribute("movieInfo", movieInfo);
        return "/movie/edit";
    }

    /**
     * 编辑
     * @param req
     * @return
     */
    @RequestMapping("/editSubmit")
    public String editMovieInfo(EditMovieReq req){
        this.movieInfoService.updateMovieInfo(req);
        return "redirect:/movie/findMovieInfoList";
    }

    /**
     * 删除
     * @param id
     * @return
     */
    @RequestMapping("/delMovieInfoById")
    public String delMovieInfoById(Long id){
        this.movieInfoService.deleteMovieInfoById(id);
        return "redirect:/movie/findMovieInfoList";
    }
}

9.7、service层

1)接口层

package com.example.demo.service;

import com.example.demo.entity.MovieInfo;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;

import java.util.List;

public interface IMovieInfoService {

    /**
     *  新增电影
     * @param req
     */
    void addMovie(AddMovieReq req);

    /**
     * 列表查询
     * @return
     */
    List<MovieInfo> findMovieInfoList();

    /**
     * 根据ID查询
     * @param id
     * @return
     */
    MovieInfo selectMovieInfoById(Long id);

    /**
     * 修改
     */
    void updateMovieInfo(EditMovieReq req);

    /**
     * 删除
     * @param id
     */
    void deleteMovieInfoById(Long id);
}

2)实现层

package com.example.demo.service.impl;

import com.example.demo.entity.MovieInfo;
import com.example.demo.mapper.MovieInfoMapper;
import com.example.demo.model.AddMovieReq;
import com.example.demo.model.EditMovieReq;
import com.example.demo.service.IMovieInfoService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
public class MovieInfoServiceImpl implements IMovieInfoService {

    @Autowired
    private MovieInfoMapper movieInfoMapper;

    @Override
    public void addMovie(AddMovieReq req) {
        MovieInfo info = new MovieInfo();
        BeanUtils.copyProperties(req,info);
        this.movieInfoMapper.insertMovieInfo(info);
    }

    @Override
    public List<MovieInfo> findMovieInfoList() {
        return this.movieInfoMapper.selectMovieInfoList();
    }

    @Override
    public MovieInfo selectMovieInfoById(Long id) {
        return this.movieInfoMapper.selectMovieInfoById(id);
    }

    @Override
    public void updateMovieInfo(EditMovieReq req) {
        MovieInfo info = new MovieInfo();
        BeanUtils.copyProperties(req,info);
        this.movieInfoMapper.updateMovieInfo(info);
    }

    @Override
    public void deleteMovieInfoById(Long id) {
        this.movieInfoMapper.deleteMovieInfoById(id);
    }
}

9.8、mapper层

package com.example.demo.mapper;

import com.example.demo.entity.MovieInfo;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface MovieInfoMapper {

    void insertMovieInfo(MovieInfo info);

    List<MovieInfo> selectMovieInfoList();

    MovieInfo selectMovieInfoById(Long id);

    void updateMovieInfo(MovieInfo info);

    void deleteMovieInfoById(Long id);

}

9.9、sql编写

resources目录下创建mapper文件夹,再新建MovieInfoMapper.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.example.demo.mapper.MovieInfoMapper">

    <insert id="insertMovieInfo" parameterType="com.example.demo.entity.MovieInfo">
        insert into t_movie_info(name, role, area) values(#{name}, #{role}, #{area})
    </insert>

    <select id="selectMovieInfoList" resultType="com.example.demo.entity.MovieInfo">
        select id , name, role, area from t_movie_info order by id
    </select>

    <select id="selectMovieInfoById" resultType="com.example.demo.entity.MovieInfo">
        select id , name, role, area from t_movie_info where id = #{value}
    </select>

    <update id="updateMovieInfo" parameterType="com.example.demo.entity.MovieInfo">
        update t_movie_info set name=#{name}, role=#{role}, area=#{area} where id=#{id}
    </update>

    <delete id="deleteMovieInfoById">
        delete from t_movie_info where id = #{value}
    </delete>
</mapper>

9.10、html模板编写

在templates目录下创建movie文件夹,然后编写对应thymeleaf模板
1)列表查询:list.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>列表查询</title>
</head>
<body>
    <a th:href="@{/movie/add}">新增</a>
    <table border="1" style="width:360px;">
        <tr>
            <th>ID</th>
            <th>电影名称</th>
            <th>主演</th>
            <th>地区</th>
            <th>操作</th>
        </tr>
        <tr th:each="movie : ${list}">
            <td th:text="${movie.id}"></td>
            <td th:text="${movie.name}"></td>
            <td th:text="${movie.role}"></td>
            <td th:text="${movie.area}"></td>
            <td>
                <a th:href="@{/movie/findMovieInfoById(id=${movie.id})}">编辑</a>
                <a th:href="@{/movie/delMovieInfoById(id=${movie.id})}">删除</a>
            </td>
        </tr>
    </table>
</body>
</html>

2)新增:add.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>新增电影</title>
</head>
<body>
    <form th:action="@{/movie/addSubmit}" method="post">
        名称:<input type="text" name="name"/><br/>
        主演:<input type="text" name="role"/><br/>
        地区:<input type="text" name="area"/><br/>
        <input type="submit" value="新增"/><br/>
    </form>
</body>
</html>

3)编辑:edit.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>编辑电影</title>
</head>
<body>
<form th:action="@{/movie/editSubmit}" method="post">
    <input type="hidden" name="id" th:field="${movieInfo.id}"/>
    名称:<input type="text" name="name" th:field="${movieInfo.name}"/><br/>
    主演:<input type="text" name="role" th:field="${movieInfo.role}"/><br/>
    地区:<input type="text" name="area" th:field="${movieInfo.area}"/><br/>
    <input type="submit" value="修改"/><br/>
</form>
</body>
</html>

9.11、启动类注解

在启动类DemoApplication上添加mapper扫描注解

@MapperScan("com.example.demo.mapper")  //扫描mapper接口

9.12、效果查看

启动应用,访问http://localhost:8080/movie/findMovieInfoList地址,即可进行列表的增删改成操作
在这里插入图片描述

结语

至此,我们已经详细介绍了很多Springboot中的常用技术点,当然这只是其中的一部分,在项目开发过程中,我们还需要根据实际业务需求,集成其它各种的插件或框架。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值