# mybatis-plus-demo **Repository Path**: magickdc/mybatis-plus-demo ## Basic Information - **Project Name**: mybatis-plus-demo - **Description**: mybatis-plus 学习 demo - **Primary Language**: Unknown - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-05-18 - **Last Updated**: 2021-09-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Mybatis-Plus 入门 ## 1. 简介 Mybatis-Plus(简称MP)是一个Mybatis增强工具,在Mybatis基础上只做增强不做改变,为简化开发、提高效率而生。 1. **润物无声**:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。 2. **效率至上**:只须简单配置,即可快速进行CRUD操作,从而减少大量时间。 3. **丰富功能**:热加载、代码生成、分页、性能分析等功能一应俱全。 ## 2. 快速入门 ### 2.1 创建并初始化数据库 1. 创建数据库: `mybatis-plus` 2. 创建数据表:`user` ```mysql 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`) ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci; ``` 3. 插入数据 ```mysql 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'); ``` ### 2.2 创建 Spring Boot 工程 1. 版本选择:2.2.1 ```xml org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE ``` 2. 引入 Mybatis-Plus 依赖 ```xml com.baomidou mybatis-plus-boot-starter 3.2.0 ``` 3. 引入 Mysql 驱动依赖 ```xml mysql mysql-connector-java 8.0.23 ``` 4. 引入 Lombok(可选),IDEA中需要安装 Lombok插件 ```xml org.projectlombok lombok 1.18.18 ``` ### 2.3 配置数据库信息 在 `application.properties` 配置文件中添加Mysql数据库的相关配置 Spring Boot 2.0 以下版本内置 jdbc 5 驱动 ```properties spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-plus?characterEncoding=utf-8&useSSL=false spring.datasource.username=root spring.datasource.password=123456 ``` Spring Boot 2.1 以上版本内置 jdbc 8 驱动 ```properties spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-plus?characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=123456 ``` ### 2.4 创建实体类 创建 `com.example.entity.User`实体类 ```java package com.example.entity; import lombok.Data; /** * @Author 孔德成 * @Slogan 致敬大师,致敬未来的你 */ @Data public class User { private Long id; private String name; private Integer age; private String email; } ``` ### 2.5 创建 Mapper映射文件 1. 创建 `com.example.mapper.UserMapper` 2. 继承 `BaseMapper` - 这是 Mybatis-Plus 特性,该接口定义了很多常用方法 3. 传入实体类类型 ```java package com.example.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.entity.User; /** * @Author 孔德成 * @Slogan 致敬大师,致敬未来的你 */ public interface UserMapper extends BaseMapper { } ``` 4. 在启动类上添加 `@MapperScan` 注解,配置Mapper路径 - Mapper接口实现类对象是动态生成的,启动类默认找不到,需要配置路径 - `@MapperScan("com.example.mapper")` ### 2.6. 测试 ```java package com.example; import com.example.entity.User; import com.example.mapper.UserMapper; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class DemoMpApplicationTests { // 注入 UserMapper @Autowired UserMapper userMapper; @Test void findAll() { List users = userMapper.selectList(null); System.out.println(users); } } ``` > 注意:这里 userMapper idea会报错,但是程序可以正常运行,可以给 UserMapper 添加一个 @Repository 注解解决该问题 ### 2.7 查看 sql 输出日志 在 配置文件中配置 : `mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl` ## 3. Mybatis-Plus CRUD ### 3.1 添加 ```java void testAdd() { User user = new User(); user.setName("孔德成"); user.setAge(11); user.setEmail("iamliuche@163.com"); // 返回值为受影响行数 int insert = userMapper.insert(user); } ``` 结果: ![image-20210518111221881](/Users/codemao/Documents/笔记/Mybatis-Plus 入门.assets/image-20210518111221881.png) ### 3.2 MP主键策略 | 类型 | 说明 | | ------------- | ---------------------- | | **ASSIGN_ID** | 长度为19位的唯一值 | | **AUTO** | 自动增长 | | NONE | 没有策略,需要手动设置 | | INPUT | 需要手动设置 ID | | ASSIGN_UUID | UUID,唯一值 | #### 3.2.1 ASSIGN_ID Mybatis-Plus 默认的主键策略是 ASSIGN_ID(使用了雪花算法)。 **雪花算法:分布式ID生成器**。它是由 Twitter 公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。 ![image-20210518112051182](/Users/codemao/Documents/笔记/Mybatis-Plus 入门.assets/image-20210518112051182.png) #### 3.2.2 AUTO 自增策略 ```java @TableId(type = IdType.AUTO) private Long id; ``` 要是想影响所有实体的配置,可以设置全局主键配置 ```properties mybatis-plus.global-config.db-config.id-type=auto ``` ### 3.3 更新 ```java @Test void testUpdate() { User user = new User(); user.setName("Decheng Kong"); user.setId(1394490488815190018L); int count = userMapper.updateById(user); } ``` ### 3.4 自动填充 项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用 Mybatis-Plus 的自动填充功能,完成这些字段的赋值工作。 #### 3.4.1 准备工作 1. 在 user 表中添加 datetime 类型的新字段 create_time、update_time 2. 在表对应实体类中添加对应属性 ```java private Date createTime; private Date updateTime; ``` 3. 在属性上添加自动填充注解 ```java // insert的时候自动填充该属性 @TableField(fill = FieldFill.INSERT) private Date createTime; // insert 和 update 的时候都自动填充该属性 @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; ``` 4. 创建一个类实现`MetaObjectHandler`接口,实现两个方法,一个方法添加时执行,一个方法修改时执行,在方法里设置要添加什么值 ```java @Component public class MyMetaObjectHandler implements MetaObjectHandler { // MP 执行添加操作的时候执行 @Override public void insertFill(MetaObject metaObject) { this.setFieldValByName("createTime", new Date(), metaObject); this.setFieldValByName("updateTime", new Date(), metaObject); } // MP 执行更新操作,这个方法执行 @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime", new Date(), metaObject); } } ``` 5. 测试 ```java @Test void testInsertAndUpdate() { // insert // User user = new User(); // user.setName("uzi"); // user.setEmail("xxx@email.com"); // user.setAge(24); // userMapper.insert(user); // update User user = new User(); user.setId(1394515905960263681L); user.setName("uzi-RNG"); userMapper.updateById(user); } ``` ### 3.5 乐观锁 **乐观锁是解决丢失更新问题的一种解决方案。** 当多人同时操作同一条数据时,可能出现该种问题。比如**抢票**: ![image-20210518132318936](/Users/codemao/Documents/笔记/Mybatis-Plus 入门.assets/image-20210518132318936.png) **应用场景:** 当要更新一条记录时,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。 **实现方式:** 1. 取出记录时获取当前 version 2. 更新时,带上这个 version 3. 执行更新时,`set version = new Version where version=oldVersion`,如果 version 不对,就更新失败 ![image-20210518133209701](/Users/codemao/Documents/笔记/Mybatis-Plus 入门.assets/image-20210518133209701.png) ### 3.6 MP实现乐观锁 #### 3.6.1 修改表结构和实体类 在表里添加 `version` 字段。 修改实体类,添加 `@Version`注解 在 version 属性上。 ```java @Version private Integer version; ``` #### 3.6.2 创建配置文件 创建 `config` 包,创建文件 `MPConfig.java` 此时可以删除主类中的 `@MapperScan` 扫描注解 ```java package com.example.config; @Configuration @MapperScan("com.example.mapper") public class MPConfig { } ``` #### 3.6.3 注册乐观锁插件 ```java @Configuration @MapperScan("com.example.mapper") public class MPConfig { /** * 配置乐观锁插件 * * @return */ @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } } ``` 配置 version 属性自动填充 ```java @Version @TableField(fill = FieldFill.INSERT) private Integer version; ``` ```java this.setFieldValByName("version", 1, metaObject); ``` #### 3.6.4 测试 先添加一条记录,初始化版本号 ```java @Test void testAdd() { User user = new User(); user.setName("王五"); user.setAge(11); user.setEmail("iamliuche@163.com"); // 返回值为受影响行数 int insert = userMapper.insert(user); } ``` 执行更新操作,观察版本号是否自动增长 ```java @Test void testOptimisticLocker() { User user = userMapper.selectById(1394535763972243457L); user.setName("张三"); int count = userMapper.updateById(user); } ``` ### 3.7 查询 #### 3.7.1 通过多个 id 批量查询 ```java /** * 根据多个 id 批量查询 */ @Test void testSelect1() { List users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3)); System.out.println(users); } ``` #### 3.7.2 简单的条件查询 ```java /** * 简单条件查询 */ @Test void testSelect2() { HashMap columnMap = new HashMap<>(); columnMap.put("name", "Jone"); columnMap.put("age", 18); List users = userMapper.selectByMap(columnMap); System.out.println(users); } ``` #### 3.7.3 分页查询 1. 在`MPConfig.java` 配置文件中配置分页插件 ```java /** * 配置分页插件 * * @return */ @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } ``` 2. 编写分页代码 - 创建 `Page`对象,传入两个参数`当前页`和`每页记录数` - 调用 MP 的方法实现分页 - ```java /** * 测试分页查询 */ @Test void testSelect3() { Page userPage = new Page<>(1, 3); userMapper.selectPage(userPage, null); // 总页数 long pages = userPage.getPages(); // 当前页 long current = userPage.getCurrent(); // 数据集合 List records = userPage.getRecords(); // 总记录数 long total = userPage.getTotal(); // 当前页是否有下一页 boolean hasNext = userPage.hasNext(); // 当前页是否有上一页 boolean hasPrevious = userPage.hasPrevious(); System.out.println(pages); System.out.println(current); System.out.println(records); System.out.println(total); System.out.println(hasNext); System.out.println(hasPrevious); } ``` #### ### 3.8 删除 #### 3.8.1 根据 id 删除记录 ```java @Test void testDelete1() { int i = userMapper.deleteById(1L); } ``` #### 3.8.2 根据 id 批量删除 ```java @Test void testDelete2() { int i = userMapper.deleteBatchIds(Arrays.asList(2, 3, 4)); } ``` #### 3.8.3 根据简单条件删除 ```java @Test void testDelete3() { HashMap columnMap = new HashMap<>(); columnMap.put("name","Billie"); columnMap.put("age","24"); int i = userMapper.deleteByMap(columnMap); } ``` ### 3.9 逻辑删除 #### 3.9.1 物理删除和逻辑删除 1. **物理删除:真实删除**,将对应数据从数据库中删除,之后查询不到此条被删除数据 2. **逻辑删除:假删除**,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录。 #### 3.9.2 逻辑删除实现流程 1. 数据库修改:添加 `deleted` 字段,设置默认值为 `false` ```mysql ALTER TABLE `user` ADD COLUMN `deleted` boolean DEFAULT FALSE; ``` 2. 实体类修改:添加 `deleted` 属性,并加上 `@TableLogic` 注解 ```java @TableLogic private Integer deleted; ``` 3. 配置(可选) ```properties # 删除用1表示,不删除用0表示,此为默认值 mybatis-plus.global-config.db-config.logic-delete-field=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0 ``` 4. 测试 ```java @Test void testDelete4() { int i = userMapper.deleteById(1394490488815190018L); } ``` > 可以发现,数据并没有被删除,只是 deleted 字段更新为了1,只有 deleted 字段为0的记录才能执行删除方法。查询的时候也会过滤掉 deleted=0的记录 ### 4. 条件构造器和常用接口 #### 4.1 Wrapper 用来构造复杂查询条件,传入查询语句中,可完成复杂查询 ```java QueryWrapper objetQueryWrapper = new QueryWrapper<>(); ``` #### 4.2 ge,gt,le,lt ```java @Test void testSelect4() { // 创建条件构造器 Wrapper, 常用 QueryWrapper QueryWrapper userQueryWrapper = new QueryWrapper<>(); // ge gt le lt // 1. 查询年龄大于等于20岁的用户 userQueryWrapper.ge("age", 20); List users = userMapper.selectList(userQueryWrapper); System.out.println(users); } ``` #### 4.3 eq,ne ```java @Test void testSelect5() { QueryWrapper userQueryWrapper = new QueryWrapper<>(); // eq ne // 2. 查询 name=Tom 的用户 userQueryWrapper.eq("name", "Tom"); List users = userMapper.selectList(userQueryWrapper); System.out.println(users); } ``` #### 4.4 between, notBetween 包含左右边界 ```java @Test void testSelect6() { QueryWrapper userQueryWrapper = new QueryWrapper<>(); // eq ne // 3. 查询 年龄在 24-28 之间的用户 userQueryWrapper.between("age", 24, 28); List users = userMapper.selectList(userQueryWrapper); System.out.println(users); } ``` #### 4.5 like,notLike,likeLeft,likeRight **selectMaps** 返回Map集合列表,通常配合 select() 使用 ```java @Test void testSelect7() { QueryWrapper userQueryWrapper = new QueryWrapper<>(); // like,notLike,likeLeft,likeRight // left 和 right 表示 % 在左边还是右边 // 4. 查询姓名中有张的用户 %张% userQueryWrapper.like("name", "张"); List> maps = userMapper.selectMaps(userQueryWrapper); maps.forEach(System.out::println); } ``` #### 4.6 orderBy、orderByDesc、orderByAsc ```java @Test void testSelect8() { QueryWrapper userQueryWrapper = new QueryWrapper<>(); // orderBy,orderByDesc, orderByAsc // 5. 根据 id 作降序排列 userQueryWrapper.orderByDesc("id"); List users = userMapper.selectList(userQueryWrapper); System.out.println(users); } ```