前言
在很长的时间的项目中,一直在用mybatis-plus操作数据库。感觉mybatis-plus可以作为mybatis的替代品,陪我在数据库的江湖上浪迹天涯。这里,根据日常使用经验,将Mybatis-plus的一些日常操作,以及踩到的坑,整理成博客。方便自己日后项目开发中查阅,也分享给需要的同事和小伙伴。
Mybatis-plus是Mybatis 的增强工具,只做增强不做改变,所以任何能使用mybatis进行crud,并且能支持sql标准的数据库,都可以通过mybatis-plus进行连接。此外,源代码中包含中文注释,可以方便英文理解。
特点
- 低侵入性:由于它只是对Mybatis进行增强,不做改变,所以通过它来替换Mybatis不会对当前项目产生任何影响;
- 损耗小: 项目启动时会自动注入CRUD,性能基本无损耗,面向对象操作;
- 内置通用Mapper: 在Mybatis-plus中,提供通用Mapper接口(BaseMapper<T>)、通用service,可以解决大部分单表操作的CRUD,并且支持强大的条件构造器;
- 支持Lambda表达式调用: 通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键策略: 大部分数据库可以支持四种主键操作,并且包含分布式唯一ID生成器(Sequence),可以自由配置,帮助开发者解决主键生成问题;
- 支持ActiveMode模式: 实体类通过继承Model类,就可以通过实体类来完成大多数操作;
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
上手准备
开发环境: IDEA2020.03
开发框架: SpringBoot 2.3.2.RELEASE
数据库: Oracle 11G
Jar包引入
两种方式:
方式一:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.1</version> </dependency> |
方式二:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>3.4.1</version> </dependency> |
解释:方式一包含于方式二;也就是说,如果通过方式一的方式引入jar包,它会自动帮你引入方式二中的jar包;如图所示:
其他jar包引入:
<!--oracle连接方式--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> <!--Druid 连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!—lombok 简化实体类的开发工作--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.16</version> <scope>provided</scope> </dependency> <!—HuTool 提供工具类--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.5.7</version> </dependency> |
创建表
CREATE TABLE PUB_USERS (ID NUMBER(23,0) NOT NULL ENABLE, CODE VARCHAR2(50), NAME VARCHAR2(50), PWD VARCHAR2(50), ADMIN_PWD VARCHAR2(50), CONSTRAINT ID_PRIMARY PRIMARY KEY (ID) ) |
插入数据:
INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(1, '111111', '李元芳', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(2, '111112', '狄仁杰', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(3, '111113', '诸葛亮', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(4, '111114', '孙思凯', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(5, '111115', '蒙犽', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(6, '111116', '武后主', '1', '000'); INSERT INTO WNSALARYDB.PUB_USERS (ID, CODE, NAME, PWD, ADMIN_PWD) VALUES(7, '111117', '李焕珍', '1', '000'); |
配置文件
server: # 端口号设置 port: 8080 spring: application: name: vue-login datasource: name: dev_db type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: oracle.jdbc.OracleDriver // 加载驱动 url: jdbc:oracle:thin:@127.0.0.1:1521:orcl // 连接方式 username: test // 用户名 password: test // 密码 mybatis-plus: mapper-locations: classpath*:mybatis/mapper/*.xml # config-location: classpath:mybatis/mybatis-config.xml global-config: #全局配置 banner: true #是否需要开启控制台 configuration: # 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射 map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #设置mybatis 打印日志到控制台 type-aliases-package: cn.eli.vue.entity |
注意: 这里有一个配置上的坑,以前我在使用Mybatis时,都会使用mybatis-config.xml配置文件,来设置mybatis的全局配置设置完成之后,我只需要在application.yml配置文件中引入即可(config-location: classpath:mybatis/mybatis-config.xml);如果我这里使用global-config 进行全局配置,那么就不需要在设置config-location,因为两者是真的冲突;
创建实体类
@Data @EqualsAndHashCode(callSuper = false) @TableName("PUB_USERS") @ToString public class Users {
private static final long serialVersionUID = 1L; // @TableId(type = IdType.ASSIGN_ID) private BigDecimal id;
private String code;
private String name;
private String pwd;
private String adminPwd;
} |
关于adminPwd 字段的说明:
这里有一点需要注意的是,我们的数据库中的字段要和我们实体类中的字段相对应;比如
数据库中 code 和 实体类 code;但是数据库中的admin_pwd 字段,和实体类中的adminPwd字段并不相互对应;我们喜欢在java中喜欢用驼峰命名的方式(第一个单词的首字母小写,第二个单词的首字母大写)。但是mybatis-plus还是帮我们映射成功,这是因为我们在配置中存在map-underscore-to-camel-case=true的配置,它就负责帮我们开启驼峰命名的映射规则;
如果我们需要手动映射,那么我们可以借助注解@TableField() 如下所示:
@TableField(value = "code") // value值即指定需要映射的数据库字段名 private String code; |
如果我们的实体类中,存在字段不是数据库中的,如我新增到实体类中的字段:
private String sex; |
这个字段在数据库中并不存在,我们不需要它和数据库中的字段映射,那么我们怎么办呢?
可以借助@TableField 的exists 属性,把它设置为false,表示不需要做映射;
注意: TableId 标识本字段为表中主键,它的type属性用于指定主键生成方式;主要有以下四种生成方式: AUTO(数据库ID自增,数据库需要支持主键自增(如MySQL),并设置 主键自增)
INPUT(用户输入ID,数据类型和数据库保持一致就行)
ASSIGN_ID //待补充
ASSIGN_UUID // 待补充
如果用户操作的是oracle数据库,不建议使用提供的自增长方式;后台会报错;
创建Mapper接口
import com.baomidou.mybatisplus.core.mapper.BaseMapper @Repository public interface UsersMapper extends BaseMapper<Users> { } |
这里,我们可以看一下,当我们继承BaseMapper接口之后,可以获取到通用方法,并且每个方法上都带有中文注释;
如下图所示:
创建对应的Controller:
@RestController @RequestMapping("/users") public class UsersController { @Autowired private UsersMapper usersMapper; } |
注意: 这里我们先跳过创建service及实现类的过程,来说明通用Mapper中的部分用法;
通用方法
增加操作
/** * 插入一条记录 * * @param entity 实体对象 */ int insert(T entity); ---->这里T泛型,会转换成Users类; |
使用方式: UsersController: @RequestMapping("/get/list") public void getUsersList(){ Users users = new Users(); users.setId(8); users.setCode("111118"); users.setName("孙尚香"); users.setPwd("123"); users.setAdminPwd("1"); int num = usersMapper.insert(users); //这个返回的参数,表示的是执行成功的条数 } |
大家注意看第一处标红的地方,这里SQL就是我们要执行的插入操作,这就是在application.yml中增加了配置: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 之后的效果;
修改操作
/** 修改方式1: 根据用户id,修改指定字段内容的值; */ int updateById(@Param(Constants.ENTITY) T entity); |
使用方式:
/** 举个例子,我需要修改 id= 111118的pwd(密码),改为”456” */ @RequestMapping("/update/byid") public void updateById(){ Users users = new Users(); users.setId(8); users.setPwd("456"); int num = usersMapper.updateById(users); // 返回值依然是修改数据的条数 } |
/** 根据 whereEntity 条件,更新记录 Params: entity – 实体对象 (set 条件值,可以为 null) updateWrapper – 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */ int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper); |
/** 举个例子,设置id = 8的用户,将其Pwd 字段修改为“789” */ @RequestMapping("/update") public void update(){ /** * 这个实体对象,可以为我们需要修改的字段设置值; * */ Users users = new Users(); users.setPwd("789");// 设置需要修改的字段 pwd ,内容为789 /** * 设置查询条件 * */ QueryWrapper<Users> updateWrapper = new QueryWrapper<>(); updateWrapper.eq("id",8); // 查找字段id,内容是8 usersMapper.update(users,updateWrapper); // 设置修改 } |
查询操作
单一操作:
/** * 根据 ID 查询 * * @param id 主键ID */ T selectById(Serializable id); |
举例说明: /** 我想查询,id=8的数据; */ @RequestMapping("/select/byid") public void selectById(){ Integer id = 8; Users users = usersMapper.selectById(id); System.out.println(users); } |
批量查询:
/** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */ List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); |
@RequestMapping("/select/batch") public void selectBatchId(){ List<Integer> ids = Arrays.asList(new Integer[]{1,2,3}); // 设置id集合 List<Users> users = usersMapper.selectBatchIds(ids); users.stream().forEach(System.out::println); } |
设置多项条件进行查询:
/** * 查询(根据 columnMap 条件) * * @param columnMap 表字段 map 对象 * 这里有一个缺点就是,它只会根据设置的查询条件进行and 拼接,缺乏灵活性; */ List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); |
/** 设置查询条件为 :id = 3 并且 code = “111113” */ @RequestMapping("/select/map") public void selectBatchByMap(){ Map<String,Object> queryMap = new HashMap<>(); queryMap.put("id",3); queryMap.put("code","111113"); List<Users> users = usersMapper.selectByMap(queryMap); users.stream().forEach(System.out::println); } |
selectBatchByMap 查询条件过于死板,毕竟不能灵活设置查询条件;如果我想随意设置内容,该用什么方法呢?
----- 可以会用selectOne 和 selectList;
selectOne 和 selectList 中传递的参数均为Wrapper<T> queryWrapper ,这个参数可以灵活配置查询条件,可以满足单表查询的所有情况;
和上面update方法使用类似;
selectOne ---> 查询结果类型为Users;
selectList ----> 查询返回结果是List;
@RequestMapping("/select/one") public void selectOne(){ QueryWrapper<Users> queryWrapper = new QueryWrapper(); queryWrapper.eq("name","李元芳");// 查询姓名为“李元芳”的用户 Users users = usersMapper.selectOne(queryWrapper); System.out.println("当前查询出的数据为:" + users); }
@RequestMapping("/select/list") public void selectList(){ QueryWrapper<Users> queryWrapper = new QueryWrapper<>(); queryWrapper.like("name","李"); // 查询名字中 包含 “李”的用户 List<Users> users = usersMapper.selectList(queryWrapper); users.stream().forEach(System.out::println); } |
作妖一下,如果我用selectOne 查询,查询出的结果由多条,会产生什么效果呢?如下代码:
QueryWrapper<Users> queryWrapper = new QueryWrapper(); // queryWrapper.eq("name","李元芳");// 查询姓名为“李元芳”的用户 queryWrapper.like("name","李"); // 查询名字中 包含 “李”的用户 Users users = usersMapper.selectOne(queryWrapper); System.out.println("当前查询出的数据为:" + users); |
执行结果果断报错,错误内容为:
Expected one result (or null) to be returned by selectOne(), but found: 2 |
如果我需要查询,符合条件的数据有多少条,可以使用selectCount;
@RequestMapping("/select/count") public void selectCount(){ // 统计pub_users表中一共有多少条数据 /*** * 传递参数为 Wrapper<T> queryWrapper, * 传递值为null,表示不设置任何查询条件 */ Integer count = usersMapper.selectCount(null); System.out.println(count); } |
分页操作可以使用selectPage 方法,代码如下:
QueryWrapper<Users> queryWrapper = new QueryWrapper<>(); // Page 中传递的参数为 当前页码 每页大小 Page<Users> usersPage = usersMapper.selectPage(new Page<Users>(1, 2), queryWrapper); // 分页查询 List<Users> users = usersPage.getRecords(); users.forEach(System.out::println); |
注意: 如果我们执行上述代码,输出操作结果,就会发现,并没有帮我们进行分页,因为这里没有设置查询条件,所以会查询出所有的内容。结果如下:
原因是什么呢? 重点是,我们操作的数据库是oracle;当我们在使用selectPage 方法时,底层代码会帮我们在SQL尾部拼接 LIMIT 10 OFFSET 10语句,表示每页显示10条,从下标为10的数据开始显示。但是,oracle并不支持这种语法,所以不会有此操作;
(后面我会补充PageHelper分页的方法)
删除数据
按照ID进行删除---deleteById
@RequestMapping("/delete/id") public void delete(){ Integer id = 8; int num = usersMapper.deleteById(id); //返回的是成功删除数据的条数 System.out.println(num); } |
根据ID批量删除---- deleteBatchIds
@RequestMapping("/delete/ids") public void deleteByIds(){ List<Integer> ids =Arrays.asList(new Integer[]{1,2,3,4}); // 设置批量删除id值 int num = usersMapper.deleteBatchIds(ids); System.out.println(num); } |
设置多条件删除数据--- deleteByMap
@RequestMapping("/delete/map") public void deleteMap(){ Map<String,Object> map = new HashMap<>(); map.put("id",8); map.put("admin_pwd","1"); int num = usersMapper.deleteByMap(map); // 设置删除条件 System.out.println(num); } |
自定义设置删除条件----delete
public void delete(){ QueryWrapper<Users> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("name","孙尚香"); int num = usersMapper.delete(queryWrapper); System.out.println(num); } |