数据库使用 MySQL 、持久层框架使用 Mybatis-plus,本篇文章介绍对文章表简单的增删改查。
接口遵循 RESTful 规范,详情可参考:理解RESTful架构——阮一峰
1、首先导入依赖
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.3.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.18version>
dependency>
Mybatis-plus 官网:https://mp.baomidou.com
2、配置数据源,以及 dao 层日志级别
application-dev.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: 111111
logging:
level:
pers.qianyucc.qblog.dao: trace
3、新建文章表
CREATE TABLE `articles` (
`id` varchar(30) NOT NULL,
`author` varchar(20) NOT NULL,
`category` varchar(50) DEFAULT NULL,
`tabloid` varchar(255) DEFAULT NULL,
`content` text,
`tags` varchar(50) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`type` int(11) DEFAULT NULL,
`views` int(11) DEFAULT NULL,
`gmt_create` bigint(20) DEFAULT NULL,
`gmt_update` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4、新建对应的实体类
package pers.qianyucc.qblog.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import java.io.*;
@Data
@TableName("articles")
public class ArticlePO implements Serializable {
private static final long serialVersionUID = -1849698844197610571L;
@TableId
private String id;
private String author;
private String title;
private String content;
private String tags;
private Integer type;
private String category;
private Long gmtCreate;
private Long gmtUpdate;
private String tabloid;
private Integer views;
}
说明:
5、新建 dao 层接口
package pers.qianyucc.qblog.dao;
import com.baomidou.mybatisplus.core.mapper.*;
import pers.qianyucc.qblog.model.entity.*;
@Mapper
public interface ArticleMapper extends BaseMapper<ArticlePO> {
}
说明:
6、配置分页,注意加上注解扫描
package pers.qianyucc.qblog.config;
import com.baomidou.mybatisplus.extension.plugins.*;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.*;
import org.mybatis.spring.annotation.*;
import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.*;
@EnableTransactionManagement
@Configuration
@MapperScan("pers.qianyucc.qblog.dao")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(10);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
7、创建 VO 转换接口,用于由 PO 转换成 VO
package pers.qianyucc.qblog.model.vo;
public interface IConverter<T,E> {
/**
* VO 转换函数
*
* @param t 目标对象
* @return 转换结果
*/
E convertToVO(T t);
}
说明:
8、创建 ArticleVO 类,并实现 PO 到 VO 转换的方法
package pers.qianyucc.qblog.model.vo;
import cn.hutool.core.bean.*;
import cn.hutool.core.bean.copier.*;
import lombok.*;
import pers.qianyucc.qblog.model.entity.*;
import pers.qianyucc.qblog.model.enums.*;
import pers.qianyucc.qblog.utils.*;
@Data
public class ArticleVO {
private String id;
private String author;
private String title;
private String content;
private String[] tags;
private String type;
private String category;
private String gmtCreate;
private String gmtUpdate;
private String tabloid;
private Integer views;
/**
* 根据 PO 创建 VO 对象
* @param articlePO PO对象
* @return VO对象
*/
public static ArticleVO fromArticlePO(ArticlePO articlePO) {
return new Converter().convertToVO(articlePO);
}
private static class Converter implements IConverter<ArticlePO, ArticleVO> {
@Override
public ArticleVO convertToVO(ArticlePO article) {
final ArticleVO vo = new ArticleVO();
BeanUtil.copyProperties(article, vo, CopyOptions.create()
.ignoreNullValue().ignoreError());
vo.setTags(article.getTags().split(","));
for (ArticleTypeEnum item : ArticleTypeEnum.values()) {
if (item.getFlag() == article.getType()) {
vo.setType(item.getNotes());
}
}
vo.setGmtCreate(BlogUtils.formatDatetime(article.getGmtCreate()));
vo.setGmtUpdate(BlogUtils.formatDatetime(article.getGmtUpdate()));
return vo;
}
}
}
这里的文章类型为一个枚举类型
package pers.qianyucc.qblog.model.enums;
public enum ArticleTypeEnum {
/**
* 文章类型:原创
*/
ORIGINAL("原创", 1),
/**
* 文章类型:转载
*/
REPRINT("转载", 0);;
private String notes;
private int flag;
public String getNotes() {
return notes;
}
public int getFlag() {
return flag;
}
ArticleTypeEnum(String notes, int flag) {
this.notes = notes;
this.flag = flag;
}
}
9、定义 PageVO 来储存分页信息
package pers.qianyucc.qblog.model.vo;
import lombok.*;
import java.util.*;
@Data
@Builder
public class PageVO<T> {
protected List<T> records;
protected long total;
protected long size;
protected long current;
}
10、编写 ArticleService
package pers.qianyucc.qblog.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.*;
import pers.qianyucc.qblog.dao.*;
import pers.qianyucc.qblog.model.entity.*;
import pers.qianyucc.qblog.model.vo.*;
import java.util.*;
import java.util.stream.*;
@Service
public class ArticleService {
@Autowired
private ArticleMapper articleMapper;
public PageVO<ArticleVO> getArticles(int page, int limit) {
QueryWrapper<ArticlePO> qw = new QueryWrapper<>();
qw.select(ArticlePO.class, i -> !"content".equals(i.getColumn()));
Page<ArticlePO> res = articleMapper.selectPage(new Page<>(page, limit), qw);
List<ArticleVO> articleVOS = res.getRecords().stream()
.map(ArticleVO::fromArticlePO)
.collect(Collectors.toList());
PageVO<ArticleVO> pageVO = PageVO.<ArticleVO>builder()
.records(articleVOS.isEmpty() ? new ArrayList<>() : articleVOS)
.total(res.getTotal())
.current(res.getCurrent())
.size(res.getSize())
.build();
return pageVO;
}
}
说明:
11、编写 ArticleController
package pers.qianyucc.qblog.controller;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.web.bind.annotation.*;
import pers.qianyucc.qblog.model.vo.*;
import pers.qianyucc.qblog.service.*;
@Api("与文章相关的api接口")
@RestController
public class ArticleController {
@Autowired
private ArticleService articleService;
@ApiOperation("批量获取文章")
@GetMapping("/articles")
public Results<PageVO> getArticles(
@ApiParam("页码")
@RequestParam(required = false, defaultValue = "1") Integer page,
@ApiParam("每页存放的记录数")
@RequestParam(required = false, defaultValue = "5") Integer limit) {
return Results.ok(articleService.getArticles(page, limit));
}
}
12、启动项目,访问http://localhost:9000/api/v1/doc.html
,使用 Swagger 测试接口
1、新建 ArticleDTO 封装从前端获取到的数据
package pers.qianyucc.qblog.model.dto;
import cn.hutool.core.bean.*;
import io.swagger.annotations.*;
import lombok.*;
import org.hibernate.validator.constraints.*;
import pers.qianyucc.qblog.model.entity.*;
import javax.validation.constraints.NotEmpty;
@Data
@ApiModel(value = "文章类", description = "前端传入的文章信息")
public class ArticleDTO {
@NotEmpty(message = "文章作者不能为空")
@ApiModelProperty(notes = "文章作者", example = "竹林笔墨")
private String author;
@NotEmpty(message = "文章标题不能为空")
@ApiModelProperty(notes = "文章标题", example = "快速入门SpringBoot")
private String title;
@NotEmpty(message = "文章内容不能为空")
@ApiModelProperty(notes = "文章内容")
private String content;
@NotEmpty(message = "文章标签不能为空")
@ApiModelProperty(notes = "文章标签,用英文逗号隔开", example = "java,集合")
private String tags;
@Range(min = 0, max = 1, message = "文章类型必须为1或者0")
@ApiModelProperty(notes = "文章类型,0表示转载,1表示原创", example = "1")
private Integer type;
@NotEmpty(message = "文章分类不能为空")
@ApiModelProperty(notes = "文章分类", example = "设计模式")
private String category;
@NotEmpty(message = "文章简介不能为空")
@ApiModelProperty(notes = "文章摘要")
private String tabloid;
/**
* 将 DTO 转换为 PO
*
* @param articleDTO 要转换的DTO
* @param isUpdate 此对象是否为更新对象
* @return 转换结果
*/
public ArticlePO toArticlePO(boolean isUpdate) {
ArticlePO po = new Converter().convertToPO(this);
po.setViews(isUpdate ? null : 0);
po.setGmtCreate(isUpdate ? null : po.getGmtUpdate());
return po;
}
private static class Converter implements IConverter<ArticleDTO, ArticlePO> {
@Override
public ArticlePO convertToPO(ArticleDTO articleDTO) {
ArticlePO po = new ArticlePO();
po.setGmtUpdate(System.currentTimeMillis());
BeanUtil.copyProperties(articleDTO, po);
return po;
}
}
}
说明:
package pers.qianyucc.qblog.model.dto;
public interface IConverter<T,E> {
/**
* 将对应的 DTO 转换为 PO
* @param t 需要转换的 DTO 类
* @return 转换结果
*/
E convertToPO(T t);
}
2、ArticleConreoller
@PostMapping("/articles")
@ApiOperation("新增文章")
public Results postArticles(@ApiParam(name = "文章信息", value = "传入json格式", required = true)
@RequestBody @Valid ArticleDTO articleDTO) {
String id = articleService.insArticle(articleDTO);
return Results.ok(MapUtil.of("id", id));
}
说明:
3、ArticleService
public String insArticle(ArticleDTO articleDTO) {
ArticlePO po = articleDTO.toArticlePO(false);
String id = IdUtil.objectId();
po.setId(id);
articleMapper.insert(po);
return id;
}
说明:
4、别忘了全局异常处理(处理参数校验失败异常)
GlobalExceptionHandler
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.OK)
public Results handleValidationExceptions(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return Results.error("参数错误", errors);
}
5、使用 Swagger 测试
当某一字段为空时:
1、ArticleController
@GetMapping("/article/{id}")
@ApiOperation("根据id查询文章信息")
@ApiImplicitParam(name = "id", value = "文章id", required = true, dataType = "String", paramType = "path")
public Results<ArticleVO> getArticle(@PathVariable String id) {
ArticleVO articleVO = articleService.findById(id);
return Results.ok(articleVO);
}
2、ArticleService
public ArticleVO findById(String id) {
ArticlePO articlePO = articleMapper.selectById(id);
if (Objects.isNull(articlePO)) {
throw new BlogException(INVALID_ID);
}
return ArticleVO.fromArticlePO(articlePO);
}
说明:
INVALID_ID
在ErrorInfoEnum
中定义:INVALID_ID(4008, "你的id不合法"),
使用 Swagger 测试
1、ArticleController
@DeleteMapping("/article/{id}")
@ApiOperation("根据id删除文章")
@ApiImplicitParam(name = "id", value = "文章id", required = true, dataType = "String", paramType = "path")
public Results deleteArticle(@PathVariable String id) {
articleService.deleteArticle(id);
return Results.ok("删除成功", null);
}
2、ArticleService
public void deleteArticle(String id) {
int i = articleMapper.deleteById(id);
if (i <= 0) {
throw new BlogException(INVALID_ID);
}
}
使用 Swagger 测试
再次查询文章,发现文章已经不存在
1、ArticleController 注意这里不使用 @Valid
注解,因为我们不一定总是需要更新全部字段
@PutMapping("/article/{id}")
@ApiOperation("修改文章")
@ApiImplicitParam(name = "id", value = "文章id", required = true, dataType = "String", paramType = "path")
public Results<Map<String, Object>> putArticles(@ApiParam(name = "要修改的文章信息", value = "传入json格式", required = true)
@RequestBody ArticleDTO articleDTO,
@PathVariable String id) {
articleService.updateArticle(articleDTO, id);
return Results.ok("更新成功", MapUtil.of("id", id));
2、ArticleService
public void updateArticle(ArticleDTO articleDTO, String id) {
ArticlePO dbArticle = articleMapper.selectById(id);
if (Objects.isNull(dbArticle)) {
throw new BlogException(INVALID_ID);
}
ArticlePO articlePO = articleDTO.toArticlePO(true);
articlePO.setId(id);
articleMapper.updateById(articlePO);
}
先查询文章信息
更新文章内容
再次查询,发现文章内容已经修改
参考代码:https://gitee.com/qianyucc/QBlog2/tree/v-2.0