概念理解
什么是SpringBoot呢,就是一个javaweb的开发框架,和SpringMVC类似,
对比其他javaweb框架的好处,官方说是简化开发,约定大于配置,
Spring Boot的主要优点:
- 为所有Spring开发者更快的入门
- 开箱即用,提供各种默认配置来简化项目配置
- 内嵌式容器简化Web项目
- 没有冗余代码生成和XML配置的要求
Spring开发变的越来越笨重,大量的XML(SpringMVC)文件,繁琐的配置,复杂的部署流程,
整合第三方技术时 难度大等,导致开发效率低下
SpringBoot是一个用来简化Spring应用的初始化创建和开发的框架,简化配置,实现快速开发
新建boot项目
选择spring initalizr(如果没有该选项,settings—Plugins 搜索 Spring Assistant)
不需要的mvn相关文件删掉
勾选依赖
Spring Boot DevTools:?
Spring Web:启动器
Spring Configuration Processor : 针对提示“Spring Boot Configration AnnotationProcessor not configured”
Maven配置
<!-- 配置本地仓库路径 配置aliyun镜像来源 -->
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>C:\Environment\apache-maven-3.6.3\repo363</localRepository>
<interactiveMode>true</interactiveMode>
<offline>false</offline>
<pluginGroups></pluginGroups>
<proxies></proxies>
<servers></servers>
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles></profiles>
</settings>
<!-- Idea中配置 -->
setting 搜索maven
改为本地maven路径、改为本地settings.xml文件、改为本地仓库路径
???启动类需要增加一个依赖
父项目是spring-boot-starter-parent,需要继承很多东西
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
父项目的父项目是spring-boot-dependencies,用来管理SpringBoot应用中底层依赖的版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
启动器
<!-- web场景启动器 包括:tomcat等等 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
测试类
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
构建工具也加上,用来打包
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
设置banner和port
https://www.bootschool.net/ascii
拷贝生成的字符到一个文本文件中,并且将该文件命名为banner.txt
将banner.txt拷贝到项目的resources目录中:
重新查看效果。
Spring Boot项目使用一个全局的配置文件application.properties或者是application.yml
在文件中添加:
server.port=8081
或者yml的
server:
port: 8081
???运行原理探究
https://www.cnblogs.com/hellokuangshen/p/12450327.html
初步结论:
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
将这些值作为自动配置类导入容器,自动配置类就生效,帮我们进行自动配置工作;
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
它会给容器中导入非常多的自动配置类(xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;
???自动配置原理
https://edu.51cto.com/center/course/lesson/index?id=290344
https://www.bilibili.com/video/BV1PE411i7CV?p=6
https://www.bilibili.com/video/av75233634?p=7
yml
语法
空格要求极高,不过不使用yml,properties也可以
# 对空格要求极高
server:
port: 8081
servlet:
context-path: /test # 表示项目所有访问路径增加前缀/test
address: 陕西省西安市
# 在java中就可以使用@Value("${address}")就可以取到值,类型会自动转换
content: "地址是${address}"
# 也可以这样引用其他变量,
# 对象
student:
name: Alice
age: 24
student2: {
name: bob, age: 24} # 行内写法
pets:
- cat
- dog
- pig
pets2: [cat, dog, pig]
还可以注入对象的值
person:
name: mata
age: 24
happy: true
birth: 2009/09/09
maps: {
k1: v1,k2: v2}
lists:
- code
- music
- girl
dog:
name: wangcai
age: 14
创建Dog和Person实体类
@Data
@Component
public class Dog {
private String name;
private int age;
}
@Data
@Component
@ConfigurationProperties(prefix="person")
//默认从yml全局配置文件中获取对应的yml值
//默认的全局文件文件名必须是application.yml或者application.properties
public class Person {
private String name;
private Integer age;
private Boolean happy;
private Date birth;
private Map<String,Object> maps;
private List<String> lists;
private Dog dog;
}
然后可以测试类进行测试
@SpringBootTest
class HelloworldApplicationTests {
@Autowired
Person p;
@Test
void contextLoads() {
System.out.println(p);
}
}
加载指定的配置文件
注释掉该注解 @configurationProperties
替换为 @PropertySource(value = “classpath:person.properties”)
在resources目录下新建一个person.properties文件,内容如下
name=kuangshen
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
}
文件占位符
person:
name: qinjiang${
random.uuid} # 随机uuid
age: ${
random.int}
happy: false
birth: 2020/03/15
maps: {
k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
# 引用yml中person.hello的值,如果不存在就用:后面的值,即Bob,然后拼接上_旺财
name: ${
person.hello:Bob}_旺财 # 若中文出现乱码,将idea中File Encodings改为UTF-8
age: 1
@ConfigurationProperties与@Value区别
1、@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加
2、松散绑定:yml中写的last-name,这个和lastName是一样的, -后面跟着的字母默认被转为大写。
3、JSR303数据校验,这个就是我们可以在字段是增加一层过滤器验证,可以保证数据的合法性,jsr303有很多,网上可以查到。
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //自定义报错信息为:name必须是邮箱格式
private String name;
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空 格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 @Pattern 验证 String 对象是否符合正则表达式的规则
}
4、复杂类型封装,yml中可以封装对象,使用value就不支持
结论:
1,配置yml和配置properties都可以获取到值 , 强烈推荐 yml;
2,如果我们在某个业务中,只需要获取配置文件中的某个值,可以使用一下 @value;
3,如果说,我们专门编写了一个JavaBean来和配置文件进行一一映射,就直接@configurationProperties,不要犹豫!
HelloWorld
这三个包必须和启动类同级,这样就可以扫描到程序中
@RestController //@Controller
public class HelloController {
@RequestMapping({
"/hello","/hello2"}) //可接受数组形式的多个值
public String hello() {
return "Hello World";
}
//http://localhost:port/hello //http://localhost:port/hello2
}
配置文件优先级
1、spring boot项目中同时存在application.properties和application.yml文件时,两个文件都有效,
但是application.properties的优先级会比application.yml高。
2、配置文件所在目录不同优先级也不同。
最高级别:处于外层config目录下
第二级别:与最外层同级别
第三级别:resource - config 目录下
第四级别:resource目录下
热部署
<!-- 添加spring-boot-devtools的包,true必须加上 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
方式一:Build对象项目(或者build对应的文件也可以)
方式二: setting - Build,Execution - Compiler - 打勾 Compiler-Build Project automatically
Ctrl+Shift+Alt 搜索Registry 打勾 Compiler.autoMake.allow.when.app.running
两种方式都是重新编译文件,只不过一个手动一个自动
多环境配置
开发环境、生产环境、测试环境等随时切换,所以准备多个配置文件
方式一:profiles
在application.properties文件中指定要激活的配置:spring.profiles.active=dev
然后新建各环境对应的文件:application-dev.properties application-test.yml
方式二:文档块 application.properties举例:
# 第一个文档块,指定文档块id为testing,此时文档块234中3就激活,port就成了8082
server:
port: 8080
spring:
profiles:
active: test
# 第2个文档块
---
spring:
profiles: develop
server:
port: 8081
# 第3个文档块
---
spring:
profiles: test
server:
port: 8082
# 第4个文档块
---
spring:
profiles: product
server:
port: 8083
加载外部配置文件
方式1,使用@PropertySource加载properties属性文件
//entity
@Component // 将当前Bean添加到容器中
@ConfigurationProperties(prefix = "user") //在配置文件中匹配user属性,与@PropertySource没有前后顺序
@PropertySource(value={
"classpath:user.properties"}) // 加载外部的属性文件,value=可以省略
public class User {
String name;
int age;
String address;
getter setter toString ...
}
//user.propeties
user.name=Faker
user.age=21
user.address=false
//test
@SpringBootTest
class DemoApplicationTests {
@Autowired
User u;
@Test
void contextLoads() {
System.out.println(u);
}
}
方式2:@ImportResource
将方式1中User的注解替换为@ImportResource({
"classpath:xxx.xml"})
//entity
@Component // 将当前Bean添加到容器中
@ImportResource({
"classpath:spring.xml"})
public class User {
String name;
int age;
String address;
getter setter toString ...
}
//新建xxx.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="u" class="com.baidu.studyboot.entity.User">
<property name="name" value="江苏省"/>
<property name="age" value="23"/>
<property name="address" value="大寨路78号"/>
</bean>
</beans>
???方式3:使用注解方式添加组件
@Configuration // 标注在类上,表示这是一个配置文件,相当于以前的spring xml配置文件
public class MyConfig {
@Bean // 标注在方法上,向容器中添加组件,将方法的返回值添加到到容器中,将方法名作为组件id
public Address address(){
Address address = new Address();
address.setProvince("江苏");
address.setCity("苏州");
return address;
}
}
@Value
实体类中使用@Value给字段赋值,当然实体类需要加上@Component
@Value("${server.port}")
public String port;
server.port=80123
若属性port使用了static会无法赋值,为null
@ConfigurationProperties
@ConfigurationProperties(prefix = "user") //在默认配置文件中匹配user属性,即application.properties
@SpringBootApplication
是一个复合注解,包含了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan这三个注解。
- @SpringBootConfiguration:标注当前类是配置类,这个注解继承自@Configuration。又继承自@Component,
- 并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到srping容器中,并且实例名就是方法名。
- @EnableAutoConfiguration:是自动配置的注解,这个注解会根据我们添加的组件jar来完成一些默认配置,
- 我们做微服时会添加spring-boot-starter-web这个组件jar的pom依赖,这样配置会默认配置springmvc 和tomcat。
- 开启自动配置功能,SpringBoot会自动完成许多配置,简化了以前的繁琐的配置。
- @ComponentScan:默认扫描当前包及其子包下被@Component,@Controller,@Service,@Repository注解标记的类并纳入到
spring容器中进行管理。等价于context:component-scan的xml配置文件中的配置项。也可以(“com.baidu.xxx”)自行指定扫描范围。
大多数情况下,这3个注解会被同时使用,基于最佳实践,这三个注解就被做了包装,成为了@SpringBootApplication注解。
@MapperScan
spring-boot支持mybatis组件的一个注解,通过此注解指定mybatis接口类的路径,即可完成对mybatis接口的扫描。
它和@mapper注解是一样的作用,不同的地方是扫描入口不一样。@mapper需要加在每一个mapper接口类上面。
所以大多数情况下,都是在规划好工程目录之后,通过@MapperScan注解配置路径完成mapper接口的注入。
添加mybatis相应组建依赖之后。就可以使用该注解。
@PathVariable
//@PathVariable可以用来映射URL中的占位符到目标方法的参数中
@RequestMapping("/testPathVariable/{id}")
public String testPathVariable(@PathVariable("id") Integer id){
System.out.println("testPathVariable:"+id);
return SUCCESS;
}
@Controller @RequestMapping
@Controller 表明这个类是一个控制器类,和@RequestMapping来配合使用拦截请求,如果不在method中注明请求的方式,
默认是拦截get和post请求。这样请求会完成后转向一个视图解析器。但是在大多微服务搭建的时候,前后端会做分离。
所以请求后端只关注数据处理,后端返回json数据的话,需要配合@ResponseBody注解来完成。
这样一个只需要返回数据的接口就需要3个注解来完成,大多情况我们都是需要返回数据。也是基于最佳实践,所以将这三个注解进一步整合。
@RestController 是@Controller 和@ResponseBody的结合,一个类被加上@RestController 注解,
数据接口中所有方法就都不再需要添加@ResponseBody。更加简洁。
1) 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,
配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
且每一个方法都不需要加 @ResponseBody
2) 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
同样的情况,@RequestMapping(value="",method= RequestMethod.GET ),我们都需要明确请求方式。
这样的写法又会显得比较繁琐,于是乎就有了如下的几个注解。
普通风格 ---> Rest风格
@RequestMapping(value="",method = RequestMethod.GET) @GetMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.POST) @PostMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.PUT) @PutMapping(value =“”)
@RequestMapping(value="",method = RequestMethod.DELETE) @DeleteMapping(value =“”)
@Service
1,@Service:这个注解用来标记业务层的组件,我们会将业务逻辑处理的类都会加上这个注解交给spring容器。
事务的切面也会配置在这一层。当让 这个注解不是一定要用。有个泛指组件的注解,
当我们不能确定具体作用的时候 可以用泛指组件的注解托付给spring容器。
2、@Resource:@Resource和@Autowired一样都可以用来装配bean,都可以标注字段上,或者方法上。
@resource注解不是spring提供的,是属于J2EE规范的注解。
两个之前的区别就是匹配方式上有点不同,@Resource默认按照名称方式进行bean匹配,@Autowired默认按照类型方式进行bean匹配。
静态资源
################### 资源处理 #####################
SpringMVC的web配置都在 WebMvcAutoConfiguration 这个配置类里面;
我们可以去看看 WebMvcAutoConfigurationAdapter 中有很多配置方法;
有一个方法:addResourceHandlers 添加资源处理
通过观察,可以得知有几个候选路径(优先级从高到低):
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
如果不使用候选路径,可自定义
在application.properties中配置;
spring.resources.static-locations=classpath:/coding/,classpath:/kuang/
#自定义匹配规则
spring.mvc.static-path-pattern=规则
配置好后可通过http://localhost:8080/1.js访问静态资源(图片 js 音频等)
###################### 网页图标icon ########################
2.2.0版本以上已移除
spring.mvc.favicon.enabled=false
再放一个favicon.ico
再加入
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
################## 首页 ########################
底层默认固定文件名为:index.html
################### 访问 templates下页面,只能通过controller ##############
Thymeleaf模板引擎
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
@Controller //注意:不是RestController
public class TestController {
@RequestMapping("/t1")
public String test1(Model model){
model.addAttribute("msg","Hello,Thymeleaf");
model.addAttribute("users", Arrays.asList("qinjiang","kuangshen"));
return "test"; //跳转到test.html,页面必须在templates中
}
}
注意:themeleaf的命名空间 xmlns:th="http://www.thymeleaf.org"
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>狂神说</title>
</head>
<body>
<div th:text="${msg}"></div>
<h4 th:each="user :${users}" th:text="${user}"></h4>
</body>
</html>
spring.thymeleaf.cache=false # 关闭引擎缓存
MVC配置原理
https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483819&idx=1&sn=b9009aaa2a9af9d681a131b3a49d8848&scene=19#wechat_redirect
https://www.bilibili.com/video/BV1PE411i7CV?p=18
https://www.bilibili.com/video/BV1PE411i7CV?p=19
i18n
先在IDEA中统一设置properties的编码utf8
在resources资源文件下新建一个i18n目录,存放国际化配置文件
建立 login.properties login_zh_CN.properties login_en_US.properties
配置这个messages的路径: spring.messages.basename=i18n.login
在Spring中有一个国际化的Locale (区域信息对象);里面有一个叫做LocaleResolver (获取区域信息对象)的解析器!
AcceptHeaderLocaleResolver 这个类中有一个方法
修改一下前端页面的跳转连接:
<!-- 这里传入参数不需要使用 ?使用 (key=value)-->
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
写一个处理的组件类
//可以在链接上携带区域信息
public class MyLocaleResolver implements LocaleResolver {
//解析请求
@Override
public Locale resolveLocale(HttpServletRequest request) {
String language = request.getParameter("l");
Locale locale = Locale.getDefault(); // 如果没有获取到就使用系统默认的
//如果请求链接不为空
if (!StringUtils.isEmpty(language)){
//分割请求参数
String[] split = language.split("_");
//国家,地区
locale = new Locale(split[0],split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {
}
}
为了让我们的区域化信息能够生效,我们需要再配置一下这个组件!在我们自己的 MyMvcConfig 下添加bean;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index.html").setViewName("index");
}
@Bean
public LocaleResolver localeResolver(){
return new MyLocaleResolver();
}
}
整合JDBC
//依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/databaseName?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
//测试是否连接成功
@SpringBootTest
class DemoApplicationTests {
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
// JDBCTemplate
1、有了数据源(com.zaxxer.hikari.HikariDataSource),然后可以拿到数据库连接(java.sql.Connection),
有了连接,就可以使用原生的 JDBC 语句来操作数据库;
2、即使不使用第三方第数据库操作框架,如 MyBatis等,Spring 本身也对原生的JDBC 做了轻量级的封装,即JdbcTemplate。
3、数据库操作的所有 CRUD 方法都在 JdbcTemplate 中。
4、Spring Boot 不仅提供了默认的数据源,同时默认已经配置好了 JdbcTemplate 放在了容器中,程序员只需自己注入即可使用
5、JdbcTemplate 的自动配置是依赖 org.springframework.boot.autoconfigure.jdbc 包下的 JdbcTemplateConfiguration 类
JdbcTemplate主要提供以下几类方法:
execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
query方法及queryForXXX方法:用于执行查询相关语句;
call方法:用于执行存储过程、函数相关语句。
// JDBCTemplate 测试
@RestController
@RequestMapping("/jdbc")
public class JdbcController {
@Autowired
JdbcTemplate jdbcTemplate;
@GetMapping("/list")
public List<Map<String, Object>> userList(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("select * from emp");
return maps;
}
@GetMapping("/add")
public String addUser(){
//插入语句,注意时间问题
String sql = "insert into emp(last_name, email,gender,department,birth)" +
" values ('狂神说','[email protected]',1,101,'"+ new Date().toLocaleString() +"')";
jdbcTemplate.update(sql);
return "addOk";
}
@GetMapping("/update/{id}")
public String updateUser(@PathVariable("id") int id){
String sql = "update emp set last_name=?,email=? where id="+id;
Object[] objects = new Object[2];
objects[0] = "秦疆";
objects[1] = "[email protected]";
jdbcTemplate.update(sql,objects);
return "updateOk";
}
@GetMapping("/delete/{id}")
public String delUser(@PathVariable("id") int id){
String sql = "delete from emp where id=?";
jdbcTemplate.update(sql,id);
return "deleteOk";
}
}
整合Druid
https://mp.weixin.qq.com/s?__biz=Mzg2NTAzMTExNg==&mid=2247483786&idx=1&sn=f5f4ca792611af105140752eb67ce820&scene=19#wechat_redirect
https://www.bilibili.com/video/BV1PE411i7CV?p=32
整合Redis
springboot 2.x 之后spring-boot-starter-data-redis底层的 jedis 被替代为 letture
jedis
Spring-data-redis
Spring-boot-starter-data-Redis
三者关系是后者封装前者,jedis最原生类似jdbc
jedis:采用直连,多线程时不安全,使用jedis pool
lettuce:采用netty,实例在多线程中共享,不存在不安全情况
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
lettuce:
pool:
max-idle: 8 # 连接池中的最大空闲连接 默认8
min-idle: 0 # 连接池中的最小空闲连接 默认0
max-active: 8 # 连接池最大连接数 默认8 ,负数表示没有限制
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认-1
timeout: 30000
@SpringBootTest
class DemoApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("age","24");
// 如果 set 对象,该 entity 必须实现序列化
System.out.println(redisTemplate.opsForValue().get("age"));
}
}
如果有中文,会出现乱码,解决方法是增加自定义序列化配置文件
@Configuration
public class Config {
@Autowired
private RedisTemplate redisTemplate;
@Bean
public RedisTemplate redisTemplateInit() {
StringRedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
return redisTemplate;
}
}
源码可以看出,SpringBoot自动帮我们在容器中生成了一个 RedisTemplate 和 StringRedisTemplate。
但自动配置不好用,就重新配置一个RedisTemplate
public class RedisConfig {
@Bean @SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet(); return template;
}
}
RedisUtil.java
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*/
public boolean redisTemplate.hasKey(key)
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param map 对应多个键值
* @param time 时间(秒)
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param item 项
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param item 项
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param time 时间(秒)
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param time 时间(秒)
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param time 时间(秒)
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
整合Mybatis
//entity
@Data
@NoArgsConstructor
public class User {
private Integer id;
private String name;
private String pwd;
}
//sql
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`pwd` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', 'faker', '123456');
//依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
//兼容问题
关于mybatis-spring-boot-starter的版本需要注意:
2.1.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.1+
2.0.x版本适用于:MyBatis 3.5+、Java 8+、Spring Boot 2.0/2.1
1.3.x版本适用于:MyBatis 3.4+、Java 6+、Spring Boot 1.5
//interface
@Mapper
//表示这是一个mybatis的mapper类
//也可以在另一个文件中使用@MapperScan=("com.xxx.xxx")
@Repository
public interface UserMapper {
List<User> selectAll();
int update(User user);
}
//controller
@RestController
public class UserController {
@Autowired
private UserMapper um;
@RequestMapping("/selectAll")
public List<User> selectAll(){
List<User> list = um.selectAll();
return list;
}
}
//xml在resources文件夹下的mapper下,resources/mapper/UserMapper.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.kuang.mapper.UserMapper">
<select id="selectAll" resultType="user">
select * from user
</select>
</mapper>
//application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:port/databaseName?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
mybatis.type-aliases-package: com.xxx.xxx
mybatis.mapper-locations=classpath:**/*.xml
//application.yml
spring
datasource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:port/databaseName?serverTimezone: UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
mybatis
type-aliases-package: com.xxx.xxx
mapper-locations: classpath:**/*.xml
# 注意
1,driver并不是 com.mysql.jdbc.Driver"
2,url也比较特殊
3,location有个s,当初这里费劲了1个小时
//测试
http://localhost:8080/selectAll
//maven配置资源过滤问题
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>