中文官网: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则提供了大量通用的方法让我们提高开发的效率
| id | name | age | create- | |
|---|---|---|---|---|
| 1 | Jone | 18 | test@hc.com | 2022-09-22 15:20:57 |
| 2 | Jack | 20 | test@hc.com | 2022-09-22 15:20:57 |
| 3 | Tom | 21 | test@hc.com | 2022-09-22 15:20:57 |
| 4 | Sandy | 22 | test@hc.com | 2022-09-22 15:20:57 |
| 5 | Billie | 23 | test@hc.com | 2022-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也支持链式调用
由于作者能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!