Mybatis-Plus(一)入门篇

224 阅读10分钟

中文官网:MyBatis-Plus(简称 MP)是一个 MyBatis(opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

快速入门

以下内容都是基于官方文档而写

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

入门案例

学习前提

  • 熟悉SpringBoot
  • 熟悉Maven
  • 熟悉Mybatis
  • 了解常用SQL语句

下面有一张表,我们需要对其进行增删改查的操作,如果是Mybatis我们需要写xxxMapper.xml和SQL语句,但是MP则提供了大量通用的方法让我们提高开发的效率

idnameageemailcreate-
1Jone18test@hc.com2022-09-22 15:20:57
2Jack20test@hc.com2022-09-22 15:20:57
3Tom21test@hc.com2022-09-22 15:20:57
4Sandy22test@hc.com2022-09-22 15:20:57
5Billie23test@hc.com2022-09-22 15:20:57

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `age` int(0) NULL DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1571790158700826625 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user` VALUES (1, 'Jone', 18, 'test1@baomidou.com', '2022-09-22 15:20:57');
INSERT INTO `user` VALUES (2, 'Jack', 20, 'test2@baomidou.com', '2022-09-22 15:20:59');
INSERT INTO `user` VALUES (3, 'Tom', 28, 'test3@baomidou.com', '2022-09-22 15:21:01');
INSERT INTO `user` VALUES (4, 'Sandy', 21, 'test4@baomidou.com', '2022-09-22 15:21:03');
INSERT INTO `user` VALUES (5, 'Billie', 24, 'test5@baomidou.com\r\n ', '2022-09-22 15:21:05');
INSERT INTO `user` VALUES (6, '王五', 12, 'test@hc.com', '2022-09-22 15:21:08');
INSERT INTO `user` VALUES (7, '赵六', 12, 'test@hc.com', '2022-09-22 15:21:11');

引入依赖

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</dependency>

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.5.2</version>
</dependency>

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

增加配置

老生常谈的数据库账号,密码,MySQL的驱动,地址,这些MP都是需要的

spring:
  datasource:
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis-plus?characterEncoding=UTF-8&serverTimezone=CTT&useSSL=false

编写实体类

和数据库表一一对应

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user")
public class User {
    @TableId(value = "id")
    private Long id;

    @TableField(value = "name")
    private String name;

    @TableField(value = "age")
    private Integer age;

    @TableField(value = "email")
    private String email;

    @TableField(value = "create_time")
    private LocalDateTime createTime;
}

编写Mapper

这里只要继承MP官方提供的BaseMapper,并且是组件即可

@Mapper
    public interface UserMapper extends BaseMapper<User> {

    }

测试

@Autowired
    private UserMapper userMapper;

@Test
    public void testSelect() {
    List<User> userList = userMapper.selectList(null);
    userList.forEach(System.out::println);
}

结果

User(id=1, name=张三, age=12, email=hc@demo.com, createTime=2022-09-23T17:08:12)
User(id=5, name=Billie, age=24, email=test5@baomidou.com
 , createTime=2022-09-22T15:21:05)

是不是非常简单,甚至连sql都不用写,接下来我们了解一下MP常用的注解

常用注解

@TableName

描述:表名注解,标识实体类对应的表,和数据库表一一对应

示例:@TableName(value = "user")

使用位置:实体类

@TableId

描述:主键注解,标识主键

示例:@TableId(value = "id")

使用位置:实体类的主键字段

IdType

描述:指定主键类型

示例:@TableId(value = "id", type = IdType.INPUT)

详情:上述的 type = IdType.INPUT表明当我们在插入数据库数据的使用需要 set 主键值,如果是 IdType.AUTO则表明数据库 ID 自增,我们在插入的可以不 set 主键值,当然可以分配UUID等等

@TableField

描述:字段注解(非主键),标识非主键字段

示例:@TableField(value = "email")

使用位置:实体类的非主键字段

@OrderBy

描述:内置 SQL 默认指定排序,优先级低于 wrapper 条件查询

使用位置:实体类的字段上

增删改查

Mapper CRUD接口

  • 通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
  • 泛型 T 为任意实体对象,和数据库表一一对应
  • 参数 Serializable 为任意类型主键 ,Mybatis-Plus 不推荐使用复合主键,约定每一张表都有自己的唯一 id 主键
  • 对象 Wrapper 为 条件构造器,达到where,orderby,groupby等等作用

这里不全部列举,需要使用时查看官方文档即可

首先需要让一个接口继承BaseMapper

// 插入一条记录

int insert(T entity);

 @Test
    public void testMapperInsert() {
        User user = new User(8L, "张三", 12, "hc@demo.com", LocalDateTime.now());
        int insert = userMapper.insert(user);
        System.out.println(insert);
    }

返回值1表示插入成功

// 根据 entity 条件,删除记录

int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

@Test
    public void testMapperDelete() {
    QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
    userQueryWrapper.eq("name", "王五");
    //        删除的条数
    int delete = userMapper.delete(userQueryWrapper);
    System.out.println(delete);
}

这里的 userQueryWrapper.eq("name", "王五")相当于where name='王五'

返回的值表示的是删除的条数


// 删除(根据ID 批量删除)

int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    @Test
    public void testMapperDeleteBatchIds() {
        ArrayList<Long> longs = new ArrayList<>();
        longs.add(3L);
        longs.add(4L);
        //        删除的条数
        int i = userMapper.deleteBatchIds(longs);
        System.out.println(i);
    }

会删除主键为3,4的数据,返回的值表示的是删除的条数


// 根据 columnMap 条件,删除记录

int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    @Test
    public void testMapperDeleteByMap() {
        HashMap<String, Object> stringStringHashMap = new HashMap<>();
        stringStringHashMap.put("name", "Jack");
        int i = userMapper.deleteByMap(stringStringHashMap);
        System.out.println(i);
    }

这里会删除name为Jack的数据,使用起来感觉比较鸡肋,用wrapper用比较方便

返回的值依旧是被删除的条数

// 根据 whereWrapper 条件,更新记录

int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);

    @Test
    public void testMapperUpdate() {
        User user = new User(8L, "张三", 12, "hc@demo.com", LocalDateTime.now());
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name", "Jone");
        int update = userMapper.update(user, userQueryWrapper);
        System.out.println(update);
    }

sql修改语句就是update...set...where...,where的作用就是找到你需要修改的数据,set就是将字段修改成你指定的值。所以这个方法的第一个参数作用相当于set参数类型就是与数据对应的实体类,第二个参数作用相当于where

返回的参数是被修改的数据数

// 根据 entity 条件,查询一条记录

T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    @Test
    public void testMapperSelectOne() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name", "张三");
        User user = userMapper.selectOne(userQueryWrapper);
        System.out.println(user);
    }

根据wrapper的条件查询单条数据,如果查询到的是多条数据则返回第一条


// 查询(根据ID 批量查询)

List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    @Test
    public void testMapperSelectBatchIds() {
        List<String> query = new ArrayList<>();
        query.add("1");
        query.add("5");
        query.add("7");
        List<User> users = userMapper.selectBatchIds(query);
        System.out.println(users);
    }

// 查询(根据 columnMap 条件)

List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    @Test
    public void testMapperSelectMaps() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.gt("age", 10);
        List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
        System.out.println(maps.get(0).get("create_time"));
    }

用起来感觉挺鸡肋的,而且返回的是List<Map>,不如List<User>方便


// 根据 Wrapper 条件,查询全部记录。

List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

  @Test
    public void testMapperSelectObjs() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.gt("age", 10);
        List<Object> objects = userMapper.selectObjs(userQueryWrapper);
        System.out.println(objects.get(0));
    }

注意: 只返回第一个字段的值,基本就是主键的值,假如说3条数据满足条件则返回三条数据的id


// 根据 entity 条件,查询全部记录(并翻页)

IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

  @Test
    public void testMapperSelectPage() {
        Page page = new Page(0, 2);
        Page page1 = userMapper.selectMapsPage(page, null);
        System.out.println(page1.getRecords());
    }

第一个参数是和分页相关的一些参数,比如说查询的当前页,页大小,总页数,总条数,页数据等等,第二个参数和条件查询相关,null则表示查询全部

new Page(0, 2)表示的是第0页,每页两条数据,返回结果也是page,而查询的参数则存在了page的records中

Service CRUD接口

  • 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
  • 对象 Wrapper 为 条件构造器

这里需要注意的是,MP提供了一个IService接口,其中提供了大量的curd接口,并且MP也提供了实现了IService的一个实现类ServiceImpl,我们可以简单看一下源码

public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
    protected Log log = LogFactory.getLog(this.getClass());
    @Autowired
    protected M baseMapper;
    protected Class<T> entityClass = this.currentModelClass();
    protected Class<M> mapperClass = this.currentMapperClass();

    public ServiceImpl() {
    }

    public M getBaseMapper() {
        return this.baseMapper;
    }
    //...
     public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
        return throwEx ? this.baseMapper.selectOne(queryWrapper) : SqlHelper.getObject(this.log, this.baseMapper.selectList(queryWrapper));
    }
    //...
}

从M extends BaseMapper和this.baseMapper.selectOne(queryWrapper)就印证了第一条,Service接口进一步的封装了CRUD接口。所以如果我们要使用,我们可以这样做

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> {

}

我们简单测试一下

    @Autowired
    private UserServiceImpl userService;
	
 	@Test
    public void testService() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name", "Billie");
        User user = userService.getOne(userQueryWrapper);
        System.out.println(user);
    }
    //User(name=Billie, id=5, age=24, email=test5@baomidou.com
    // , createTime=2022-09-22T15:21:05)

但是呢这样写总是感觉哪里怪怪的,测试的时候直接依赖注入的是实现类,我们最好面向接口编程,这时候就需要创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类,然后修改下Service的实现类

     public interface UserService extends IService<User> {
	}
	@Service
	public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

	}

测试

	@Autowired
    private UserService userService;

	@Test
    public void testService() {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name", "Billie");
        User user = userService.getOne(userQueryWrapper);
        System.out.println(user);
    }

Save

// 插入一条记录(选择字段,策略插入)

boolean save(T entity);

    @Test
    public void testServiceSave() {
        User user = new User();
        user.setAge(12);
        user.setEmail("123@qq.com");
        user.setId(12l);
        user.setCreateTime(LocalDateTime.now());
        user.setName("赵六");
        boolean save = userService.save(user);
        System.out.println(save);
    }

// 插入(批量)

boolean saveBatch(Collection entityList, int batchSize);

    @Test
    public void testServiceSaveBatch() {
        User user1 = new User("孙七", 18l, 12, "123@qq.com", LocalDateTime.now());
        User user2 = new User("周八", 19l, 12, "123@qq.com", LocalDateTime.now());
        User user3 = new User("刘一", 20l, 12, "123@qq.com", LocalDateTime.now());
        User user4 = new User("陈二", 21l, 12, "123@qq.com", LocalDateTime.now());
        List<User> list = new ArrayList<>();
        list.add(user1);
        list.add(user2);
        list.add(user3);
        list.add(user4);
        boolean reuslt = userService.saveBatch(list, 2);
        System.out.println(reuslt);
    }

注意:第二个参数是每次批量导入的条数

SaveOrUpdate

// TableId 注解存在更新记录,否插入一条记录

boolean saveOrUpdate(T entity);

  @Test
    public void testServiceSaveOrUpdate() {
        User user1 = new User("孙七111", 14l, 12, "123@qq.com111", null);
        boolean saveOrUpdate = userService.saveOrUpdate(user1);
        System.out.println(saveOrUpdate);
    }

先根据主键去查询,如果存在则更新否则插入

Remove

// 根据 entity 条件,删除记录

boolean remove(Wrapper queryWrapper);

 @Test
    public void testServiceRemove() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "张三");
        boolean remove = userService.remove(queryWrapper);
        System.out.println(remove);
    }

// 根据 columnMap 条件,删除记录

boolean removeByMap(Map<String, Object> columnMap);

 @Test
    public void testServiceRemoveByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "Billie11");
        boolean remove = userService.removeByMap(map);
        System.out.println(remove);
    }

注意:如果没有查询到则返回false

Update

// 根据 whereWrapper 条件,更新记录

boolean update(T updateEntity, Wrapper whereWrapper);

  @Test
    public void testServiceUpdate() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "刘一");
        User user = new User();
        user.setName("刘一11");
        boolean update = userService.update(user, queryWrapper);
        System.out.println(update);
    }

Get

// 根据 Wrapper,查询一条记录

T getOne(Wrapper queryWrapper, boolean throwEx);

 @Test
    public void testServiceGetOne() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "陈二");
        User one = userService.getOne(queryWrapper, false);
        System.out.println(one);
    }

注意:第二个参数表示的是如果查询到多个符合的数据是否会报错


// 根据 Wrapper,查询一条记录

V getObj(Wrapper queryWrapper, Function<? super Object, V> mapper);

  @Test
    public void testServiceGetObj() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "陈二");
        Object obj = userService.getObj(queryWrapper, (item) -> item);
        System.out.println(obj.toString());
    }

第二个参数是一个函数式接口,需要我们传入一个function,可以让我们对结果参数做指定的操作,比如拼接另一个字符(item)->item+"str"等等

List

// 查询所有列表

List<Map<String, Object>> listMaps();

 @Test
    public void testServiceListMaps() {
        List<Map<String, Object>> maps = userService.listMaps();
        System.out.println(maps);
    }

返回的是多个map而不是多个对象


// 查询全部记录

List<Object> listObjs();

  @Test
    public void testServiceListObjs() {
        List<Object> objects = userService.listObjs();
        System.out.println(objects);
    }

返回多条数据的主键


// 根据 Wrapper 条件,查询全部记录

List listObjs(Wrapper queryWrapper, Function<? super Object, V> mapper);

 @Test
    public void testServiceListObjsWrapper() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "陈二");
        List<Long> objects = userService.listObjs(queryWrapper, (item) -> (Long) item);
        System.out.println(objects);
    }

返回多条数据的主键,并且可以做自定义操作

Page

// 无条件分页查询

IPage page(IPage page);

  @Test
    public void testServicePage() {
        Page<User> userPage = new Page<>(1, 3);
        Page<User> page = userService.page(userPage);
        System.out.println(page.getRecords());
    }

返回page对象,其中包含着和分页相关的所有数据,record则是分页数据


// 无条件分页查询

IPage<Map<String, Object>> pageMaps(IPage page);

  @Test
    public void testServicePageMaps() {
        Page<Map<String, Object>> userPage = new Page<>(1, 3);
        Page<Map<String, Object>> mapPage = userService.pageMaps(userPage);
        System.out.println(mapPage.getRecords());
    }

record中则是多个map组成的list,每个map键值对都是字段:值

Count

// 查询总记录数

int count();

 @Test
    public void testServiceCount() {
        long count = userService.count();
        System.out.println(count);
    }

也可以按照条件统计总数

Chain

// 链式查询 QueryChainWrapper query();

// 链式更改 UpdateChainWrapper update();

	//查询name="陈二"的所有数据
    @Test 
    public void testServiceChainQuery() {
        List<User> list = userService.query().eq("name", "陈二").list();
        System.out.println(list);
    }
    //删除name="陈二"的所有数据
    @Test
    public void testServiceChainUpdate() {
        boolean remove = userService.update().eq("name", "陈二").remove();
        System.out.println(remove);
    }

MP也支持链式调用

由于作者能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!