SpringBoot 学习
- Spring是什么?
- 什么是 SpringBoot
- 为什么使用 SpringBoot
- 什么是Spring MVC ?
- SpringMVC的特点:
- SpringBoot 和SpringMVC的区别
- SpringBoot 和 SpringCloud的区别
- springboot整合 web功能
- 在 springboot 实现文件上传
- SpringBoot 修改访问请求后缀
- SpringBoot使用拦截器处理日志
- springboot 整合视图层技术
- springboot 服务器端表单数据校验
- springboot 异常处理五种方式
- springboot 单元测试
- springboot 热部署
- 属性注入方式
- SpringBoot 整合 SpringData JPA
- SpringBoot 缓存技术
- SpringBoot 使用 spring cache 缓存注解
Spring是什么?
Spring 是分层的 JavaEE 开源框架,主要作用是为了解决企业级开发的复杂度问题,而复杂度问题也就是耦合度问题,Spring 以 IOC 和 AOP 为内核(当然远不止这些),提供了表现层和持久层以及业务层事务管理等众多企业级应用技术,还能整合众多的第三方框架和类库,现在是使用最多的JavaEE企业应用开源框架
什么是 SpringBoot
新一代开发标准,简化了开发流程,提高了开发效率,并且提供了开箱即用的特性
为什么使用 SpringBoot
传统的 ssm 或者 ssh 最大缺点:开发效率低, Jar 冲突,配置多,springboot 底层帮你实现版本统一(maven继承原理)
1、能快速整合第三方框架
2、简化 XML 配置,完全采用注解化,内置 http 服务器(tomcat和jetty)
3、最终是以 Java 应用程序执行
什么是Spring MVC ?
Spring MVC是一个基于Java的实现了 MVC 设计模式的请求驱动类型的轻量级 Web 框架,通过把 Model,View,Controller 分离,将 web 层进行职责解耦,把复杂的web应用分成逻辑清晰的几部分,简化开发,方便组内开发人员之间的配合。
SpringMVC的特点:
1、轻量级,简单易学
2、高效,基于请求响应的MVC框架
3、与Spring兼容性好,无缝结合
4、约定优于配置
5、功能强大:RESTful、数据验证、格式化、本地化、主题等
6、简洁灵活
SpringBoot 和SpringMVC的区别
SpringBoot 是一个快速开发的框架,能够快速地整合第三方框架,简化XML配置,全部采用注解形式,内置 tomcat容器,帮助开发者实现快速开发,SpringBoot 的 Web 组件默认集成的是 SpringMVC框架
SpringBoot 和 SpringCloud的区别
它们是一种渐进式的关系,SpringBoot 用来快速构建单个微服务而SpringCloud 用来协调微服务,它关注全局的微服务协调整理治理方案,它将 SpringBoot 开发的一个个单体服务整合并管理起来,为各个微服务之间提供:配置管理、服务发现、路由、代理、分布式会话等集成服务
SpringBoot 可以离开 SpringCloud 单独使用,开发项目,但 SpringCloud 离不开 SpringBoot,属于依赖关系
springboot整合 web功能
整合 servlet(方式一)
导 jar 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
通过注解来完成 servlet注册
@WebServlet(name="indexServlet",urlPatterns = "/firstServlet")
public class firstServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("service 方法执行了。。");
}
}
编写启动类 扫描servlet
SpringBootApplication
@ServletComponentScan//在springboot启动时扫描@webservlet注解,并将该类实例化
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
}
整合 servlet(方式二)
通 过方法来完成 servlet 注册
public class SecondServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("第二个servlet");
}
}
编写启动类 扫描servlet
@SpringBootApplication
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
@Bean //加入IOC容器
public ServletRegistrationBean getServletBean(){
ServletRegistrationBean<SecondServlet> secondServlet = new ServletRegistrationBean(new SecondServlet());
secondServlet.addUrlMappings("/secondServlet");
return secondServlet;
}
}
整合 filter(方式一)
通过注解来完成 filter注册
//@WebFilter(name="firstFilter",urlPatterns = {"*.do", "*.jsp"})//拦截以.do .jsp结尾的资源
@WebFilter(filterName="firstFilter",urlPatterns = {"/firstServlet"})//只拦截firstServlet
public class FirstFilter implements Filter {//表示浏览器输入locallhost/firstServlet会被拦截
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("进入filter");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("放行了...");
}
}
编写启动类 扫描 filter
@SpringBootApplication
@ServletComponentScan
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
}
整合 filter(方式二)
过方法来完成 filter 注册
public class SecondFilter implements javax.servlet.Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("进入filter");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("放行了...");
}
}
编写启动类 扫描 filter
@SpringBootApplication
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
@Bean //加入IOC容器
public FilterRegistrationBean getFilterBean(){
FilterRegistrationBean bean = new FilterRegistrationBean(new SecondFilter());
//bean.addUrlPatterns(new String[]{".do", "*.jsp"});
bean.addUrlPatterns("/secondServlet");
return bean;
}
}
整合 listener (方式一)
通过注解完成 注册
@WebListener
public class FirstListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("监听器已启动");
}
}
@SpringBootApplication
@ServletComponentScan
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
}
整合 listener (方式二)
public class SecondListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("第二个监听器已启动");
}
}
@SpringBootApplication
public class SpringbootStartApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootStartApplication.class, args);
}
@Bean //注册 加入IOC容器
public ServletListenerRegistrationBean<SecondListener> getListenerBean(){
ServletListenerRegistrationBean<SecondListener> listener = new ServletListenerRegistrationBean<>(new SecondListener());
return listener;
}
}
访问静态资源
方式一:默认 static 目录
spring 官方建议静态资源(图片、html…)统一放入 resource/static 文件夹下,览器输入 http://localhost:8080/aa.jpg或者http://localhost:8080/index.html 都可以访问到资源
方式二:ServletContext根目录( webapp 目录)
名字必须叫 webapp, 为了让新建的 webapp 可以建 Jsp等资源,先要把它变成 web 目录,如下:
接着为了使得能够访问 webapp下的资源,还须做如下设置,配置完重启即可访问
在 springboot 实现文件上传
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<form action="upload/uploadfile" method="post" enctype="multipart/form-data">
文件:<input type="file" name="multipartFile"><br>
<input type="submit" value="开始上传">
</form>
</body>
</html>
//@Controller
@RestController // 表示这个类下所有的方法的返回值为 json 格式
public class FileUploadController {
//multipartFile 必须跟页面 file 表单 name 相同
@RequestMapping("upload/uploadfile")
public @ResponseBody Map<String,String> uploadFile(MultipartFile multipartFile, HttpServletRequest request){
Map<String,String> map = new HashMap<>();
try {
String path = request.getSession().getServletContext().getRealPath("/upload");
File file = new File(path);
if (!file.exists()){
file.mkdirs();
}
String fileName = multipartFile.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-", "");
fileName = uuid+"_"+fileName;
multipartFile.transferTo(new File(file,fileName));
map.put("msg","OK");
return map;
} catch (Exception e) {
e.printStackTrace();
map.put("msg","failed");
}
return map;
}
}
可以在 application.proterties 配置上传文件的限制大小
#spring.http.multipart.max-file-size=200MB 已过时
#spring.http.multipart.max-request-size=200MB 已过时
spring.servlet.multipart.max-file-size=300MB
spring.servlet.multipart.max-request-size=300MB
SpringBoot 修改访问请求后缀
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* -设置url后缀模式匹配规则
* -该设置匹配所有的后缀
*/
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(true) //设置是否是后缀模式匹配,即:/test.*
.setUseTrailingSlashMatch(true); //设置是否自动后缀路径模式匹配,即:/test/
}
/**
* -该设置指定匹配后缀*.do;
* @param dispatcherServlet servlet调度器
* @return ServletRegistrationBean
*/
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
ServletRegistrationBean servletServletRegistrationBean = new ServletRegistrationBean(dispatcherServlet);
servletServletRegistrationBean.addUrlMappings("*.do");//指定.do后缀,可替换其他后缀
return servletServletRegistrationBean;
}
}
@RestController
public class HelloController {
@GetMapping("/hello.do")//这里的.do写不写都行
@ResponseBody
public String hello(){
return "dddddd";
}
}
SpringBoot使用拦截器处理日志
拦截器定义
@Slf4j //有了这个注解 就不用定义 log 对象了
public class MyInterceptor implements HandlerInterceptor {
//private final Logger log = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.debug("preHandle execute....");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.debug("postHandle execute....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.debug("afterCompletion execute....");
}
}
拦截器配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
application.yaml设置日志级别
logging:
level:
com.lhg: debug #注意这里是个 map
springboot 整合视图层技术
springboot 整合 jsp
导入整合 jsp 所需包
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
代码
@Controller
public class IndexController {
@RequestMapping("testJsp")
public String testJsp(Model model){
//模拟查询数据库
List<Account> lists = new ArrayList<>();
lists.add(new Account(1, 200, "张三"));
lists.add(new Account(2, 250, "李四"));
model.addAttribute("lists",lists);
return "list";
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<table border="solder 1px blue" align="center" width="50%">
<tr>
<th>ID</th>
<th>姓名</th>
<th>账户金额</th>
</tr>
<c:forEach items="${lists}" var="account">
<tr align="center">
<td>${account.id}</td>
<td>${account.name}</td>
<td>${account.money}</td>
</tr>
</c:forEach>
</table>
</body>
</html>
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp
springboot 整合 freemarker
添加 freemarker 坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
freemarker 配置
#设定 ftl 文件路径
spring.freemarker.template-loader-path=classpath:/templates
#关闭缓存及时刷新
spring.freemarker.cache=false
#设定 templates的编码
spring.freemarker.charset=utf-8
# 是否检查 templates 路径是否存在
spring.freemarker.check-template-location=false
spring.freemarker.content-type=text/html
# 设定模板的后缀
spring.freemarker.suffix=.ftl
编写试图
springboot 要求模板形式的视图层技术的文件必须要放到 src/main/resource/templates 目录下
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>index</title>
</head>
<body>
<a href="testFtl">查询所有</a>
</body>
</html>
@Controller
public class IndexController {
@RequestMapping("testFtl")
public String testJsp(Model model) {
//模拟查询数据库
List<Account> lists = new ArrayList<>();
lists.add(new Account(1, 200, "张三"));
lists.add(new Account(2, 250, "李四"));
model.addAttribute("lists", lists);
return "list";
}
}
<body>
<table border="solder 1px blue" align="center" width="50%">
<tr>
<th>ID</th>
<th>姓名</th>
<th>账户金额</th>
</tr>
<#list lists as account>
<tr align="center">
<td>${account.id}</td>
<td>${account.name}</td>
<td>${account.money}</td>
</tr>
</#list>
</table>
</body>
注意部署项目运行时默认会找 index.ftl,不需要在地址栏敲 http://localhost:8080/index.ftl,而且也没有效果,直接http://localhost:8080/就行
springboot 整合 thymeleaf
导 thymeleaf 包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
创建存放视图的目录
和 freemarker 一样,必须放在 src/main/resource/templates 目录下,该目录是安全的,只有服务器级别的请求才能访问到
thymeleaf 基本使用
thymeleaf 通过它特定 语法对 html 的标记做渲染,现罗列如下常见用法:
变量输出和字符串操作:thymeleaf 语法要求 th:xx=“” 这里必须要带双引号
<body>
<span th:text="hello,刘和广"></span><br>
<!--变量输出 model.addAttribute("msg","thymeleaf language"); -->
<span th:text="${msg}"></span>
<input type="text" th:value="${msg}">
<!--判断字符串是否为空-->
<!--Thymeleaf 内置对象 注意语法:
1,调用内置对象一定要用#
2,大部分的内置对象都以 s 结尾 strings、numbers、dates.
3, API 跟 java 很多都是一样的。。 -->
<span th:text="${#strings.isEmpty(msg)}"></span><br>
<!--msg中是否包含 9-->
<span th:text="${#strings.contains(msg,'9')}"></span><br>
<!--msg中是否以 XXX 开头-->
<span th:text="${#strings.startsWith(msg,'Thy')}"></span><br>
<!--msg中是否以 XXX 结尾-->
<span th:text="${#strings.endsWith(msg,'Thy')}"></span><br>
<!--msg长度-->
<span th:text="${#strings.length(msg)}"></span><br>
<!--msg 指定字符索引位置,从零开始,没有返回 -1 -->
<span th:text="${#strings.indexOf(msg,'a')}"></span><br>
<span th:text="${#strings.substring(msg,3)}"></span><br>
<span th:text="${#strings.toUpperCase(msg)}"></span><br>
<span th:text="${#strings.toLowerCase(msg)}"></span><br>
</body>
日期操作
<!-- model.addAttribute("date",new Date());-->
<!--格式化日期,默认以浏览器默认语言为格式化标准-->
<span th:text="${#dates.format(date)}"></span><br>
<!--指定格式化日期-->
<span th:text="${#dates.format(date,'yyyy-MM-dd')}"></span><br>
<span th:text="${#dates.year(date)}"></span><br>
<span th:text="${#dates.month(date)}"></span><br>
<span th:text="${#dates.day(date)}"></span><br>
条件判断
<!--model.addAttribute("sex","男");-->
<span th:if="${sex} == '男' ">
性别:男
</span>
<span th:if="${sex} == '女' ">
性别:女
</span>
<!--model.addAttribute("id","2");-->
<div th:switch="${id}">
<span th:case="1">ID 为1</span>
<span th:case="2">ID 为2</span>
<span th:case="3">ID 为3</span>
</div>
迭代遍历 List 集合
<!--model.addAttribute("lists", lists);-->
<table border="solder 1px blue" align="center" width="50%">
<tr>
<th>ID</th>
<th>姓名</th>
<th>账户金额</th>
</tr>
<tr th:each="account : ${lists}">
<td th:text="${account.id}"></td>
<td th:text="${account.name}"></td>
<td th:text="${account.money}"></td>
</tr>
</table>
迭代遍历过程中的状态变量
<tr th:each="account,var : ${lists}">
<td th:text="${account.id}"></td>
<td th:text="${account.name}"></td>
<td th:text="${account.money}"></td>
<td th:text="${var.index}"></td>
<td th:text="${var.count}"></td>
<td th:text="${var.size}"></td>
<td th:text="${var.even}"></td>
<td th:text="${var.odd}"></td>
<td th:text="${var.first}"></td>
<td th:text="${var.last}"></td>
</tr>
<!--状态变量属性
1,index:当前迭代器的索引 从 0 开始
2,count:当前迭代对象的计数 从 1 开始
3,size:被迭代对象的长度
4,even/odd:布尔值,当前循环是否是偶数/奇数 从 0 开始
5,first:布尔值,当前循环的是否是第一条,如果是返回 true 否则返回 false
6,last:布尔值,当前循环的是否是最后一条,如果是则返回 true 否则返回 false-->
迭代遍历 Map
<!--model.addAttribute("map", map);-->
<table border="solder 1px blue" align="center" width="50%">
<tr>
<th>key</th>
<th>ID</th>
<th>姓名</th>
<th>账户金额</th>
</tr>
<tr th:each="map : ${map}">
<td th:text="${map.key}"></td>
<td th:text="${map.value.id}"></td>
<td th:text="${map.value.name}"></td>
<td th:text="${map.value.money}"></td>
</tr>
</table>
域对象操作
@RequestMapping("scope")
public String testMap(HttpServletRequest request, Model model){
request.setAttribute("req","request的数据");
request.getSession().setAttribute("ses","sesson的数据");
request.getSession().getServletContext().setAttribute("appli","applicatiod的数据");
return "list";
}
<!--固定用法,注意大小写敏感o-->
request:<span th:text="${#httpServletRequest.getAttribute('req')}"></span><br>
session:<span th:text="${session.ses}"></span><br>
application:<span th:text="${application.appli}"></span><br>
URL 表达式
<a th:href="@{http://www.baidu.com}">绝对路径</a><br/>
<a th:href="@{/show}">相对路径</a>
<a th:href="@{`/project2/resourcename}">相对于服务器的根</a>
<a th:href="@{/show(id=1,name=zhagnsan)}">相对路径-传参</a>
<a th:href="@{/path/{id}/showRestFul(id=1,name=zhagnsan,age=18)}"> 相 对 路 径 - 传 参 -restful</a>
@RequestMapping("show")
public String testShow(Integer id, String name){
System.out.println(id+"--"+name);
return "list";
}
@RequestMapping("path/{id}/showRestFul")//id会绑定在地址栏,name和age作为参数xxx?name=xx&aeg=xx
public String testRestFul(@PathVariable Integer id, String name, int age){
System.out.println(id+"--"+name);
System.out.println(age);
return "list";
}
springboot 整合 mybatis
导入相关 包
spring-boot-starter-web
坐标里面包含了spring 和 springmvc的 jar 包,但是不包含 mybatis 的 jar,springboot官方没有给mybatis加启动器 ,https://github.com/mybatis/spring-boot-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
</dependency>
application.yaml 文件
server:
port: 8088
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.62.134:3306/ssm?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: root
type: org.springframework.jdbc.datasource.DriverManagerDataSource
mybatis:
type-aliases-package: com.lhg.domain
configuration:
map-underscore-to-camel-case: true
index 首页 及其它页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
<a th:href="@{/account/findAll}">查询所有账户</a><br>
<a th:href="@{/account/saveAccount}">新增账户</a><br>
<a th:href="@{/account/transfer}">转账事务</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>list</title>
</head>
<body>
<!--model.addAttribute("lists", lists);-->
<table border="solder 1px blue" align="center" width="50%">
<tr>
<th>ID</th>
<th>姓名</th>
<th>账户金额</th>
<th>操作</th>
</tr>
<tr th:each="account : ${lists}" align="center">
<td th:text="${account.id}"></td>
<td th:text="${account.name}"></td>
<td th:text="${account.money}"></td>
<td >
<a th:href="@{/account/findAccountById(id=${account.id})}">更新账户</a>
<a th:href="@{/account/delAccount(id=${account.id})}">删除账户</a>
</td>
</tr>
</table>
</body>
</html>
th:field:thymeleaf 语法,能做数据回显,th:value 只能把值注入value 属性当中
<body>
<div>更新账户</div>
<form th:action="@{/account/update}" method="post">
<input type="hidden" name="id" th:field="${account.id}" >
账户名字:<input type="text" name="name" th:field="${account.name}"><br>
账户金额:<input type="text" name="money" th:field="${account.money}"><br>
<input type="submit" value="提交">
</form>
</body>
<body>
<div>添加新账户账户</div>
<form th:action="@{/account/addAccount}" method="post">
账户:<input type="text" name="name"><br>
金额:<input type="text" name="money"><br>
<input type="submit" value="提交">
</form>
</body>
表现层及业务层代码
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
@RequestMapping("/{page}")
public String showPage(@PathVariable String page){
System.out.println("page:"+page);
return page;
}
@RequestMapping("findAll")
public String findAll(Model model){
List<Account> lists = accountService.findAll();
model.addAttribute("lists",lists);
return "showAccount";
}
@RequestMapping("transfer")
public String transfer(){
try {
accountService.transfer("a","b",500);
} catch (Exception e){
e.printStackTrace();
return "failed";
}
return "success";
}
@RequestMapping("findAccountById")
public String findAccountById(Integer id,Model model){
try {
Account account = accountService.findById(id);
model.addAttribute("account",account);
} catch (Exception e){
e.printStackTrace();
return "failed";
}
return "findAccount";
}
@RequestMapping("update")
public @ResponseBody Map<String,String> update(Account account){
Map<String,String> map = new HashMap<>();
int i = accountService.updateAccount(account);
if (i>0){
map.put("msg","OK");
return map;
}else {
map.put("msg","failed");
return map;
}
}
@RequestMapping("delAccount")
public String delAccount(Integer id){
accountService.delAccount(id);
return "redirect:/account/findAll";
}
@RequestMapping("addAccount")
public String addAccount(Account account){
try {
System.out.println(account);
accountService.saveAccount(account);
} catch (Exception e){
e.printStackTrace();
return "failed";
}
return "success";
}
}
@Service
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountMapper accountMapper;
@Override
public Account findById(Integer id) {
return accountMapper.findById(id);
}
@Override
public List<Account> findAll() {
return accountMapper.findAll().isEmpty()?null: accountMapper.findAll();
}
@Override
public int updateAccount(Account account) {
return accountMapper.updateAccount(account);
}
@Override
public void transfer(String sourceName, String targetName, int money) {
System.out.println("start transfer...");
//根据名称查询转入转出账户
Account source = accountMapper.findByName(sourceName);
Account target = accountMapper.findByName(targetName);
//转出账户减钱 转入账户加钱
source.setMoney(source.getMoney()-money);
target.setMoney(target.getMoney()+money);
//更新转入转出账户
accountMapper.updateAccount(source);
int i = 1/0;
accountMapper.updateAccount(target);
}
@Override
public void saveAccount(Account account) {
accountMapper.saveAccount(account);
}
@Override
public void delAccount(Integer id) {
accountMapper.delAccount(id);
}
}
@SpringBootApplication
@MapperScan(basePackages = {"com.lhg.smb.mapper"})//在这里写了MapperScan就不用在每个pojo类上面写@Mapper注解了
public class SpringbootMybatisThymeleafApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisThymeleafApplication.class, args);
}
}
注意:有@configuration注解标识的是一个配置文件,springboot会主动扫描到,如果项目中有这种文件,要看看是否对项目运行有影响,如果无用的文件就删掉
springboot 服务器端表单数据校验
前端代码
<body>
<div>添加新账户账户</div>
<form th:action="@{/account/addAccount}" method="post">
账户:<input type="text" name="name"><span style="color: red;" th:errors="${account.name}"></span><br>
金额:<input type="text" name="money"><span style="color: red" th:errors="${account.money}"></span><br>
<input type="submit" value="提交">
</form>
</body>
服务器端代码
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired
private AccountService accountService;
/**
* @param account 当通过restful直接进入添加页面,${account.xxx}便取不到会报错,所以这里加个参数
*/
@RequestMapping("/{page}")
public String showPage(@PathVariable String page, Account account) {
return page;
}
@RequestMapping("addAccount")
public String addAccount(@Valid Account account, BindingResult bindingResult) {
if (bindingResult.hasErrors()){
return "saveAccount";
}
accountService.saveAccount(account);
return "success";
}
}
public class Account implements Serializable {
private int id;
@NotBlank(message="money不能为空")//message属性可以自定以错误消息
private Integer money;
@NotBlank
private String name;
}
如果不想用驼峰式命名,可以用 @ModelAttribute 注解自定义
public String showPage(@PathVariable String page, @ModelAttribute("aa") Account account) {...}
public String addAccount(@ModelAttribute("aa") @Valid Account account, BindingResult bindingResult) {...}
springboot 异常处理五种方式
自定义错误页面
SpringBoot 默认的处理异常的机制: SpringBoot 默认的已经提供了一套处理异常的机制。 一旦程序中出现了异常 SpringBoot 会像/error 的 url 发送请求。 springBoot 中提供了一个 叫 BasicExceptionController 来处理/error 请求,然后跳转到默认显示异常的页面来展示异常 信息。
如 果 我 们 需 要 将 所 有 的 异 常 同 一 跳 转 到 自 定 义 的 错 误 页 面 , 需 要 再 templates 目录下创建 error.html 页面。注意:名称必须叫 error ,
<body>
这是我自定义的总的错误页面。。,exception 是内置的属性
<span th:text="${exception}"></span>
</body>
这样一来所有的异常都会跳转到这个error.html 页面,不太好…
@ExceptionHandle 注解处理异常
@RequestMapping("except")
public String exception(){
int i = 1/0;
return "index";
}
//与空指针有关的异常进这里,如果还有除这两个外的其它异常则进 error.html
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView nullPointerException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("error",e.toString());
mv.setViewName("exception");
return mv;
}
//与算数有关的异常进这里,如果还有除这两个外的其它异常则进 error.html
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView arithmeticException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("error",e.toString());
mv.setViewName("exception");
return mv;
}
<body>
这是我自定义的总的错误页面。。,我操
<span th:text="${error}"></span>
</body>
这种方式的问题就是如果代码中有很多种类异常,岂不是要写很多这种,而且这种方式只在当前 controller有效,其它 controller无效,所以…看下面的几种吧
@ControllerAdvice+@ExceptionHandler 注解处理异常
需要创建一个能够处理异常的全局异常类。在该类上需要添加@ControllerAdvice 注解,使之能够在多个controller 中共同使用,缺点就是还是要写很多方法,如果有多种异常的话
@ControllerAdvice
public class GlobalException {
//与空指针有关的异常进这里,如果还有除这两个外的其它异常则进 error.html
@ExceptionHandler(value = {java.lang.NullPointerException.class})
public ModelAndView nullPointerException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("error",e.toString());
mv.setViewName("exception");
return mv;
}
//与算数有关的异常进这里,如果还有除这两个外的其它异常则进 error.html
@ExceptionHandler(value = {java.lang.ArithmeticException.class})
public ModelAndView arithmeticException(Exception e) {
ModelAndView mv = new ModelAndView();
mv.addObject("error",e.toString());
mv.setViewName("exception");
return mv;
}
}
配置 SimpleMappingExceptionResolver 处理异常
通过 SimpleMappingExceptionResolver 做全局异常处理,不过这种方式与前面第三种不同的是不会传递具体的异常信息(你在 html 页面不能通过类似 ${exception}取得具体的异常)
@Configuration
public class GlobalException {
/*
该方法必须要有返回值。返回值类型必须是: SimpleMappingExceptionResolver
*/
@Bean
public SimpleMappingExceptionResolver getMappingExceptionHandler(){
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
/** *
* 参数一:异常的类型,注意必须是异常类型的全名
** 参数二:视图名称
* */
properties.put("java.lang.NullPointerException","exception1");
properties.put("java.lang.ArithmeticException","exception2");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}
}
自定义 HandlerExceptionResolver 类处理异常
这种相较于之前的几种,除了可以跳转到不同的视图还可以传递异常信息
@Configuration
public class GlobalException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView mv = new ModelAndView();
//根据异常的类型不同跳转到不同的视图去
if (e instanceof ArithmeticException){
mv.setViewName("exception1");
}
if (e instanceof NullPointerException){
mv.setViewName("exception2");
}
//传递异常信息
mv.addObject("error",e.toString());
return mv;
}
}
<body>
这是我自定义的总的错误页面。。,我操,还全局的YYY
<span th:text="${error}"></span>
</body>
springboot 单元测试
/** SpringBoot 测试类
* @RunWith:启动器
* SpringJUnit4ClassRunner.class:让 junit 与 spring 环境进行整合
* @SpringBootTest(classes={App.class}) 1,当前类为 springBoot 的测试类
* @SpringBootTest(classes={App.class}) 2,加载 SpringBoot 启动类。启动 springBoot
* junit 与 spring 整合 @Contextconfiguartion("classpath:applicationContext.xml")
*/
@RunWith(SpringRunner.class)//SpringJUnit4ClassRunner.class也可以
@SpringBootTest(classes = {App.class})
public class SpringbootMybatisThymeleafApplicationTests {
@Autowired
private AccountService accountService;
@Test
public void test() {
List<Account> lists = accountService.findAll();
lists.forEach(account -> System.out.println(account));
}
}
springboot 热部署
使用 springloader 插件
这种对页面无能为力,还比较麻烦,总之不使用
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.5.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
DevTools 工具
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
对于 IDEA 开发还需作如下设置
ctrl+alt+shift+/---->registry
属性注入方式
spring 属性 java 注入方式
首先导入 jdbc 启动器、mysql驱动包, 下面是 db.properties 文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.62.134:3306/ssm?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root
@Configuration//标识这是一个配置文件
@PropertySource("classpath:db.properties")//加载外部属性文件
public class JdbcConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.url}")
private String url;
@Bean(value = "dataSource")
public DataSource createDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(url);
return dataSource;
}
}
@RestController
public class HelloController {
@Autowired
private DataSource dataSource;//debug发现dataSource属性已经加载
@GetMapping("/hello")
@ResponseBody
public String hello(){
return "hello!";
}
}
springboot 属性 java 注入方式 一
首先 db.properties 重命名 application.properties,springboot默认会去读取application.properties,并导入 lombok 包
@ConfigurationProperties(prefix = "jdbc") //加上前缀
@Data //set/get方法
public class JdbcProperties {
private String driverClassName;
private String username;
private String password;
private String url;
}
//标识这是一个配置文件
@Configuration
//EnableConfigurationProperties:启用配置属性
//或者可以在 JdbcProperties 类上使用 @Component 注解,效果一样
@EnableConfigurationProperties(JdbcProperties.class)
public class JdbcConfig {
@Bean(value = "dataSource")
public DataSource createDataSource(JdbcProperties prop){//你也用可以 @Autowire 在属性上
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(prop.getDriverClassName());
dataSource.setUsername(prop.getUsername());
dataSource.setPassword(prop.getPassword());
dataSource.setUrl(prop.getUrl());
return dataSource;
}
}
springboot 属性 java 注入方式二
只保留 application.properties 文件
//标识这是一个配置文件
@Configuration
public class JdbcConfig {
//当springboot扫描到Bean注解时,发现还有个 ConfigurationProperties 注解,
//它就会检查这个实例对象的 setXXX方法,然后根据前缀从配置文件中读取并设值
@Bean(value = "dataSource")
@ConfigurationProperties(prefix = "jdbc") //加上前缀
public DataSource createDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
return dataSource;
}
}
yaml 配置文件
properties 形式的配置比较冗余,现在主流使用 yaml 配置文件,注意 : 号后面必须要有空格
jdbc:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.62.134:3306/ssm?characterEncoding=utf-8
username: root
password: root
SpringBoot 整合 SpringData JPA
导入 jar 包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
application.properties 配置
# db config
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.62.134:3306/ssm?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
# jpa config
spring.jpa.database=mysql
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
SpringData JPA 提供的几种接口
Repository
方法名称命名查询方式
public interface AccountRepository2 extends Repository<Account,Integer> {
//方法的名称必须要遵循驼峰式规则,方法名构成:findBy+属性名字(首字母大写)+查询条件
List<Account> findByName(String name);//省略了Equals/Is
List<Account> findByIdAndName(Integer id, String name);
//根据用户姓名做 Like 处理
List<Account> findByNameLike(String name);
//:查询名称为王五,并且他的年龄大于等于 22 岁
List<Account> findByNameAndAgeGreaterThanEqual(String name, Integer age);
}
query 注解查询方式
public interface AccountRepository3 extends Repository<Account,Integer> {
//方法的名称必须要遵循驼峰式规则,方法名构成:findBy+属性名字(首字母大写)+查询条件,注意Account 大写
@Query(value=" from Account where name=:name")
List<Account> queryByNameUseHQL(String name);
//nativeQuery 值为 true 本地化 sql 查询,account 要小写
@Query(value = "select * from account where id=?1 and name = ?2", nativeQuery = true)
List<Account> queryByNameUseSQL(Integer id, String name);
@Query(value = "update Account set name=?1 where id=?2")
@Modifying //需要执行一个更新操作
int updateAccountById(String name, Integer id);
//JPQL:通过 Hibernate 的 HQL 演变过来的。他和 HQL 语法及其相似
@Query(" from Account where username like ?")
List<Account> queryUserByLikeNameUse JPQL(String name);
@Query("from Account where Name = ? and age >= ?")
List<Account> queryUserByNameAndAge(String name,Integer age);
}
注意使用 update 更新操作时,由于是和 @test 一起使用,会默认回滚,所以得像下面这么写
@Test
@Transactional
@Rollback(false) //取消自动回滚
public void test2(){
int i = accountRepository3.updateAccountById("林红川",11);
System.out.println(i);
}
CrudRepository
继承自 Repository 接口,主要是完成一些增删改查的操作
public interface AccountRepository4 extends CrudRepository<Account, Integer> {
}
@Test
public void test2(){
Account account = new Account();
account.setId(14);
account.setName("李克强");
account.setMoney(666);
//增加和更新都是save方法
Account res = accountRepository4.save(account);
Iterable<Account> all = accountRepository4.findAll();
accountRepository4.delete(14);//根据 id 删除
System.out.println(res);
}
PagingAndSortingRepository
继承自 CrudRepository 接口,提供了分页和排序的操作,该接口只提供两个方法 findAll(Sort sort) 和 findAll(Pageable pageable)
public interface AccountRepository5 extends PagingAndSortingRepository<Account, Integer> {
}
@Service
public class AccountServiceImpl {
@Autowired
private AccountRepository5 accountRepository5;
public Page pageAccounts(int pageNum, int pageSize){
Pageable pageable = PageRequest.of(pageNum, pageSize);//new PageRequest已过时
Page<Account> accountPage = accountRepository5.findAll(pageable);
return accountPage;
}
}
当然你也可以查询过程进行排序, SpringBoot2.0 后 api 有多处修改
@Service
public class AccountServiceImpl {
@Autowired
private AccountRepository5 accountRepository5;
public Page pageAccounts(int pageNum, int pageSize){
//Sort.Order order = new Sort.Order(Sort.Direction.DESC, "id");//单个属性降序
//Sort sort = new Sort(Sort.Direction.DESC, "id","money"); //多个属性降序排序
Sort.Order order1 = new Sort.Order(Sort.Direction.DESC,"id");//id降序
Sort.Order order2 = new Sort.Order(Sort.Direction.ASC,"money");//money升序
Sort sort = Sort.by(order1,order2);
Pageable pageable = PageRequest.of(pageNum, pageSize, sort);
Page<Account> accountPage = accountRepository5.findAll(pageable);
return accountPage;
}
}
@Test
public void test2(){
int pageNum = 4;//从0 开始
int pageSize = 3;
Page page = accountServiceImpl.pageAccounts(pageNum-1, pageSize);//如果不减一,查询的是第五页,没有数据
System.out.println("当前页数:"+(page.getNumber()+1));
System.out.println("页大小:"+page.getSize());
System.out.println("总记录数:"+page.getTotalElements());
System.out.println("总页数:"+page.getTotalPages());
System.out.println("查询出的记录数:"+page.getNumberOfElements());
System.out.println("查询数据结果集:");
List pageContent = page.getContent();
pageContent.forEach(account-> System.out.println(account));
}
JpaRepository
在实际开发中尽量用这个接口,因为是它有其它接口的所有方法,直接使用即可
@Service
public class AccountServiceImpl {
@Autowired
private AccountRepository5 accountRepository5;
public void deleteBatch(Iterable<Integer> ids){
List<Account> accountList = accountRepository5.findAllById(ids);
accountRepository5.deleteInBatch(accountList);
}
}
@Test
public void test3(){
List<Integer> ids = new ArrayList<>();
ids.add(16);
ids.add(17);
accountServiceImpl.deleteBatch(ids);
}
JPASpecificationExecutor
该接口主要提供了多条件查询的支持,并且可以在查询中添加分页与排序。注意:JpaSpecificationExecutor是单独存在,完全独立。
public interface AccountRepository5 extends JpaRepository<Account, Integer>, JpaSpecificationExecutor<Account> {
}
比如查询条件是 and 的多条件写法
@Test
public void test4() {
/**
* root:查询对象的属性的封装
*criteriaQuery:封装了我们要执行的查询中的各个部分信息 select from order
* criteriaBuilder:查询条件的构造器,定义不同的查询条件
*/
Specification<Account> spec = new Specification<Account>() {
@Override
public Predicate toPredicate(Root<Account> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> list = new ArrayList<>();
list.add(criteriaBuilder.equal(root.get("id"), 1));
list.add(criteriaBuilder.equal(root.get("name"), "a"));
Predicate[] predicates = new Predicate[list.size()];
return criteriaBuilder.and(list.toArray(predicates));
}
};
List<Account> accountList = accountRepository5.findAll(spec);
accountList.forEach(account -> System.out.println(account));
}
}
对于上面这段代码可以有如下简写
@Test
public void test4() {
Specification<Account> spec = (Specification<Account>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.and(criteriaBuilder.equal(root.get("id"), 1),criteriaBuilder.equal(root.get("name"), "a"));
List<Account> accountList = accountRepository5.findAll(spec);
accountList.forEach(account -> System.out.println(account));
}
如果查询条件变成了 or 则是下面
@Test
public void test4() {
Specification<Account> spec = (Specification<Account>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.or(criteriaBuilder.equal(root.get("id"), 1),criteriaBuilder.equal(root.get("id"), 11));
List<Account> accountList = accountRepository5.findAll(spec);
accountList.forEach(account -> System.out.println(account));
}
如果查询条件有 or 又有 and 呢?
@Test
public void test4() {
//(name="a" and id=1) or id=13
Specification<Account> spec = (Specification<Account>) (root, cq, cb) ->
cb.or( cb.and(cb.equal(root.get("name"),"a"),cb.equal(root.get("id"),1)),
cb.equal(root.get("id"),13));
List<Account> accountList = accountRepository5.findAll(spec);
accountList.forEach(account -> System.out.println(account));
}
SpringBoot 缓存技术
redis 介绍
Redis 是目前业界使用最广泛的内存数据存储,相比 memcached,Redis 支持更丰富的数据结构,例如hashes,lists,sets 等,同时支持数据持久化,除此之外,Redis 还提供一些类数据库的特性,比如事务、HA、主从库。可以说 Redis 兼具了缓存系统和数据库的一些特性,因此有着丰富的场景。
整合 redis
pom 所需 jar 包
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.7</version>
</dependency>
</dependencies>
配置文件
application.properties
# Redis 数据库索引(默认为0)
spring.redis.database=2
# Redis 服务器地址
spring.redis.host=192.168.62.134
# Redis 服务端连接端口
spring.redis.port=6379
# Redis 连接密码(默认为空)
spring.redis.password=123456
# 连接超时时间(毫秒)
spring.redis.timeout=3000
spring.redis.jedis.pool.max-active=20
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=5
自定义序列化器代替 jdk 自带的
@Configuration
public class MySerializConfig {
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> creaeteRedisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> redisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
redisSerializer.setObjectMapper(objectMapper);
//设置String<key,value>的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(redisSerializer);
//设置hash<key,<k,v>>的序列化规则
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(redisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
测试
@RunWith(SpringRunner.class)
@SpringBootTest(classes = SpringbootRedisApplication.class)
public class SpringbootRedisApplicationTests {
//操作字符串
@Autowired
private StringRedisTemplate stringRedisTemplate;
//带泛型,操作对象
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue() {
//存数据
stringRedisTemplate.opsForValue().set("age","18");
List<String> list=new ArrayList<String>();
list.add("a1");
list.add("a2");
list.add("a3");
stringRedisTemplate.opsForList().leftPushAll("lists", list);
}
@Test
public void getValue() throws InterruptedException {
//取数据
String lists = stringRedisTemplate.opsForList().leftPop("lists",1, TimeUnit.SECONDS);
System.out.println(lists);
}
//存对象并用json格式
@Test
public void test3(){
User users = new User();
users.setAge(23);
users.setId(2);
users.setName("李四sf");
this.redisTemplate.opsForValue().set("user2", users);
}
}
SpringBoot 使用 spring cache 缓存注解
Spring cache 缓存介绍
Spring3.1 引入了激动人心的基于注解的缓存技术,它本质上不是一个具体的缓存实现方案,而是一个对缓存使用 抽象,通过在既有代码中加入少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。
Spring的环迅技术还具备相当的灵活性,不仅能够使用 SpEL(Spring Expression Language)来定义缓存的 key 和各种 condition,还提供开箱即用的缓存暂时存储方案,也支持和主流专业缓存
注解介绍
添加 Spring Cache 步骤
开启注解缓存
@Configuration
@EnableCaching//开启缓存注解
public class MySerializConfig {
@Bean(name = "cacheManager")
public CacheManager createCacheManage(RedisConnectionFactory redisConnectionFactory){
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置 CacheManage的序列化方式为 json 序列化
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, cacheConfiguration);
return cacheManager;
}
}
Pojo 类
@Data
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
}
Controller 代码
@RestController
//如果写了此注解,下面的 Cacheable、CachePut、CacheEvict..括号中不用再写 value,这里统一管理
//@CacheConfig(cacheNames = "user")
public class RedisController {
@Cacheable(value = "user")//从缓存数据库取数据
@GetMapping("user/{id}")
public User getUser(@PathVariable Integer id){
User user = new User();
user.setId(id);
user.setAge(new Random().nextInt(20));
user.setName("get user:"+id);
System.out.println("模拟查询数据库:"+id);
return user;
}
@CachePut(value = "user")//缓存数据库中的数据也会修改
@GetMapping("user/update/{id}")
public User updateUser(@PathVariable Integer id){
User user = new User();
user.setId(id);
user.setAge(new Random().nextInt(20));
user.setName("update user:"+id);
System.out.println("模拟更新数据库:"+id);
return user;
}
@CacheEvict(value = "user", key ="#id")//根据 Id 删,默认全删
@GetMapping("user/delete/{id}")
public void deleteUser(@PathVariable Integer id){
System.out.println("模拟删除数据库:"+id);
}
}