目标:MybatisPlus,为简化Mybatis开发而生,只做增强,不做改变
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。简单来说就是简化JDBC操作 愿景 我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
几乎包含市面上所有主流数据库
官方地址:传送门 核心步骤: 使用第三方组件:
DROP TABLE IF EXISTS USER;
CREATE TABLE USER (
id BIGINT ( 20 ) NOT NULL COMMENT '主键ID',
NAME VARCHAR ( 30 ) NULL DEFAULT NULL COMMENT '姓名',
age INT ( 11 ) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR ( 50 ) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY ( id )
);
DELETE FROM USER;
INSERT INTO USER ( id, NAME, age, email )
VALUES
( 1, 'Jone', 18, 'test1@baomidou.com' ),
( 2, 'Jack', 20, 'test2@baomidou.com' ),
( 3, 'Tom', 28, 'test3@baomidou.com' ),
( 4, 'Sandy', 21, 'test4@baomidou.com' ),
( 5, 'Billie', 24, 'test5@baomidou.com' );
-- 真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified
spring-boot
项目,导入依赖,初始化项目<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--spring-boot-test-->
<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>
</dependencies>
注意:在使用mybatis-plus可以简化我们大量的代码,不可以在同一个项目下导入mybatis依赖和mybatis-plus,会有版本冲突!!!
# MySQL5
spring:
datasource:
url: jdbc:mysql://localhost:3306/index
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# MySQL8配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
注意:
com.mysql.jdbc.Driver
com.mysql.cj.jdbc.Driver
serverTimezone=GMT%2B8
@Getter
@Setter
@ToString
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
dao
接口/**
* Software:IntelliJ IDEA 2020.1 x64
* Author: MoBai·杰
* Date: 2020/6/8 14:05
* ClassName:UserDao
* 使用mybatis-plus,每个mapper接口都需要继承BaseMapper
* BaseMapper类描述: Mapper继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
*/
@Repository // 表示持久层
public interface UserDao extends BaseMapper<User> {
}
扫描mapper接口
/**
* 全局启动类
*/
// 表示扫描mapper包下的所有接口
@MapperScan("com.mobai.mybatis_plus.dao")
@SpringBootApplication
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
@SpringBootTest
class MybatisPlusApplicationTests {
// 注入Dao接口,继承BaseMapper
@Autowired
private UserDao userDao;
@Test
void contextLoads() {
// 调用BaseMapper查询方法<selectList>
List<User> list = userDao.selectList(null);
list.forEach(System.out::println);
}
}
yml配置添加日志处理
# 配置日志输出 使用默认控制台打印
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
/**
* 添加数据
*/
@Test
public void testInsert() {
User user = new User();
user.setName("墨白君");
user.setAge(25);
user.setEmail("mobaijun8@163.com");
// mybatis-plus会自动帮助我们生成主键ID
int insert = userDao.insert(user);
// 被影响的行数
System.out.println("insert = " + insert);
// ID会自动回填
System.out.println("user = " + user);
}
数据库插入的ID是全局唯一ID
mybats-plus
默认策略为:ID_WORKER
扩展:分布式系统唯一ID生成:传送门
Twitter的snowflake算法 snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。以上实现思路基本可以保证主键ID为全球唯一
看IdType
源码
public enum IdType {
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID
* 该类型可以通过自己注册自动填充插件进行填充
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
}
IdType.AUTO
:主键自增长
-- 已有数据表添加自增策略
ALTER TABLE `user` CHANGE id LONG NOT NULL AUTO_INCREMENT PRIMARY KEY;
主键自增长策略
@Getter
@Setter
@ToString
public class User {
// 常见的主键生成策略(UUID/MySQL自增长/雪花算法/redis/zookeeper...)
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
/**
* 添加数据
*/
@Test
public void testInsert() {
User user = new User();
user.setName("墨白");
user.setAge(21);
user.setEmail("mobaijun8@163.com");
// mybatis-plus会自动帮助我们生成主键ID
int insert = userDao.insert(user);
// 被影响的行数
System.out.println("insert = " + insert);
// ID会自动回填
System.out.println("user = " + user);
}
/**
* 根据ID修改操作
*/
@Test
public void testUpdate() {
User user = new User();
user.setId(1269882840871374849L);
user.setName("小柠檬");
user.setEmail("mobaijun8@163.com");
// 根据用户ID修改
int i = userDao.updateById(user);
System.out.println("i = " + i);
}
注意:
updateById()
名字虽然是ById,但源码里面的参数是一个对象!!! 注意:Mybatis-Plus会通过条件帮我们自动拼接动态SQL
,比mybatis手动编写动态SQL标签灵活
比如一些测试
log
,创建时间和修改时间,这些操作一般都是通过自动化完成的,一般我们不希望手动操作!!! 扩展:阿里巴巴开发手册规定:所有的数据库表都要包含两个字段:gmt_create
和gmt_modified
,几乎所有的表都要配置上,并且需要自动化
自动填充有两种方式
-- 已有数据表添加新字段
alter table user add gmt_create date; -- 开始时间
alter table user add gmt_modified date; -- 结束时间
@Getter
@Setter
@ToString
public class User {
// 常见的主键生成策略(UUID/MySQL自增长/雪花算法/redis/zookeeper...)
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
// 开始时间
private Date gmtCreate;
// 结束时间
private Date gmrModified;
}
/**
* 添加数据
*/
@Test
public void testInsert() {
User user = new User();
user.setName("框架师");
user.setAge(21);
user.setEmail("mobaijun8@163.com");
// mybatis-plus会自动帮助我们生成主键ID
int insert = userDao.insert(user);
// 被影响的行数
System.out.println("insert = " + insert);
// ID会自动回填
System.out.println("user = " + user);
}
// 开始时间
// 插入填充字段
@TableField(fill = FieldFill.INSERT)
private Date gmtCreate;
// 结束时间
// 更新填充字段
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date gmtModified;
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
@Slf4j // 添加日志
@Component // 注入Spring容器中
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入时的填充策略
*
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
// 添加日志
log.info("start insert fill............");
/**
* String fieldName: 需要插入的字段
* Object fieldVal: 需要插入的类型
* MetaObject metaObject: 需要给那个数据处理
*/
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
/**
* 更新时的填充策略
*
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
log.info("end update fill............");
/**
* 更新的时候只需要更新的字段
*/
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
自定义元对象处理器接口必须要实现
MetaObjectHandler
接口
/**
* 添加数据
*/
@Test
public void testInsert() {
User user = new User();
user.setName("小狐狸");
user.setAge(21);
user.setEmail("mobaijun@163.com");
// mybatis-plus会自动帮助我们生成主键ID
int insert = userDao.insert(user);
// 被影响的行数
System.out.println("insert = " + insert);
// ID会自动回填
System.out.println("user = " + user);
}
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
version
字段// 配置乐观锁插件
@Version // 乐观锁注解
private Integer version;
config
包,创建MyBatisPlusConfig
配置类/**
* Software:IntelliJ IDEA 2020.1 x64
* Author: MoBai·杰
* Date: 2020/6/8 16:52
* ClassName:MyBatisPlusConfig
* 类描述: Spring配置类
*/
// 表示扫描mapper包下的所有接口
@MapperScan("com.mobai.mybatis_plus.dao")
@EnableTransactionManagement // 开启自动事务
@Configuration // 表示这个类是全局配置类
public class MyBatisPlusConfig {
/**
* 注册乐观锁插件
*
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
/**
* 测试乐观锁成功
*/
@Test
public void testOptimisticLocker() {
// 1.查询用户信息
User user = userDao.selectById(1L);
// 2.修改用户信息
user.setName("墨白科技");
user.setEmail("mobaijun8@163.com");
user.setAge(21);
// 3.执行更新
userDao.updateById(user);
}
# 控制台打印sql UPDATE user SET name=?, age=?, email=?, version=?, gmt_create=?, gmt_modified=? WHERE id=? AND version=?
/**
* 测试乐观锁失败!多线程下
*/
@Test
public void testOptimisticLocker2() {
/**
* 线程1
*/
User user = userDao.selectById(1L);
user.setName("墨白科技111");
user.setEmail("mobaijun8@163.com");
user.setAge(21);
/**
* 线程2
* 模拟另外一个线程执行了插队操作
*/
User user2 = userDao.selectById(1L);
user2.setName("墨白科技222");
user2.setEmail("mobaijun8@163.com");
user2.setAge(21);
userDao.updateById(user2);
// 自旋锁来多次尝试提交!
userDao.updateById(user); // 如果没有乐观锁就会覆盖插队线程的值!
}
/**
* 测试查询
*/
@Test
public void testSelectById() {
User id = userDao.selectById(1L);
System.out.println("id = " + id);
}
/**
* 批量查询
*/
@Test
public void testSelectByBatchId() {
List<User> users = userDao.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println("users = " + users);
}
/**
* 按条件查询
*/
@Test
public void testSelectByBatchIdS() {
HashMap<String, Object> map = new HashMap<>();
// 自定义条件
map.put("name", "Jack");
map.put("age", 20);
List<User> users = userDao.selectByMap(map);
users.forEach(System.out::println);
}
应用场景:分页在浏览场景或一些web网站非常常见
limit
进行分页Mybatis
插件PageHelper
MybatisPlus
其实也内置了分页插件 /**
* 分页拦截器
*
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
// 开启 count 的 join 优化,只针对部分 left join
return new PaginationInterceptor();
}
/**
* 测试分页查询
*/
@Test
public void testPage() {
/**
* 参数一: 当前页
* 参数一: 页大小
* 使用了分页插件以后,所有的分页操作也变得非常简单
*/
Page<User> page = new Page<>(2, 4);
// 调用selectPage进行分页
userDao.selectPage(page, null);
page.getRecords().forEach(System.out::println);
// 获取记录总数
System.out.println("page = " + page.getTotal());
}
/**
* 测试删除
*/
@Test
public void testDeleteById() {
userDao.deleteById(1269882840871374854L);
}
/**
* 批量删除
*/
@Test
public void testDeleteList() {
userDao.deleteBatchIds(Arrays.asList(1269882840871374853L,
1269882840871374852L,
1269882840871374851L,
1269882840871374850L,
1269882840871374849L));
}
/**
* 通过集合删除
*/
@Test
public void testDeleteMap() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "墨白科技222");
userDao.deleteByMap(map);
}
deleted
逻辑删除// 逻辑删除
@TableLogic // 逻辑删除注解
private Integer deleted;
MyBatisPlusConfig
添加逻辑删除组件/**
* 逻辑删除组件
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
yml
配置逻辑删除# 配置逻辑删除
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
/**
* 逻辑删除
* 相当于回收站
*/
@Test
public void testDeleted() {
userDao.deleteById(6L);
}
0
记录依旧在数据库,但是值确已经变化了!
MyBatisPlusConfig
导入插件/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev", "test"}) // 设置dev test环境开启,保证开发效率
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor =
new PerformanceInterceptor();
// 设置执行时间和写入日志
Properties properties = new Properties();
properties.setProperty("format", "true");
// 设置SQL执行时间和写入日志
performanceInterceptor.setProperties(properties);
return performanceInterceptor;
}
// 设置SQL最大执行时间,如果超过了则不执行
performanceInterceptor.setMaxTime(10);
// 设置SQL是否格式化
performanceInterceptor.setFormat(true);
application.yml
配置插件# 数据库配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/index?characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# 设置性能分析插件
profiles:
active: dev
/**
* 测试性能分析插件
*/
@Test
public void contextLoads2() {
List<User> users = userDao.selectList(null);
users.forEach(System.out::println);
}
使用性能分析插件,可以帮助我们提高效率!
十分重要:Wrapper
@SpringBootTest
public class WrapperTest {
@Autowired
private UserDao userDao;
/**
* 条件查询
*/
@Test
public void wrapper1() {
// 查询name不为空和email不为空并且年龄大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 链式编程
wrapper.isNotNull("name").
isNotNull("email").
ge("age", 20);
userDao.selectList(wrapper).forEach(System.out::println);
}
/**
* 根据名称查询
*/
@Test
public void wrapper2() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name", "MoBai");
// selectOne(wrapper);查询一个数据
User user = userDao.selectOne(wrapper);
System.out.println("user = " + user);
}
/**
* 查询区间
*/
@Test
public void wrapper3() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 查询年龄在15-25之间的数据
wrapper.between("age", 15, 25);
// selectOne(wrapper);查询一个数据
Integer count = userDao.selectCount(wrapper);
System.out.println(count);
}
/**
* 模糊查询
*/
@Test
public void test4() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.
// 查询name不包含e的字段
notLike("name", "e").
// 左和右 t%
likeRight("email", "t");
List<Map<String, Object>> maps = userDao.selectMaps(wrapper);
maps.forEach(System.out::println);
}
/**
* 模糊查询
*/
@Test
public void test5() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// id在子查询中查出来
wrapper.inSql("id", "select id from user where id < 3");
List<Object> list = userDao.selectObjs(wrapper);
list.forEach(System.out::println);
}
/**
* 模糊查询
*/
@Test
public void test6() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 通过ID进行排序
wrapper.orderByAsc("id");
List<User> list = userDao.selectList(wrapper);
list.forEach(System.out::println);
}
}
其余的方法多练习,多记笔记
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--简化实体类-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.43</version>
</dependency>
<!--mybatis-plus启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!--spring-boot-test-->
<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>
</dependencies>
</project>
package com.mobai.mybatis_plus;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
/**
* Software:IntelliJ IDEA 2020.1 x64
* Author: MoBai·杰
* Date: 2020/6/9 11:38
* ClassName:MoBaiCode
* 类描述: MybatisPlus代码生成器
*/
public class MoBaiCode {
public static void main(String[] args) {
// MybatisPlus代码生成器必须要new一个AutoGenerator对象
AutoGenerator mpg = new AutoGenerator();
/**
* 配置策略
* 1.全局配置
*/
GlobalConfig gc = new GlobalConfig();
// 获取当前资源路径
String projectPath = System.getProperty("user.dir");
// 生成的代码输出路径
gc.setOutputDir(projectPath + "/src/main/java");
// 生成作者信息
gc.setAuthor("MoBai·杰");
// 是否打开输出目录
gc.setOpen(false);
// 是否覆盖文件
gc.setFileOverride(false);
// 取消生成以后I前缀
gc.setServiceName("%sService");
// 指定生成的主键的ID类型
gc.setIdType(IdType.ID_WORKER);
// 只使用 java.util.date 代替
gc.setDateType(DateType.ONLY_DATE);
// 开启 swagger2 模式
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
/**
* 2.配置数据源相关
* DataSourceConfig:数据库配置
*/
DataSourceConfig ds = new DataSourceConfig();
ds.setDriverName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/index?characterEncoding=utf-8");
ds.setUsername("root");
ds.setPassword("root");
// 数据库类型
ds.setDbType(DbType.MYSQL);
mpg.setDataSource(ds);
/**
* 3.设置包的配置
*/
PackageConfig pc = new PackageConfig();
// 项目名称
pc.setModuleName("cxdz");
// 项目路径
pc.setParent("com.mobai");
// 实体类包名
pc.setEntity("pojo");
// Dao包名
pc.setMapper("dao");
// service包名
pc.setService("service");
// service实现类包名
pc.setServiceImpl("service.impl");
// controller包名
pc.setController("controller");
mpg.setPackageInfo(pc);
/**
* 4.策略配置
*/
StrategyConfig sc = new StrategyConfig();
// 需要映射的表
sc.setInclude("user");
// 从数据库表到文件的命名策略/下划线转驼峰命名
sc.setNaming(NamingStrategy.underline_to_camel);
sc.setColumnNaming(NamingStrategy.underline_to_camel);
// 是否为lombok模型/默认false
sc.setEntityLombokModel(true);
// 设置逻辑删除字段
sc.setLogicDeleteFieldName("deleted");
/**
* 5.设置自动填充
*/
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> list = new ArrayList<>();
list.add(gmtCreate);
list.add(gmtModified);
// 表填充字段
sc.setTableFillList(list);
// 设置乐观锁字段
sc.setVersionFieldName("version");
// 生成@RestController控制器
sc.setRestControllerStyle(true);
// 驼峰转连字符
sc.setControllerMappingHyphenStyle(true);
mpg.setStrategy(sc);
// 执行
mpg.execute();
}
}
MybatisPlus是对mybatis的增强,只做增强,不做修改