SpringBoot
Spingboot是Spring提供的一个子项目,用于快速构建Spring应用程序
特性
1.起步依赖(简化pring应用程序的构建)
起步依赖本质上是一个 Maven 项目对象模型(Project Object Model,POM),定义了对其他库的传递依赖,这些东西加在一起即支持某项功能。
springboot提供给的依赖
pom.xml中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
里面包含了完成一个功能的所有依赖
spring-beans
spring-core
spring-aop
spring-web
spring-context
...
2.自动配置(简化pring应用程序的构建)
简化开发,遵循约定大于配置的原则,在boot程序启动后,一些bean对象会自动注入到ioc容器中,不需要手动声明
比如说
以前整合mybatis时,先引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.5.6</version>
</dependency>
然后声明两个bean对象
SqlSessionFactoryBean
MapperScannerConfigurer
而现在,只需要引入mybatis的起步依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
内部自动生成需要的两个bean对象
3.其他特性
内嵌Tomcat(无需打WAR包,打JAR包即可)、Jetty
外部化配置
不需要XML配置
SpringBoot入门
1.创建Maven工程
2.导入spring-boot-starter-web起步依赖
3.编写Controller
@RestController
public class ArticleController {
@RequestMapping("/hello")
public String hello (){
System.out.println("Heloo");
return "Hello";
}
}
4.提供启动类
// 启动类的入口
@SpringBootApplication
public class BigEventApplication {
public static void main(String[] args) {
// 传两个参数 当前文件的字节码文件和args
SpringApplication.run(BigEventApplication.class, args);
}
}
创建好之后的工程
pom文件
<!-- boot工程的父工程,用于管理起步依赖的版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- web起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
创建controller及下面的HelloController
package com.whx.springbootquickstart.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
// 第一步
@RestController
public class HelloController {
// 第二步
@RequestMapping("/hello")
public String hello() {
return "Hello world";
}
}
然后就可以访问了
基础篇
配置文件
application.properties
举例
# 配置默认端口号(8080)
server.port=9090
# 配置默认请求路径(默认无)
server.servlet.context-path=/start
application.yml
举例
server:
port: 9090
servlet:
context-path: /start
yml配置信息的书写和获取
- 使用缩进表示层级关系
- 值前面必须有空格, 作为分隔符
- '#'表示注释
- 单引号与双引号表示字符串内容会被转义/不转义
# 数组这样写
food: # 集合
- 烤羊排
- 烤韭菜
- 烤鱿鱼
student:
name: "易烊千玺" # 字符串
age: 21 # 数值
gender: true # 布尔值
birthday: 2000/1/1 12:30:20 # 日期
hobby: ["唱", "跳", "rap"] # 数组
score: {"Java": 100, "MySQL": 100} # Map
car: # 对象
name: 比亚迪
@Value
低耦合
info:
name:whx
age:20
sex:man
public class Person() {
@Value("${info.name}")
pulbic String name;
@Value("${info.age}")
public String age;
@Value("${info.sex}")
public String sex;
}
@ConfigurationProperties
@Value太麻烦了,让我来
@ConfigurationProperties(prefix = "info") // info必须是yml里的info
public class Person() {
// @Value("${info.name}")
pulbic String name;
// @Value("${info.age}")
public String age;
// @Value("${info.sex}")
public String sex;
}
SpringBoot整合mybatis
pom.xml中
<dependencies>
<!--第二步: mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- 第一步: mybatis的起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
application.properties中
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=20030702
Bean对象管理
Bean扫描
如何进行Bean扫描
1.xml配置文件中
<content:component-scan base-package=“com.whx” />
2.配置类
@ComponentScan(basePackage = “com.whx”)
@SpringBootApplication是组合注解,其中包括:
@SpringBootConfiguration
@EnableAutoConfiguration
@Component
该注解会默认扫描启动类所在的包及其子包
如果想扫描包之外的路径可以手动设计扫描路径
@ComponentScan(basePackage = “com.whx”)
Bean注册
@Bean
创建一个配置类TestConfig:
@Configuration
public class TestConfig {
// 声明通过此方法获取一个对象放入到Spring容器中进行管理
// @Bean(/*指定启动器获取是的名字*/"abc")
@Bean
public User getUser() {
return new User();
}
// @Bean
// public Person person(User getUser) { // 声明参数 即引入容器内的对象
// return new Person();
// }
}
在启动类中调用:
@SpringBootApplication
public class Springboot03registerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Springboot03registerApplication.class, args);
// 第一种获取方式
User user = context.getBean(User.class);
System.out.println(user);
// 第二种获取方式(通过默认方法名获取)
System.out.println(context.getBean("getUser"));
// 更改@Bean参数之后的获取方式
// System.out.println(context.getBean("abc"));
}
}
@Import
如果config包并不在启动类的包下,我们可以用@Import在启动类下导入config中类的字节码文件
@SpringBootApplication
@Import(TestConfig.class)
public class Springboot03registerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Springboot03registerApplication.class, args);
// 第一种获取方式
User user = context.getBean(User.class);
System.out.println(user);
}
}
导入之后TestConfig就会被放到IOC容器中,因为TestConfig是配置类,其中添加@Bean注解的方法的返回值也会被添加到IOC容器中
如果有多个的话
@Import({TestConfig.class, TestConfig.class, TestConfig.class, TestConfig.class, TestConfig.class})
注册条件
注入容器时赋初始值
TestCongfig中
@Configuration
public class TestConfig {
@Bean
public User getUser() {
User user = new User();
user.setId(1);
user.setName("whx");
user.setAge(20);
user.setInfo("666");
return user;
}
}
耦合太高了,换一种
TestCongfig中
@Configuration
public class TestConfig {
@Bean
public User getUser(@Value("${user.id}") Integer id,
@Value("${user.name}")String name,
@Value("${user.age}")Integer age,
@Value("${user.info}")String info) {
User user = new User();
user.setId(id);
user.setName(name);
user.setAge(age);
user.setInfo(info);
return user;
}
}
application.propertities中
user.id=1
user.name=whx
user.age=20
user.info=666
@Conditional
实体类
@Data
public class Son {
}
@Data
public class Wife {
}
@Data
public class GirlFriend {
}
配置类
@Configuration
public class BeanConfig {
// 注意条件类的位置要在被引用条件之前
// @Bean("wife")
public Wife getWife() {
return new Wife();
}
// 当spring容器中包含Wife类的对象时才创建对象
@ConditionalOnBean(Wife.class)
@Bean(name = "son")
public Son getSon() {
return new Son();
}
// 当spring容器中不包含Wife类的对象时才创建对象
@ConditionalOnMissingBean(Wife.class)
@Bean("girlFriend")
public GirlFriend getGirlFriend() {
return new GirlFriend();
}
// 如果当前环境中不存在DispatcherServlet类,则注入
// 如果当前引入了web的起步依赖,则环境中有DispatcherServlet
@ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet")
}
启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
// 获取容器
ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
// 判断是否包含指定组件
boolean hasSon = run.containsBean("son");
// 如果包含指定的组件
if (hasSon) {
// 从容器中获取对象
Son son = run.getBean("son", Son.class);
System.out.println("有儿子:" + son);
} else {
System.out.println("没有儿子");
}
// 判断容器中是否包含指定组件
boolean hasGirlFriend = run.containsBean("girlFriend");
if (hasGirlFr
iend) {
GirlFriend girlFriend = run.getBean(GirlFriend.class);
System.out.println("有女朋友:" + girlFriend);
} else {
System.out.println("没有女朋友");
}
}
}
自动配置原理
遵循约定大于配置的原则,在boot程序启动后,一些bean对象会自动注入到ioc容器中
在主启动类上添加了@SpringBootApplication注解,这个注解是个组合注解,组合了@EnableAutoConfiguration
@EnableAutoConfiguration又组合了@Import,@Import导入了AutoConfigurationImportSelecter类
AutoConfigurationImportSelecter类实现了selectImports方法,这个方法最终会读取到META-INF目录下的后缀名为imports的文件(2.7之后,之前为spring.factories文件)
imports文件中全是全类名,读取全类名时会解析其中的注册条件@Conditional及其衍生注解,然后把满足条件的bean对象自动注入到IOC容器中
自定义starter
公共组件封装为starter
需求:
自定义mybatis的starter
步骤:
1.创建dmybatis-spring-boot-autoconfiguration模块,提供自动配置功能,并自定义配置文件META-INF/spring/xxx.imports
2.创建dmybatis-spring-boot-starter模块,在starter中引入自动配置模块
实战篇
开发流程
明确需求—>阅读接口文档—>思路分析—>开发—>测试
创建Maven工程
创建包结构,准备实体类
导入依赖
<!--继承父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
</parent>
<!-- web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<!-- mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
引入mybatis的配置信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/big_event
username: root
password: 20030702
准备启动类
@SpringBootApplication // 启动类的入口
public class BigEventApplication {
public static void main(String[] args) {
SpringApplication.run(BigEventApplication.class, args);
}
}
用户模块
注册
lombok
User实体类
public class User {
private Integer id;//主键ID
private String username;//用户名
private String password;//密码
private String nickname;//昵称
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
没有getter setter toString
lombok在编译阶段,为实体类自动生成getter setter toString
导入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
添加@Data
@Data
public class User {
private Integer id;//主键ID
private String username;//用户名
private String password;//密码
private String nickname;//昵称
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
Result实体类
//统一响应结果
public class Result<T> {
private Integer code;//业务状态码 0-成功 1-失败
private String message;//提示信息
private T data;//响应数据
//快速返回操作成功响应结果(带响应数据)
public static <E> Result<E> success(E data) {
return new Result<>(0, "操作成功", data);
}
//快速返回操作成功响应结果
public static Result success() {
return new Result(0, "操作成功", null);
}
public static Result error(String message) {
return new Result(1, message, null);
}
}
需要全参构造和无参构造
//统一响应结果
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;//业务状态码 0-成功 1-失败
private String message;//提示信息
private T data;//响应数据
//快速返回操作成功响应结果(带响应数据)
public static <E> Result<E> success(E data) {
return new Result<>(0, "操作成功", data);
}
//快速返回操作成功响应结果
public static Result success() {
return new Result(0, "操作成功", null);
}
public static Result error(String message) {
return new Result(1, message, null);
}
}
加密
Md5加密工具类
Service层
UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void register(String username, String password) {
// 加密
String md5String = Md5Util.getMD5String(password);
// 注册
userMapper.add(username, md5String);
}
}
Mapper层
UserMapper
@Mapper
public interface UserMapper {
// 查询
@Insert("insert into user(username, password, create_time, update_time)" +
" values(#{username}, #{password}, now(), now()) ")
void add(String username, String password);
}
报错
406 No acceptable representation
原因:Result类中没有@Data
注册校验Validation
- 引入Spring Validation 起步依赖
<!--validation依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 在参数前面添加@Pattern注解,在Controller类上添加@Validated
UserController
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
private UserService userService;
@PostMapping("register")
public Result register(@Pattern(regexp = "^\\S{5,16}$") String username, @Pattern(regexp = "^\\S{5,16}$") String password) {
// 查询用户
User u = userService.findByUserName(username);
if(u==null) {
// 没有占用
// 注册
userService.register(username, password);
return Result.success();
}else {
// 已占用
return Result.error("用户已拥有");
}
}
}
全局异常处理器
处理参数校验失败异常处理
exception.GlobalExceptionHandler
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class) // 处理所有的异常
public Result handleException(Exception e) {
e.printStackTrace();
return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "操作失败");
}
}
登录
登录认证
如果用户没有登陆的话是无法访问其他页面的
JWT令牌
JSON Web Token
组成:
第一部分:Header(类型,算法)
第二部分:Payload(自定义信息,默认信息)
第三部分:Signature(header,payload,secret,确保安全
JWT生成
导入依赖
<!-- java-JWT依赖-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<!-- 单元测试坐标(springboot整合单元测试起步依赖)-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
创建一个单元测试类test
public class JwtTest {
@Test
public void testGen() {
Map<String, Object> claims = new HashMap<>();
claims.put("id", 1);
claims.put("username", "whx");
// 生成Jwt代码
String token = JWT.create() // 链式编程
.withClaim("user", claims) // 添加载荷
.withExpiresAt(new Date(System.currentTimeMillis()+1000*60*60*12)) // 添加过期时间
.sign(Algorithm.HMAC256("whx")); // 指定算法,配置密钥
System.out.println(token);
}
}
JWT验证
public class JwtTest {
@Test
public void testParse() {
// 定义字符串,模拟用户传递过来的Token
String token = "...";
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("whx")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token); // 解析token,生成一个解析后的JWT对象
Map<String, Claim> claims = decodedJWT.getClaims();
System.out.println(claims.get("user"));
}
}
Interceptor
用于登录验证
定义一个例如LoginInterceptor类,实现HandlerInterceptor
重写preHandle方法
@Component // 把当前拦截器的对象注入到容器里
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 令牌验证
String token = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(token);
// 放行
return true;
} catch (Exception e) {
// 不放行
// 设置http响应状态码为401
response.setStatus(401);
return false;
}
}
}
再写一个WebConfig 的配置类,实现WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry /*注册拦截器*/) {
// 登录和注册接口不拦截
registry.addInterceptor(loginInterceptor).excludePathPatterns("/user/login", "/user/register");
}
}
获取用户详细信息
@GetMapping("/userInfo")
public Result<User> userInfo(@RequestHeader(name = "Authorization") String token) {
// 根据用户名查询用户
Map<String, Object> map = JwtUtil.parseToken(token);
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}
@JsonIgnore
密码这一项在测试的时候不能够出现在后台,所以要用到
User实体类
@Data
public class User {
@JsonIgnore // 让springMvc把当前对象转换成Json字符串的时候,忽略掉password,最终的json字符串中就没有password这个属性了
private String password;//密码
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
驼峰命名和下划线的转换
由于数据库变量是下划线命名,而实体类中是以驼峰命名,所以我们需要在application.yml中添加转化代码
application.yml
mybatis:
configuration:
map-underscore-to-camel-case: true #开启驼峰命名和下划线命名的自动转换
ThreadLocal
本次用ThreadLocal来优化获取用户详细信息
提供线程局部变量
提供set() get() 方法
使用ThreadLocal来存储数据,线程安全
ThreadLocalTest 测试类
public class ThreadLocalTest {
@Test
public void testThreadLocalSerAndGet() {
// 提供一个ThreadLocal对象
ThreadLocal tl = new ThreadLocal();
new Thread(()->{
tl.set("消炎");
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
}, "蓝色").start();
new Thread(()->{
tl.set("药尘");
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
System.out.println(Thread.currentThread().getName() + ":" + tl.get());
}, "绿色").start();
}
}
获取到的结果绿色的只能是药尘,蓝色的只能是消炎,线程安全。
优化用户查询
UserController
@GetMapping("/userInfo")
public Result<User> userInfo() {
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}
LoginInterceptor
@Component // 把当前拦截器的对象注入到容器里
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 令牌验证
String token = request.getHeader("Authorization");
try {
Map<String, Object> claims = JwtUtil.parseToken(token);
// 把业务数据存贮到ThreadLocal中
ThreadLocalUtil.set(claims);
// 放行
return true;
} catch (Exception e) {
// 不放行
// 设置http响应状态码为401
response.setStatus(401);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 清空ThreadLocal中的数据
ThreadLocalUtil.remove();
}
}
更新用户信息
参数校验
在需要校验的变量上添加@NotNull @NotEmpty @Email
User实体类
@Data
public class User {
@NotNull
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore // 让springMvc把当前对象转换成Json字符串的时候,忽略掉password,最终的json字符串中就没有password这个属性了
private String password;//密码
@NotEmpty
@Pattern(regexp = "^\\S{1,10}$")
private String nickname;//昵称
@NotEmpty
@Email
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
然后添加@Validated才能生效
UserController
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success();
}
更新用户头像
UserController
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl) {
userService.updateAvatar(avatarUrl);
return Result.success();
}
UserServiceImpl
调用ThreadLocal获取id
@Override
public void updateAvatar(String avatarUrl) {
// 调用ThreadLocal获取id
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvatar(avatarUrl, id);
}
UserMapper
调用数据库的now()方法
@Update("update user set user_pic=#{avatarUrl}, update_time=now() where id=#{id}")
void updateAvatar(String avatarUrl, Integer id);
Url校验
@URL就可以校验url参数
UserController
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl) {
userService.updateAvatar(avatarUrl);
return Result.success();
}
文章分类模块
新增校验
Category
@Data
public class Category {
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
CategoryController
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public Result add(@RequestBody @Validated Category category){
categoryService.add(category);
return Result.success();
}
}
实体参数的校验要在实体参数的属性上添加注解还需要在接口的方法对应的参数前添加@Validated
日期格式转换@JsonFormat
实体类对象转换成json字符串时,如何指定日期格式
Category实体类中
@Data
public class Category {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间
}
分组校验
把校验项进行分组,在完成不同的功能的时候,校验指定组中的校验项。
1、定义分组
Category实体类
@Data
public class Category {
...
...
...
public interface Add{
}
public interface Update{
}
}
2、定义校验项时指定归属的分组
groups里面的值可以填多个
@NotEmpty(groups = {Update.class, Add.class})
Category实体类
@Data
public class Category {
@NotNull(groups = Update.class)
private Integer id;//主键ID
@NotEmpty
private String categoryName;//分类名称
@NotEmpty
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;//更新时间
public interface Add extends Default {
}
public interface Update extends Default{
}
}
3、校验时指定要校验的分组
CategoryController
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){
categoryService.update(category);
return Result.success();
}
文章管理模块
自定义校验注解
1、自定义注解State
anno.State
@Documented // 元注解
@Target(FIELD) // 元注解 表示该注解可以作用的地方
@Retention(RUNTIME) // 元注解 该注解会在哪个阶段被保留 编译 | 源码 | 运行
@Constraint(validatedBy = {StateValidation.class}) // 指定提供校验规则の类
public @interface State {
// 提供校验失败后的校验信息
String message() default "state参数的值只能是已发布或者草稿";
// 指定分组
Class<?>[] groups() default { };
// 负载 获取到state注解的附加信息 用不着
Class<? extends Payload>[] payload() default { };
}
2、自定义校验数据的类StateValidation实现ConstraintValidator接口
validation.StateValidation
public class StateValidation implements ConstraintValidator</*给哪个注解提供校验规则,校验的数据类型*/State, String> {
/*
* 如果返回false,校验不通过
* 如果返回true,校验通过
* */
@Override
public boolean isValid(String value/*将来要检验的数据*/, ConstraintValidatorContext context) {
// 提供校验规则
if (value==null){
return false;
}
if(value.equals("已发布")||value.equals("草稿")){
return true;
}
return false;
}
}
3、在需要校验的地方使用自定义注解
@Data
public class Article {
@State
private String state;//发布状态 已发布|草稿
}
文章列表(条件分页)
1.自定义对象PageBean封装分页查询的结果
//分页返回结果对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean <T>{
private Long total;//总条数
private List<T> items;//当前页数据集合
}
2.ArticleController
@RequestParam(required = false)表示可以不传该参数
@GetMapping
public Result<PageBean<Article>> list(
Integer pageNum,
Integer pageSize,
@RequestParam(required = false) Integer categoryId,
@RequestParam(required = false)String state
){
PageBean<Article> pb = articleService.list(pageNum, pageSize,categoryId, state);
return Result.success(pb);
}
3.xml文件中引入PageHelper坐标
<!--pageHelper坐标-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
4.ArticleServiceImpl
@Override
public PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {
// 1.创建PageBean对象
PageBean<Article> pb = new PageBean<>();
// 2.开启分页查询 PageHelper
PageHelper.startPage(pageNum, pageSize);
// 3.调用mapper
Map<String, Object> map = ThreadLocalUtil.get();
Integer userId = (Integer) map.get("id");
// 当前页数和每页条数不需要传,PageHelper会自动将pageNum和pageSize拼接到sql后面,加上limit完成分页查询
List<Article> as = articleMapper.list(userId, categoryId, state);
// 强转 Page中提供了方法可以获取PageHelper分页查询的总记录条数和当前页数据
Page<Article> p = (Page<Article>) as;
// 把数据填充到PageBean对象中
pb.setTotal(p.getTotal());
pb.setItems(p.getResult());
return pb;
}
5.ArticleMapper
List<Article> list(Integer userId, Integer categoryId, String state);
6.sql不能写死,用映射配置文件编写动态sql
ArticleMapper
<?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="org.example.mapper.ArticleMapper">
<!--编写动态sql-->
<select id="list" resultType="org.example.pojo.Article">
select * from article
<where>
<if test="categoryId!=null">
category_id=#{categoryId}
</if>
<if test="state!=null">
and state=#{state}
</if>
and create_user=#{userId}
</where>
</select>
</mapper>