Mybatis-Plus使用与配置

本测试项目涵盖了Mybatis-Plus框架的入门快速构建以及基本的CRUD相关的操作说明。望可以帮助到有相关需求的伙伴。
测试代码Github地址:https://github.com/yaokuku123/mybatis-demo

1 Mybatis-Plus快速搭建

本项目测试需要基于SpringBoot框架完成。

1.1 创建并初始化数据库

  • 创建mybatis_plus数据库,并添加user表
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)
);
  • 向user表中添加初始的字段用于测试
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');

1.2 初始化Maven工程,并引入依赖

注意:引入 MyBatis-Plus 无需再引入 MyBatis 以及 MyBatis-Spring,以避免因版本差异导致的问题。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <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>
    <!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
</dependencies>

1.3 主配置类中的配置

#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

#mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

1.4 编写代码

  • 主启动类

注意:在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹。

package com.yqj;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.yqj.mapper")
public class MybatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class,args);
    }
}

  • 实体类
package com.yqj.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

  • mapper

注意:让UserMapper接口继承mybatisplus中的BaseMapper<User>接口

package com.yqj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yqj.entity.User;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

1.5 测试

package com.yqj;

import com.yqj.entity.User;
import com.yqj.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList(){
        List<User> users = userMapper.selectList(null);
        for (User user : users) {
            System.out.println(user);
        }
    }
}

输出:说明成功实现基于Mybatis-Plus的快速入门操作

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

2. Mybatis-Plus的CRUD相关操作

基于之前搭建的快速搭建的程序测试CRUD的相关操作

2.1 insert

  • 插入操作

注意:数据库插入id值默认为:ID_WORKER 全局唯一id

//插入用户数据
@Test
public void testInsert(){
    User user = new User();
    user.setName("yorick");
    user.setAge(18);
    user.setEmail("yorick@qq.com");
    int result = userMapper.insert(user);
    System.out.println(result);
}
  • 主键生成策略
1.IdType.AUTO 主键自增策略
2.IdType.ID_WORKER 若主键为数值型,Mybatis-Plus默认使用的策略
3.IdType.ID_WORKER_STR 若主键为字符串型,Mybatis-Plus默认使用的策略
4.IdType.INPUT 主键id需要手动输入
5.IdType.NONE 默认跟随全局策略
6.IdType.UUID 生成UUID

在实体类的主键上增加主键生成策略的字段注解即可

@TableId(type = IdType.AUTO)
private Long id;

2.2 update

  • 根据id更新
//更新用户数据
@Test
public void testUpdate(){
    User user = userMapper.selectById(1L);
    user.setName("alice");
    int result = userMapper.updateById(user);
    System.out.println(result);
}
  • 自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作。

测试:

  1. 在数据库中添加两个datetime类型的新字段 create_time、update_time
ALTER TABLE `user` ADD COLUMN `create_time` datetime
ALTER TABLE `user` ADD COLUMN `update_time` datetime
  1. 在对应实体类的属性上添加注解
//在插入时自动填充
@TableField(fill = FieldFill.INSERT)
private Date createTime;
//在插入和更新时自动填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
  1. 实现元对象处理器接口
package com.yqj.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    //设置执行插入时,自动填充的内容
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    //设置执行更新时,自动填充的内容
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

  1. 测试
//插入用户数据
@Test
public void testInsert(){
    User user = new User();
    user.setName("yorick2");
    user.setAge(19);
    user.setEmail("yorick2@qq.com");
    int result = userMapper.insert(user);
    System.out.println(result);
}
  • 乐观锁

当要更新一条记录的时候,希望这条记录没有被别人更新,也就是说实现线程安全的数据更新。

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

测试:

  1. 数据库添加version字段
ALTER TABLE `user` ADD COLUMN `version` INT
  1. 实体类的对应属性上添加注解
@Version
@TableField(fill = FieldFill.INSERT) //设置插入时自动填充
private Integer version;
  1. 元对象处理器接口添加version的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
    ......
    this.setFieldValByName("version", 1, metaObject);
}
  1. 注册乐观锁的配置插件

另外,将原来标注在主启动类上的 @MapperScan 注解转移到配置类上面

package com.yqj.config;

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.yqj.mapper")
public class MybatisPlusConfig {
    
    //乐观锁插件
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
}

  1. 测试

将id为1的记录中version字段改为1,然后执行测试发现结果为2,说明乐观锁配置成功。

//更新用户数据
@Test
public void testUpdate(){
    User user = userMapper.selectById(1L);
    user.setName("alice2");
    int result = userMapper.updateById(user);
    System.out.println(result);
}

2.3 select

  • 根据id查询记录
//根据id查询
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}
  • 多个id批量查询
//多个id批量查询
@Test
public void testSelectBatchIds(){
    List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
    for (User user : users) {
        System.out.println(user);
    }
}
  • map封装的条件查询

注意:map中的key对应的是数据库中的列名。例如数据库user_id,实体类是userId,这时map的key需要填写user_id

//map封装的条件查询
@Test
public void testSelctByMap(){
    Map<String,Object> map = new HashMap<>();
    map.put("name","yorick");
    map.put("age",18);
    List<User> users = userMapper.selectByMap(map);
    for (User user : users) {
        System.out.println(user);
    }
}
  • 分页查询

最终通过page对象获取相关数据

测试:

  1. 在配置类中添加分页插件
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
}
  1. 测试selectPage分页
//分页查询
@Test
public void testSelectPage(){
    Page<User> page = new Page<>(1, 3); 
    userMapper.selectPage(page, null);
    page.getRecords().forEach(System.out::println); //当前页的数据
    System.out.println(page.getCurrent()); //当前页
    System.out.println(page.getPages()); //总页数
    System.out.println(page.getSize()); //每页显示的数据条目
    System.out.println(page.getTotal()); //总共的数据条目
    System.out.println(page.hasNext()); //是否有下一页
    System.out.println(page.hasPrevious()); //是否有上一页
}

2.4 delete

物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除数据

逻辑删除:假删除,将对应数据中代表是否被删除字段状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

  • 根据id删除
//根据id删除
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(5L);
    System.out.println(result);
}
  • 根据id批量删除 deleteBatchIds()

  • 根据map删除 deleteByMap()

  • 逻辑删除

测试:

  1. 数据库中添加 deleted字段,用于表示逻辑删除
ALTER TABLE `user` ADD COLUMN `deleted` boolean
  1. 实体类的对应属性上面添加逻辑删除的注解
@TableLogic
@TableField(fill = FieldFill.INSERT) //插入时自动填充
private Integer deleted;
  1. 元对象处理器接口添加deleted的insert默认值
@Override
public void insertFill(MetaObject metaObject) {
    ......
    this.setFieldValByName("deleted", 0, metaObject);
}
  1. 在配置类中注册逻辑删除的插件
//逻辑删除
@Bean
public ISqlInjector iSqlInjector(){
    return new LogicSqlInjector();
}
  1. 测试逻辑删除

将id为4的记录中deleted字段改为0,然后执行测试发现其变为1,说明逻辑删除成功。

  • 测试后发现,数据并没有被删除,deleted字段的值由0变成了1
  • 测试后分析打印的sql语句,是一条update
  • MyBatis Plus中查询操作也会自动添加逻辑删除字段的判断
  • 注意:被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
//根据id删除
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(4L);
    System.out.println(result);
}

2.5 性能分析

性能分析拦截器,用于输出每条 SQL 语句及其执行时间

SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题

测试:

  1. 在配置类中添加性能分析的插件
//性能分析
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
    //参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题
    //参数:format: SQL是否格式化,默认false
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(500);//ms,超过此处设置的ms则sql不执行
    performanceInterceptor.setFormat(true);
    return performanceInterceptor;
}
  1. SpringBoot中设置为dev开发环境
#环境设置:dev、test、prod
spring.profiles.active=dev
  1. 测试
//插入用户数据
@Test
public void testInsert(){
    User user = new User();
    user.setName("yorick");
    user.setAge(20);
    user.setEmail("yorick@qq.com");
    int result = userMapper.insert(user);
    System.out.println(result);
}

结果:显示出插入的性能为4ms

Time:4 ms - ID:com.yqj.mapper.UserMapper.insert
Execute SQL:
    INSERT 
    INTO
        user
        ( id, name, age, email, create_time, update_time, version, deleted ) 
    VALUES
        ( 1298135534432567298, 'yorick', 20, 'yorick@qq.com', '2020-08-25 13:50:04', '2020-08-25 13:50:04', 1, 0 )

2.6 复杂查询

//复杂查询
@Test
public void testComplexSelect(){
    QueryWrapper<User> wrapper = new QueryWrapper<>();

    //ge,gt,le,lt,isNull,isNotNull
    //查询年龄不小于20岁的用户
    //wrapper.ge("age",20);

    //eq,ne
    //查询姓名为yorick的用户
    //wrapper.eq("name","yorick");

    //between,notBetween
    //查询年龄在20~30之间的用户,包含20和30边界
    //wrapper.between("age",20,30);

    //like
    //查询姓名包含yorick的用户
    //wrapper.like("name","yorick");

    //orderByDesc,orderByAsc
    //按id降序排序
    //wrapper.orderByDesc("id");

    //last 直接拼接sql到最后,有sql注入风险
    //wrapper.last("limit 1");

    //指定要查找的列
    wrapper.select("id","name");


    List<User> users = userMapper.selectList(wrapper);
    for (User user : users) {
        System.out.println(user);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350