前言
该架构是参考公司原架构做了优化,计划慢慢从0开始完全独立自己搭建一个基于springboot的restful服务后台架构,并且完全后端分离。系列文章所涉及的项目源码都放在了个人github上,关于前端我采用vue,后期会写在其他文章中。
本人的github地址:https://github.com/jokerliuli
接上一章,本文开始springboot集成mybatis-plus
mybatis
mybatis,作为dao层大哥大框架,可以说是目前使用率最高的。面试的时候也几乎是必问的一项(底层源码如果能看懂差不多就能敲门HAT了),关于它的详细信息这篇文章不再过多介绍,我这篇文章是在各位同学已经学会mybatis的基础上展开。
mybatis-plus
我们直接进入mybatis-plus。
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。这是MP官网的介绍,很简单很准确,按照我个人理解,MP其实是MyBatis模仿JPA的,然后对tkmap(通用mapper)的一种升级。
什么是JPA呢,这边简单介绍一下就是一个dao层的ORM框架规则,spring-data-jpa就是jpa的一个实现,jpa旨在减少sql,直接通过接口的命名就自动映射拼接sql。有兴趣的同学可以去自学一下,这边也不多介绍了。
MP很好的继承了tk的通用接口(单表的增删改查),然后在此基础上有自己的条件构造器(类似jpa的地方),最后还有自己的代码生成器。这些在官网的guide里写的很清晰,建议结合guide一起看这篇文章。
先在pom中引入:
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.6</version>
</dependency>
如果需要用到MP的代码生成还需要引入freemarker 模板引擎:
<!-- freemarker 模板引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
再来看MP的教程
看到这个用过tk的同学应该已经懂了,这个dao层有现成的写好的很多CRUD接口,我们只需要extends BaseMapper,直接点就能用。
实战
假如我们有一个Information表,我们怎么对其完成增删改查的restful接口呢?
目录如下:
InformationController
package com.jokerliu.manage.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import com.jokerliu.enums.Result;
import com.jokerliu.enums.ResultStatusCode;
import javax.annotation.Resource;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jokerliu.manage.service.IInformationService;
import com.jokerliu.manage.entity.Information;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 资讯表 前端控制器
* </p>
*
* @author JokerLiu
* @since 2019-03-18
*/
@Slf4j
@RestController
@RequestMapping("/manage/information")
public class InformationController {
@Resource
private IInformationService iInformationService;
/**
* 单个新增
* @param information 传递的实体
* @return Result
*/
@PostMapping("save")
public Result save(@RequestBody Information information){
//log.info(information.toString());
return new Result(ResultStatusCode.OK,iInformationService.save(information));
}
/**
* 单个删除
* @param information 传递的实体
* @return Result
*/
@PostMapping("remove")
public Result remove(@RequestBody Information information) {
return new Result(ResultStatusCode.OK,iInformationService.remove(new QueryWrapper(information)));
}
/**
* 单个更新
* @param information 传递的实体
* @return Result
*/
@PostMapping("update")
public Result update(@RequestBody Information information) {
//information.setUpdateDate(null);
return new Result(ResultStatusCode.OK,iInformationService.updateById(information));
}
/**
* 根据id查询获取一个返回
* @param id 对象id
* @return Result
*/
@GetMapping("getOne")
public Result getOne(@RequestParam(name = "id") Long id){
return new Result(ResultStatusCode.OK,iInformationService.getById(id));
}
/**
* 条件查询list
* @param information 传递的实体
* @return Result
*/
@PostMapping("list")
public Result list( @RequestBody Information information) {
return new Result(ResultStatusCode.OK,iInformationService.list(new QueryWrapper(information)));
}
/**
* 条件分页查询
* @param page 第几页(1开始)
* @param limit 每页size
* @return Result
*/
@PostMapping("page")
public Result page(@RequestParam(name = "page") int page,
@RequestParam(name = "limit") int limit,
@RequestBody Information information) {
return new Result(ResultStatusCode.OK,iInformationService.page(new Page<>(page,limit),new QueryWrapper(information)));
}
}
Information
package com.jokerliu.manage.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.jokerliu.entity.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
* 资讯表
* </p>
*
* @author JokerLiu
* @since 2019-03-18
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("manage_information")
public class Information extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 文章标题
*/
private String title;
/**
* 简略标题
*/
private String shortTitle;
/**
* 分类栏目:新闻动态(1),产品方案(2),成功案例(3)
*/
private Integer informationType;
/**
* 关键词
*/
private String keyword;
/**
* 文章摘要
*/
private String summary;
/**
* 文章作者
*/
private String author;
/**
* 删除(0)草稿(1)发布(2)
*/
private Integer publishStatus;
/**
* 缩略图
*/
private String thumbnail;
/**
* 文章内容
*/
private String content;
}
InformationMapper
package com.jokerliu.manage.mapper;
import com.jokerliu.manage.entity.Information;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 资讯表 Mapper 接口
* </p>
*
* @author JokerLiu
* @since 2019-03-05
*/
public interface InformationMapper extends BaseMapper<Information> {
}
IInformationService
package com.jokerliu.manage.service;
import com.jokerliu.manage.entity.Information;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 资讯表 服务类
* </p>
*
* @author JokerLiu
* @since 2019-03-18
*/
public interface IInformationService extends IService<Information> {
}
InformationServiceImpl
package com.jokerliu.manage.service.impl;
import com.jokerliu.manage.entity.Information;
import com.jokerliu.manage.mapper.InformationMapper;
import com.jokerliu.manage.service.IInformationService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 资讯表 服务实现类
* </p>
*
* @author JokerLiu
* @since 2019-03-18
*/
@Service
public class InformationServiceImpl extends ServiceImpl<InformationMapper, Information> implements IInformationService {
}
InformationMapper.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.jokerliu.manage.mapper.InformationMapper">
</mapper>
其中我们注意IInformationService和InformationMapper,他们需要继承IService和BaseMapper,InformationMapper.xml什么都不需要写,然后就能实现MP文档中的那些CRUD接口了。
接下来就是在InformationController等地方书写自己需要的逻辑就行了。这边我已经写好了controller层的几个常用接口了。
代码生成
上面的实战例子里的controller,其实就是我通过代码生成加载自定义controller模板生成的。
这边我直接贴出我的代码生成器配置和模板,各位同学需要按照自己需求结合MP关于代码生成的文档来修改,不要直接黏贴使用,这个模板的自定义性太强了,我的模板你很可能不适合。
CodeGenerator
package com.jokerliu;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* Created by Joker
* Date: 2018/12/10
* Time: 13:57
*@author alex
* 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
*/
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
/**
* 拼接模块名
*/
String projectPath = System.getProperty("user.dir")+"/"+scanner("完整模块名");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("JokerLiu");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://47.100.27.31:3306/jladmin?characterEncoding=utf-8&allowMultiQueries=true");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("alex");
dsc.setPassword("536");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("父目录名"));
pc.setParent("com.jokerliu");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
List<FileOutConfig> focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig tc = new TemplateConfig();
tc.setController("templates/controller.java");
// 配置自定义输出模板
//指定自定义模板路径(resource目录下),注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
tc.setXml(null);
mpg.setTemplate(tc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.jokerliu.entity.BaseEntity");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// strategy.setSuperControllerClass("com.baomidou.ant.common.BaseController");
strategy.setInclude(scanner("表名"));
// 忽略那些数据库字段,因为BaseEntity里已经包含
strategy.setSuperEntityColumns("id","remarks","sort","version","status","uuid","create_by","create_date","update_by","update_date");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
模板controller.java.ftl
package ${package.Controller};
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import com.jokerliu.enums.Result;
import com.jokerliu.enums.ResultStatusCode;
import javax.annotation.Resource;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
<#if restControllerStyle>
import org.springframework.web.bind.annotation.*;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* <p>
* ${table.comment!} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
@Slf4j
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName??>/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
@Resource
private ${table.serviceName} i${entity}Service;
/**
* 单个新增
* @param ${table.entityPath} 传递的实体
* @return Result
*/
@PostMapping("save")
public Result save(@RequestBody ${entity} ${table.entityPath}){
//log.info(${table.entityPath}.toString());
return new Result(ResultStatusCode.OK,i${entity}Service.save(${table.entityPath}));
}
/**
* 单个删除
* @param ${table.entityPath} 传递的实体
* @return Result
*/
@PostMapping("remove")
public Result remove(@RequestBody ${entity} ${table.entityPath}) {
return new Result(ResultStatusCode.OK,i${entity}Service.remove(new QueryWrapper(${table.entityPath})));
}
/**
* 单个更新
* @param ${table.entityPath} 传递的实体
* @return Result
*/
@PostMapping("update")
public Result update(@RequestBody ${entity} ${table.entityPath}) {
//${table.entityPath}.setUpdateDate(null);
return new Result(ResultStatusCode.OK,i${entity}Service.updateById(${table.entityPath}));
}
/**
* 根据id查询获取一个返回
* @param id 对象id
* @return Result
*/
@GetMapping("getOne")
public Result getOne(@RequestParam(name = "id") Long id){
return new Result(ResultStatusCode.OK,i${entity}Service.getById(id));
}
/**
* 条件查询list
* @param ${table.entityPath} 传递的实体
* @return Result
*/
@PostMapping("list")
public Result list( @RequestBody ${entity} ${table.entityPath}) {
return new Result(ResultStatusCode.OK,i${entity}Service.list(new QueryWrapper(${table.entityPath})));
}
/**
* 条件分页查询
* @param page 第几页(1开始)
* @param limit 每页size
* @return Result
*/
@PostMapping("page")
public Result page(@RequestParam(name = "page") int page,
@RequestParam(name = "limit") int limit,
@RequestBody ${entity} ${table.entityPath}) {
return new Result(ResultStatusCode.OK,i${entity}Service.page(new Page<>(page,limit),new QueryWrapper(${table.entityPath})));
}
}
</#if>
直接执行CodeGenerator的main函数,输入完整模块名、父目录名、表名,就可以生成所需的
总结和注意点
MP的官方文档很详细,只要懂mybatis都学起来很快。这边说几个重点
1.条件构造器:
通常new QueryWapper()就可以满足日常开发。
2.自定义分页
分页有时候需要自定义sql(比如关联查询)
3.关联查询,使用到数据库的某些函数的时候,等还是xml里自己写sql吧,建议不要过度依赖MP,基本遇到什么问题就先查MP文档,优先MP解决,不可以在考虑自己写sql。推荐大家学习一下jpa,以后应该是主流。