引入
回想一下SpringBoot集成MyBatis的实现过程
步骤1:建库建表
步骤2:建项目-mybatis-demo
步骤3:依赖
步骤4:配置文件-application.properties
步骤5:逆向工程--domain/mapper/mapper.xml/test
步骤6:测试
存在问题:
回顾整个开发过程,不知道大家有没有留意到:每个实体表对应一个实体类,对应一个Mapper.java接口, 对应一个Mapper.xml配置文件,这些Mapper.java接口基本上都是重复的CRUD方法,对应的Mapper.xml也是重复的CRUD SQL,此时不禁思考:能不能对这些重复的CRUD方法, CRUD SQL 进行简化呢,不用写最好? 答案:yes, 使用:MyBatis-Plus
MyBatis-Plus简介
是什么
MyBatis-Plus (简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像中的 1P、2P,基友搭配,效率翻倍。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
支持数据库
任何能使用 MyBatis
进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
框架结构
代码托管
Github:https://github.com/baomidou/mybatis-plus
Gitee:https://gitee.com/baomidou/mybatis-plus
拓展案例
https://github.com/baomidou/awesome-mybatis-plus
MyBatis vs MP
MyBatis
优点 :
1>SQL语句自由控制,较为灵活
2>SQL与业务代码分离,易于阅读与维护
3>提供动态SQL语句,可以根据需求灵活控制
缺点 :
1>简单的crud操作也必须提供对应SQL语句
2>必须维护大量的xml文件
3>自身功能有限,要拓展只能依赖第三方插件
MyBatis-Plus 是在Mybatis的基础上进行二次开发的具有MyBatis所有功能, 也添加了不少好用的功能
比如:
1>提供无sql 的crud操作
2>内置代码生成器,分页插件, 性能分析插件等
3>提供功能丰富的条件构造器快速进行无sql开发
...
学习建议: 当一个全新的框架来学
案例:HelloWord
对MyBatis-Plus有大体了解之后,来写个helloWord看下
步骤1:建库建表
建立一个数据库:mybatis-plus 导入下面SQL语句
sql
-- ----------------------------
-- Table structure for department
-- ----------------------------
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '表示部门编号,由两位数字所组成',
`name` varchar(14) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '部门名称,最多由14个字符所组成',
`sn` varchar(13) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '部门编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 42 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES (1, '财务部', 'cwb');
INSERT INTO `department` VALUES (2, '调研部', 'dyb');
INSERT INTO `department` VALUES (3, '销售部', 'xsb');
INSERT INTO `department` VALUES (4, '运营部', 'yyb');
INSERT INTO `department` VALUES (5, '小卖部', 'xmb');
INSERT INTO `department` VALUES (6, '总经办', 'zjb');
-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
`id` bigint(0) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`age` int(0) NULL DEFAULT NULL,
`admin` bit(1) NULL DEFAULT NULL,
`dept_id` bigint(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of employee
-- ----------------------------
INSERT INTO `employee` VALUES (1, 'admin', '1', 'admin@abc.com', 40, b'1', 6);
INSERT INTO `employee` VALUES (2, '赵总', '1', 'zhaoz@xxx.cn', 35, b'0', 1);
INSERT INTO `employee` VALUES (3, '赵一明', '1', 'zhaoym@xxx.cn', 25, b'0', 1);
INSERT INTO `employee` VALUES (4, '钱总', '1', 'qianz@xxx.cn', 31, b'0', 2);
INSERT INTO `employee` VALUES (5, '钱二明', '1', 'qianem@xxx.cn', 25, b'0', 2);
INSERT INTO `employee` VALUES (6, '孙总', '1', 'sunz@xxx.cn', 35, b'0', 3);
INSERT INTO `employee` VALUES (7, '孙三明', '1', 'sunsm@xxx.cn', 25, b'0', 3);
INSERT INTO `employee` VALUES (8, '李总', '1', 'liz@xxx.cn', 35, b'0', 4);
INSERT INTO `employee` VALUES (9, '李四明', '1', 'lism@xxx.cn', 25, b'0', 4);
INSERT INTO `employee` VALUES (10, '周总', '1', 'zhouz@xxx.cn', 19, b'0', 5);
INSERT INTO `employee` VALUES (11, '周五明', '1', 'zhouwm@xxx.cn', 25, b'0', 5);
INSERT INTO `employee` VALUES (12, '吴总', '1', 'wuz@xxx.cn', 41, b'0', 5);
INSERT INTO `employee` VALUES (13, '吴六明', '1', 'wulm@xxx.cn', 33, b'0', 5);
INSERT INTO `employee` VALUES (14, '郑总', '1', 'zhengz@xxx.cn', 35, b'0', 3);
INSERT INTO `employee` VALUES (15, '郑七明', '1', 'zhengqm@xxx.cn', 25, b'0', 2);
INSERT INTO `employee` VALUES (16, '孙四明', '1', 'sunsim@xxx.cn', 25, b'0', 3);
INSERT INTO `employee` VALUES (17, '孙五明', '1', 'sunwm@xxx.cn', 25, b'0', 3);
INSERT INTO `employee` VALUES (18, '李五明', '1', 'liwm@xxx.cn', 25, b'0', 4);
INSERT INTO `employee` VALUES (19, '李六明', '1', 'lilm@xxx.cn', 25, b'0', 4);
INSERT INTO `employee` VALUES (20, 'xiaofei', '111', 'dafei@xxx.cn', 0, b'0', 1);
步骤2:建项目-mybatis-plus-demo
使用SpringBoot + MyBatis-Plus实现,建立空白maven项目,改造成标准SpringBoot项目
步骤3:导入相关依赖
xml
<!--SpringBoot父项目依赖-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.3</version>
</parent>
<dependencies>
<!--MyBatis-Plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<!--druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
<!--mysql连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--SpringBoot测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--get/set方法设置工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
步骤3:导入配置文件
application.properties
properties
#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-plus?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 配置sql打印日志
logging.level.cn.xxx.mp.mapper=debug
application.yml
yaml
#mysql
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis-plus?useUnicode=true&characterEncoding=utf8
username: root
password: admin
# 配置sql打印日志
logging:
level:
cn.xxx.mp.mapper: debug
步骤4:实体类与Mapper接口类
实体类-Employee
java
@Setter
@Getter
@ToString
public class Employee {
private Long id;
private String name;
private String password;
private String email;
private int age;
private int admin;
private Long deptId;
}
Mapper接口类
编写注意:
- 自定义EmployeeMapper接口
- 继承父接口BaseMapper
- 明确指定父接口泛型:当前接口操作的实体对象
java
public interface EmployeeMapper extends BaseMapper<Employee> {
}
步骤5:编写启动类App
注意App类放置在项目根包下,注意:必须开启Mapper接口扫描,并明确指定扫描地址。
java
@SpringBootApplication
@MapperScan(basePackages = "cn.xxx.mp.mapper")
public class App {
}
步骤6:编写测试类并测试
java
@SpringBootTest
public class CRUDTest {
@Autowired
private EmployeeMapper employeeMapper;
@Test
public void testSave(){
Employee employee = new Employee();
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("dafei@xxx");
employee.setName("dafei");
employee.setPassword("111");
employeeMapper.insert(employee);
}
@Test
public void testUpdate(){
Employee employee = new Employee();
employee.setId(1327139013313564673L);
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("dafei@xxx.cn");
employee.setName("xiaofei");
employee.setPassword("111");
employeeMapper.updateById(employee);
}
@Test
public void testDelete(){
employeeMapper.deleteById(1L);
}
@Test
public void testGet(){
System.out.println(employeeMapper.selectById(1L));
}
@Test
public void testList(){
System.out.println(employeeMapper.selectList(null));
}
}
观察执行SQL语句
到这,本案例就结束,此处应该有掌声~
注意: junit出问题
xml
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
案例解析
这么神奇的操作,无SQL有能实现CRUD操作,神了~它怎么做到的?回答2个问题:
问题1
问:EmployeeMapper接口并没有定义crud方法为什么测试类中可以调用呢?
答:EmployeeMapper 继承父接口:BaseMapper 而父接口定义很多crud方法,子接口可以直接继承
问题2
问:在项目中并没有写CRUD SQL语句,为什么可以实现CRUD操作呢?
分析:项目中没有SQL,但是能实现CRUD操作,说明框架实现CRUD SQL拼接
思考:框架怎么进行sql拼接?
分析:以查询所有为例子:
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee
上面SQL语句, 发现只需要获取 查询的表 与 查询的列, 即可实现SQL拼接
思考:怎么获取要查询的表与要查询的列呢?
分析: 需要mybatis 框架设计理论知识储备。
MyBatis 框架设计理论: ORM思想 --使用面向对象的方式操作数据库
缩写 | 全称 | 介绍 |
---|---|---|
O | Object | 对象 |
R | Relational | 关系/表 |
M | Mapping | 映射 |
ORM思想在代码上体现
O---实体表
R---数据库
M--映射--实体类与表实现一一映射关系
类名-----映射-----表名
字段名----映射----列名
上面分析得到要拼接SQL语句, 前提是获取查询的表与查询的列,使用ORM思路转换一下,获取当前要操作实体类与实体类字段也可以。
观察发现, 定义EmployeeMapper 接口
java
public interface EmployeeMapper extends BaseMapper<Employee> {
}
父接口BaseMapper需要明确指定要操作实体类泛型,那么项目启动时就可以通过反射 + 内省 获取当前接口操作实体类字节码对象,解析该字节码对象就可以获取:类名 字段名, 那么对应表名, 对应列名,也就有啦,那SQL就可以实现拼接啦。
最后会看MyBatis-Plus的架构图,是否掌握了MyBatis-Plus精髓啦
常用注解
从ORM思路出发,类名与表名,字段名与列名必须一致才可以一一映射,如果他们不一致会出现啥情况?答案很明显,报错,此时怎么办?答案:使用MyBatis-Plus常用注解。
路径:https://baomidou.com/pages/223848/#tablename
@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
java
@TableName("t_employee") //表示当前类映射指定的表
public class Employee {
//.....
}
@TableField
- 描述:字段注解(非主键)
- 作用:指定当前属性映射数据库表哪一列, 默认是跟属性名一致
java
public class Employee {
@TableField("ename") //表示当前属性映射指定列
private String name;
}
很多时候,实体类除了与数据库表一一对应的字段外,还会有很多涉及到业务实现的字段,比如:维护与部门的多对一关系
java
public class Employee {
@TableField("ename") //表示当前属性映射指定列
private String name;
@TableField(exist = false)
private Deparment dept;
}
上面的dept字段没有与一一对应,所以不能参与映射,需要明确设置忽略:@TableField(exist = false)
@TableId
- 描述:主键注解
- 使用位置:实体类主键字段
java
public class Employee {
private Long id;
}
默认情况下,MyBatis-Plus使用雪花算法计算唯一的Long类型id
*
雪花算法
SnowFlake 中文意思为雪花,故称为雪花算法。最早是 Twitter 公司在其内部用于分布式环境下生成唯一 ID。在2014年开源 scala 语言版本。
雪花算法的原理就是生成一个的 64 位比特位的 long 类型的唯一 id。
如果还是想使用数据库自动增长id就需要如下配置
java
public class Employee {
@TableId(type = IdType.AUTO)
private Long id;
}
@Version【拓展】
-
描述:乐观锁注解、标记 @Verison 在字段上
-
作用:用于标记乐观锁操作字段
详细了解:
https://blog.csdn.net/langfeiyes/article/details/122285716
@TableLogic【拓展】
- 描述:表字段逻辑处理注解(逻辑删除)
详细了解:
https://blog.csdn.net/langfeiyes/article/details/122142922
通用Mapper接口
简介
1>通用 CRUD 封装BaseMapper接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
2>泛型 T 为任意实体对象
3>参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
4>对象 Wrapper 为 条件构造器
操作准备
1>数据库员工表回复原样:employee
2>员工实体对象标准设置
java
@Setter
@Getter
@ToString
public class Employee {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String password;
private String email;
private int age;
private int admin;
private Long deptId;
@TableField(exist = false)
private Deparment dept;
}
3>配置MyBatis-Plus日志
properties
第一种:
logging.level.cn.xxx.mp.mapper=debug
第二种:
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
BaseMapper方法集
insert:1个 update:2个 delete:4个 select:10个
*
添加-insert
mybatis-plus中添加方法有1个:
java
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
用法:传入要添加的实体对象
需求:往employee表中添加一条记录
java
@Test
public void testSave(){
Employee employee = new Employee();
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("dafei@163.com");
employee.setName("dafei");
employee.setPassword("111");
employeeMapper.insert(employee);
}
执行后SQL
sql
INSERT INTO employee ( id, name, password, email, age, admin, dept_id ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
更新-update
mybatis-plus中更新方法有2个:
java
/**
* 根据 ID 修改
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(T entity, Wrapper<T> updateWrapper);
updateById
用法:以id作为更新条件更新指定实体对象
需求:更新id为1员工姓名为:dafei
java
@Test
public void testUpdateById2(){
Employee employee = new Employee();
employee.setId(1L);
employee.setName("dafei");
employeeMapper.updateById(employee);
}
执行后SQL
sql
UPDATE employee SET name=?, age=?, admin=? WHERE id=?
改前:
*
改后:
*
执行之后就发现一个问题,employee表中id为1的员工数据age跟 admin这列数据丢失了,再看打印的SQL,本来只改name字段按理SQL中会拼接set name = ?但是SQL也拼接了age 跟admin列的更新 ,怎么回事呢?
这里就涉及到MyBatis-Plus拼接 SQL的规则啦:
MyBatis-Plus拼接SQL规则:
1:实体对象作为方法参数,如果属性值为null,该属性不参与方法SQL拼接, 反之, 则参与拼接。
2:实体对象作为方法参数,如果属性为基本类型,有默认值,该属性参与方法SQL拼接
上面的updateById方法操作中,name, password, email属性值都是null, 所以update sql语句set中并没有拼接name,password, email 列的更新。而 age, admin 2个属性属于基本类型,不显示设置属性值,那默认就是0,0。 MyBatis-Plus 认为该属性有值,便在update sql语句set 中拼接 age = ?, name = ?。这操作最终导致了表中数据丢失。
那怎么解决上述问题呢
方案1:使用包装类型
java
private Integer age;
private Integer admin;
包装类型默认值为null, 不参与set拼接
方案2:使用先查询,再替换,后更新
java
@Test
public void testUpdate(){
//先查询
Employee employee = employeeMapper.selectById(1L);
//再替换
employee.setName("zhangxiaosan");
//后更新
employeeMapper.updateById(employee);
}
先查询,可将表的所有字段查询出来,那么employee所有属性便有值,替换要更新属性,最后update时可以保证set 的列都要值,最终表数据能还原。
方案3:使用update(null, wrapper)方法。
update
需求:更新id为1员工姓名为:dafei
java
@Test
public void testUpdate2(){
//Wrapper :暂时认为是sql 语句中where
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 21L); //等价于: 拼接 where id = 21
wrapper.set("name", "zhangzhongsan");
employeeMapper.update(null, wrapper);
}
执行后SQL
java
UPDATE employee SET name=? WHERE (id = ?)
这里有一个注意要点,update的第一参数为null,如果传了employee,sql 拼接跟上面的updateById一样,起不到想要的效果。
这种方案,准确的讲应该叫:使用wrapper方式。
思考:updateById 跟update 2个方法该如何选用
java
/**
* update跟updateById方法的选用
*
* 使用updateById 场景
* 1>where条件是id时候update场景
* 2>进行全量(所有字段)更新时候
*
* 使用update场景
* 1>where条件是不确定,或者多条件的update场景
* 2>进行部分字段更新时候
*/
删除-delete
mybatis-plus中删除方法有4个
java
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
*
* @param wrapper 实体对象封装操作类(可以为 null)
*/
int delete(Wrapper<T> wrapper);
/**
* 删除(根据ID 批量删除)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(Collection<? extends Serializable> idList);
deleteById
用法:删除指定id的实体信息
需求:删除id为1的员工信息
java
@Test
public void testDeleteById(){
employeeMapper.deleteById(1L);
}
执行后SQL
sql
DELETE FROM employee WHERE id=?
deleteBatchIds
用法:批量删除指定多个id对象信息
需求:删除id为1,2,3的员工信息
java
@Test
public void testDeleteBatchIds(){
employeeMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
}
执行后SQL
sql
DELETE FROM employee WHERE id IN ( ? , ? , ? )
deleteByMap
用法:按条件删除,具体条件放置在map集合中
需求:删除name=dafei并且age=18的员工信息
java
@Test
public void testDeleteByMap(){
//key: 列, value:要匹配的条件值
Map<String, Object> map = new HashMap<>();
map.put("name", "dafei");
map.put("age", 18);
//多条件删除, map里面装的都是where 条件
employeeMapper.deleteByMap(map);
}
执行后SQL
sql
DELETE FROM employee WHERE name = ? AND age = ?
delete
用法:按条件删除,具体条件通过条件构造器拼接
需求:删除name=dafei并且age=18的员工信息
java
@Test
public void testDelete(){
//如果更新条件操作使用: UpdateWrapper
//其他条件操作使用: QueryWrapper
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("age", 18);
wrapper.eq("name", "dafei"); //条件是and拼接
employeeMapper.delete(wrapper);
}
执行完的SQL
sql
DELETE FROM employee WHERE (age = ? AND name = ?)
查询-select
mybatis-plus中查询方法有10个
java
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(Collection<? extends Serializable> idList);
/**
* 查询(根据 columnMap 条件)
*
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap( Map<String, Object> columnMap);
/**
* 根据 entity 条件,查询一条记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
T selectOne(Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Integer selectCount(Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList( Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<E extends IPage<T>> E selectPage(E page, Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, Wrapper<T> queryWrapper);
selectById
用法:查询指定id 对象信息
需求:查询id=1的员工信息
java
@Test
public void testselectById(){
Employee employee = employeeMapper.selectById(1L);
}
执行完SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE id=?
selectBatchIds
用法:批量查询指定的id对象信息
需求:查询id=1,2,3的员工信息
java
@Test
public void testselectBatchIds(){
employeeMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
}
执行完SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE id IN ( ? , ? , ? )
selectByMap
用法:查询满足条件实体信息,条件封装到map集合中
需求:查询age=18并且name=dafei的员工信息
java
@Test
public void testselectByMap(){
Map<String, Object> map = new HashMap<>();
map.put("age", 18);
map.put("name", "dafei");
List<Employee> employees = employeeMapper.selectByMap(map);
}
执行完SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE name = ? AND age = ?
selectCount
用法:查询满足条件实体信息记录总条数
需求:查询员工表记录条数
java
@Test
public void testselectCount(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//如果统计所有数据:selectCount(null)
//Integer count = employeeMapper.selectCount(wrapper);
Integer count = employeeMapper.selectCount(wrapper);
}
执行完SQL
sql
SELECT COUNT( 1 ) FROM employee
selectList
用法:查询满足条件实体信息记录, 返回List
需求:查询满足条件的所有的员工信息, 返回List
java
@Test
public void testselectList(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
List<Employee> list = employeeMapper.selectList(wrapper);
for (Employee employee : list) {
System.out.println(employee);
}
}
执行完SQL
java
SELECT id,name,password,email,age,admin,dept_id FROM employee
selectMaps
用法:查询满足条件实体信息记录, 返回List<Map<String, Object>>
需求:查询满足条件的所有的员工信息, 返回List<Map<String, Object>>
java
@Test
public void testselectMaps(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
List<Map<String, Object>> mapList = employeeMapper.selectMaps(wrapper);
for (Map<String, Object> map : mapList) {
System.out.println(map);
}
}
执行完SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee
比较selectList 跟 selectMap 2个方法,一个返回值:List<实体> 一个是List
思考:开发中什么时候使用selectList,什么时候使用selectMaps呢?
java
/**
* 如果sql语句执行完之后,得到的数据能封装成实体对象时使用:selectList
* 如果sql语句执行完之后,得到的数据不能封装成实体对象时使用:selectMaps
*
* 比如:按部门id分组,统计每个部门员工的个数。
*
* select dest_id, count(id) count from employe group by dest_id
* 此时返回的2列无法封装成employee对象(原因:没有对应的属性与列对应),此时要么封装成vo值对象,要么就是map对象。
*/
selectPage
用法:分页查询满足条件实体信息记录
需求:查询第二页员工数据, 每页显示3条, (分页返回的数据是员工对象)
mybatis-plus分页查询步骤分2步:
1>在配置类中添加分页插件(springboot项目)
java
//分页
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true); //合理化
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
2>编写分页代码
java
@Test
public void testselectPage(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//等价于:PageResult PageInfo
//参数1:当前页, 参数2:每页显示条数
Page<Employee> page = new Page<>(2,3);
Page<Employee> p = employeeMapper.selectPage(page, wrapper);
System.out.println(p == page); //true
System.out.println("当前页:" + page.getCurrent());
System.out.println("每页显示条数:" + page.getSize());
System.out.println("总页数:" + page.getPages());
System.out.println("总数:" + page.getTotal());
System.out.println("当前页数据:" + page.getRecords());
}
执行完SQL
sql
SELECT COUNT(1) FROM employee
SELECT id,name,password,email,age,admin,dept_id FROM employee LIMIT ?,?
selectMapsPage
用法:跟selectPage一样,区别在与selectMapsPage返回分页数据集合泛型是Map, selectPage是实体对象。
selectOne
用法:查询满足条件实体对象,如果有多条数据返回,抛异常。
需求:查询name=dafei,password=1的员工数据
java
@Test
public void testselectOne(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name", "dafei");
wrapper.eq("password", "1");
Employee employee = employeeMapper.selectOne(wrapper);
System.out.println(employee);
}
执行完SQL
java
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND password = ?)
selectObjs
用法:查询满足条件实体对象,返回指定列的集合,如果没有指定列,默认返回第一列
需求:查询员工表所有员工名称
java
@Test
public void testselectObjs(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("name");
List<Object> list = employeeMapper.selectObjs(wrapper);
list.forEach(System.out::println);
}
执行完SQL
java
SELECT name FROM employee
到这,通用的Mapper接口就介绍完了。
条件构造器
Wrapper介绍
https://baomidou.com/pages/10c804/
条件构造器-Wrapper按照官方的给出的解释是用于生成 sql 的 where 条件,但在使用的过程中,发现Wrapper不仅仅是用于拼接where条件,可以用于拼接set 语法, select语法。所以,wrapper准确的来说,更像mybatis中的动态标签。在sql任意位置完成sql片段组装。
类体系结构
按功能分
修改型:UpdateWrapper LambdaUpdateWrapper
查询型:QueryWrapper LambdaQueryWrapper
按操作分
传统型:QueryWrapper UpdateWrapper
Lambda:LambdaQueryWrapper LambdaUpdateWrapper
不管怎么分,它们都有一个共同的父类:AbstractWrapper, 父类里面定义wrapper核心的条件方法比如:
allEqeq ne gt ge lt le between notBetween like notLike likeLeft likeRight isNull isNotNull in notIn inSql notInSql groupBy orderByAsc orderByDesc orderBy having func or and nested apply last exists notExists 等
Update类型的Wrapper独有操作方法:set setSql
Query类型的Wrapper独有操作方法:select
更新-Wrapper
更新类型的Wrapper有2个
UpdateWrapper 与 LambdaUpdateWrapper 这2个Wrapper都是用于对数据修改的条件构造器。
需求:将id=1的员工name修改为dafeifeng
UpdateWrapper
java
@Test
public void testUpdate(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L);
wrapper.set("name", "dafeifeng");
employeeMapper.update(null, wrapper);
}
独有方法操作
需求1其实还是一种写法,使用到:setSql
java
@Test
public void testUpdate(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L);
//wrapper.set("name", "dafeifeng");
wrapper.setSql("name='dafeifeng'");
employeeMapper.update(null, wrapper);
}
setSql表示使用拼接SQL片段方式进行操作,区别从打印的SQL可以看出
sql
-- set方式:
UPDATE employee SET name=? WHERE (id = ?)
-- 参数:
dafeifeng(String), 1(Long)
-- setSql方式:
UPDATE employee SET name='dafeifeng' WHERE (id = ?)
-- 参数:
1(Long)
2个对比,可以很明显看出set方式使用预编译操作方法,而setSql 直接进行拼接,可能存sql注入风险,不建议使用,操作上更推荐set方式。
问题
从上面更新代码上看,开发存在一定隐患,设置条件/更新的列都是直接使用字符串,如果手误写错,编译期是无法检查的,需要等执行之后才会出现异常。存在一定瑕疵。怎办,改进解决方案就是:LambdaUpdateWrapper
LambdaUpdateWrapper
java
@Test
public void testUpdateLambda(){
LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Employee::getId, 1L);
wrapper.set(Employee::getName, "dafeifeng");
employeeMapper.update(null, wrapper);
}
LambdaUpdateWrapper 的update 跟 UpdateWrapper 的update操作一样,唯一区别点在于UpdateWrapper的操作列都是以字符串的形式存在,LambdaUpdateWrapper 的操作列使用lambda表达式。
咋一看,感觉高大山,点开查看源码,你发现:Employee::getName其实最终还是会解析出name属性来。简单的理解就是通过Employee类中的getName方法,将name属性解析出来。这么折腾好处是比直接写字符串方式多了一种操作前检查(避免出现手误)
java
@Override
public LambdaUpdateWrapper<T> set(boolean condition, SFunction<T, ?> column, Object val) {
if (condition) {
sqlSet.add(String.format("%s=%s", columnToString(column), formatSql("{0}", val)));
}
return typedThis;
}
protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
return getColumn(LambdaUtils.resolve(column), onlyColumn);
}
private String getColumn(SerializedLambda lambda, boolean onlyColumn) {
Class<?> aClass = lambda.getInstantiatedType();
tryInitCache(aClass);
String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
ColumnCache columnCache = getColumnCache(fieldName, aClass);
return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();
}
查询-Wrapper
查询类型的Wrapper有2个
**QueryWrapper ** 与 LambdaQueryWrapper 这2个Wrapper都是用于对数据修改的条件构造器。
需求:查询name=dafei, age=18 的员工信息
QueryWrapper
java
@Test
public void testQuery(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name", "dafei");
wrapper.eq("age", 18);
List<Employee> list = employeeMapper.selectList(wrapper);
}
独有方法操作
query类型wrapper也有自己的独有的方法:select
需求:查询name=dafei, age=18 的员工信息,只需要查询id, name 2列
java
@Test
public void testQuery(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("id", "name");
wrapper.eq("name", "dafei");
wrapper.eq("age", 18);
List<Employee> list = employeeMapper.selectList(wrapper);
}
里面的wrapper.select("id", "name"); 方法表示查询结果返回id,name 2列,等价于:
sql
SELECT id,name FROM employee WHERE (name = ? AND age = ?)
-- 如果不加select 方法,默认查询所有列,等价于:
SELECT * FROM employee WHERE (name = ? AND age = ?)
问题
同UpdateWrapper
LambdaQueryWrapper
java
@Test
public void testQueryLambda(){
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getName, "dafei");
wrapper.eq(Employee::getAge, 18);
List<Employee> list = employeeMapper.selectList(wrapper);
}
2个Wrapper操作跟上面update操作一个样,都是将字符串的操作列转换成lambda方式,其实没有本质上的区别。
构建Wrapper实例
java
@Test
public void testWrapper(){
//wrapper对象的创建
//query
QueryWrapper<Employee> queryWrapper1 = new QueryWrapper<>();
QueryWrapper<Employee> queryWrapper2 = Wrappers.<Employee>query();
LambdaQueryWrapper<Employee> queryWrapper3 = new LambdaQueryWrapper<>();
LambdaQueryWrapper<Employee> queryWrapper4 = Wrappers.<Employee>lambdaQuery();
LambdaQueryWrapper<Employee> queryWrapper5 = queryWrapper1.lambda();
//update
UpdateWrapper<Employee> updateWrapper1 = new UpdateWrapper<>();
UpdateWrapper<Employee> updateWrapper2 = Wrappers.<Employee>update();
LambdaUpdateWrapper<Employee> updateWrapper3 = new LambdaUpdateWrapper<>();
LambdaUpdateWrapper<Employee> updateWrapper4 = Wrappers.<Employee>lambdaUpdate();
LambdaUpdateWrapper<Employee> updateWrapper5 = updateWrapper1.lambda();
}
其中的Wrappers 是mybatis-plus官方提供的构建Wrapper实例的工具类。
Wrapper选择
单从可读性来看,建议使用传统的Wrapper,从预防手误来看,建议使用LambadWrapper
实际开发以公司规定为准则。
Wrapper练习
将之前写的CRUD操作转换成LambadWrapper方法
条件查询
概要
1> 前一章节重点介绍了MyBatis-Plus中的wrapper体系与操作方式,本章节重点讲的wrapper中的query操作,以QueryWrapper 实例为切入点,讲解常用的条件查询,UpdateWrapper涉及到的条件同理可得即可。
2>一样沿用前几篇使用的employee 表/实体/mapper等代码
java
@Setter
@Getter
@ToString
@TableName("employee")
public class Employee {
@TableId(value = "id", type= IdType.AUTO)
private Long id;
private String name;
private String password;
private String email;
private int age;
private int admin;
private Long deptId;
}
列投影-select
select重载的方法有3个,其实就2个,一个功能重复了
用法:从查询结果集中挑选指定列
需求:查询所有员工信息,返回员工的age跟name属性
select(String...)
java
@Test
public void testQuery1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("name", "age"); //列的投影, 挑选哪一些列, 参数是列名
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT name,age FROM employee
使用注意,如果列使用别名,那就按照sql语法编写别名,换句话讲select里面参数其实就是sql中的select语句,语法一样
sql
wrapper.select("name as ename", "age as eage");
sql效果
sql
SELECT name as ename,age as eage FROM employee
还有简便的写法:sql片段方式
sql
@Test
public void testQuery1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//wrapper.select("name", "age"); //列的投影, 挑选哪一些列, 参数是列名
wrapper.select("name, age"); //参数是sql 片段
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
注意,这种写法参数为sql片接,容易出现sql注入风险
select(Class entityClass, Predicate predicate) 【拓展】
这个方法理解起来相对麻烦
enittyClass: 表示指定查询实体对象,比如:当前操作员工表,那么指定Employee.class
predicate:判断型函数接口,接口有个test方法,参数是TableFieldInfo
TableFieldInfo:表字段信息对象,将表的列抽象成java对象
方法意思:指定查询对象,使用predicate定义条件列的规则,满足条件的列,挑选出来。
需求:查询列名以 字母 "e" 结尾的列
java
@Test
public void testQuery1_1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select(Employee.class, tableFieldInfo -> tableFieldInfo.getColumn().endsWith("e"));
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT id,name,age FROM employee
查询出来的列有id,name, age。 id是默认查询,name 跟 age 列都有e字母,所以能查询出来
需求2:查询列名长度大于 5的列
java
@Test
public void testQuery1_1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select(Employee.class, tableFieldInfo -> tableFieldInfo.getColumn().length() > 5);
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT id,password,dept_id FROM employee
排序
MyBatis-Plus的排序有8个,具体分为3种:orderByAsc / orderByDesc / orderBy
orderByAsc : 正序排 3个
orderByDesc : 倒序排 3个
orderBy:1个
用法:对查询结果集排序,可以单列排,可以多列排
orderByAsc(String column)/orderByDesc(String column) : 单列正排序/倒排序
需求:查询所有员工信息,按age正序排/倒序排
java
@Test
public void testQuery2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age"); //正序
//wrapper.orderByDesc("age"); //倒序
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- 正序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC
-- 倒序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age DESC
需求:查询所有员工信息,按age正序排, 如果age 一样,按id正序排
需求:查询所有员工信息,按age正序排, 如果age 一样,按id倒序排
java
@Test
public void testQuery2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age");
wrapper.orderByAsc("id"); //正序
//wrapper.orderByDesc("id"); //倒序
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- 先正序后正序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC,id ASC
-- 先正序后倒序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC,id DESC
这里注意,排序列谁先谁后是根据orderBy方法调用顺序决定的,操作时务必小心
orderByAsc(String... column)/orderByDesc(String... column) : 多列正排序/倒排序
需求:查询所有员工信息,按age正序排, 如果age 一样,按id正序排
需求:查询所有员工信息,按age倒序排, 如果age 一样,按id倒序排
java
@Test
public void testQuery2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age", "id"); //都正序
//wrapper.orderByDesc("age", "id"); //都倒序
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- 都是正序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC,id ASC
-- 都是倒序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age DESC,id DESC
从上面也可以看出多列排序其实跟单列排序本质一样没啥区别
orderByAsc(boolean condition, String... column)/orderByDesc(boolean condition, String... column) : 带条件判断多列正排序/倒排序
condition:排序控制开关,当condition这个参数为true时,才会拼接sql排序语句
需求:查询所有员工信息,按age正序排
java
@Test
public void testQuery2_1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc(true, "age"); //true
//wrapper.orderByAsc(false, "age"); //false
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行完SQL
sql
-- true
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC
-- false
SELECT id,name,password,email,age,admin,dept_id FROM employee
orderBy(boolean condition, boolean isAsc,String... columns) : 带条件判断多列排序
condition: 当condition这个参数为true时,才对sql语句进行排序操作
isAsc:是否为正序排, true:表示正序, false:表示倒序
columns:排序的列,可以多列,可单列
需求:查询所有员工信息,按age正序排
需求:查询所有员工信息,按age倒序排
java
@Test
public void testQuery2_2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderBy(true, true, orderBy); //正序
wrapper.orderBy(true, false, orderBy); //倒序
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行完SQL
sql
-- 正序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC
-- 倒序
SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age DESC
比较运算
allEq
全等比较符,它重载方法有6个,功能由传入的参数决定
用法:全等,所有条件必须相等,条件使用Map<String, Object>封装, key:条件列, value:条件值
需求:查询name=dafei,age=18的员工信息
allEq(Map<String, V> params)
java
@Test
public void testQuery3() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "dafei");
map.put("age", 18);
wrapper.allEq(map);
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND age = ?)
allEq(Map<String, V> params, boolean null2IsNull)
null2IsNull:如果params参数中,如果某个key对应的value值为null时,
true:表示使用is null 替换, false:表示忽略该条件
java
@Test
public void testQuery3_1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", null);
map.put("age", 18); //value值为null
//wrapper.allEq(map, true); //拼接
wrapper.allEq(map, false); //不拼接
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
sql
-- true 拼接
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name IS NULL AND age = ?)
-- false 不拼接
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age = ?)
allEq(BiPredicate<String, V> filter, Map<String, V> params)【拓展】
filter:判断型函数接口,用于过滤params中的条件,接口有个test方法
需求:查询满足指定params条件的员工数据,附加条件, 如果params的key长度小于4,不参与sql条件查询
java
@Test
public void testQuery3_2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("name", "dafei");
map.put("age", 18);
wrapper.allEq((key, value)->{
return key.length() > 4;
}, map);
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ?)
params参数key值有age, 跟name, 函数接口做了限制,key长度必须大于4, 所以age被排除,符合要求只有name这个条件。sql值拼接name条件
allEq(boolean condition, Map<String, V> params, boolean null2IsNull)【拓展】
condition:allEq控制开关, 当condition为true,执行allEq语法,拼接查询条件, 为false, 不执行。
allEq(boolean condition, BiPredicate<String, V> filter, Map<String, V> params, boolean null2IsNull);【拓展】
跟上面介绍重复了,同理可得即可。
eq
重载方法有2个
*
用法:等值条件过滤, sql:where 列 = 值
eq(String column, Object value):等值匹配
需求:查询name=dafei的员工信息
java
@Test
public void testQuery4_1() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name", "dafei");
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ?)
eq(boolean condition, String column, Object value):带开关的等值匹配
带开关的eq 操作, 使用跟上面操作一样
ne
重载方法有2个
用法:不等条件过滤, sql: where 列 <> 值
ne(String column, Object value):不等值匹配
需求: 查询name != dafei的用户信息
java
@Test
public void testQuery4_2() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.ne("name", "dafei");
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name <> ?)
ne(boolean condition, String column, Object value):带开关的不等值匹配
带开关的ne 操作, 使用跟上面操作一样
gt/ge
gt有2个重载方法, ge也有2个重载方法
*
*
用法gt : great than 大于, sql: where 列 > 值
用法ge:greate than and equals, 大于等于, sql: where 列 >= 值
gt(String column, Obejct value) / ge(String column, Obejct value):大于/大于等于比较
需求:查询 age 大于 18 的员工信息
需求:查询age 大于等于 18 的员工信息
java
@Test
public void testQuery4_3() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18); //大于 18
//wrapper.ge("age", 18); //大于等于18
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- gt
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age > ?)
-- ge
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age >= ?)
gt(boolean condition, String column, Obejct value) / ge(boolean condition, String column, Obejct value):带开关的大于/大于等于比较
带开关的gt/ ge 操作, 使用跟上面操作一样
lt/le
lt有2个重载方法, le也有2个重载方法
用法lt : less than 小于, sql: where 列 < 值
用法le:less than and equals, 小于等于, sql: where 列 <= 值
lt(String column, Obejct value) / le(String column, Obejct value):小于/小于等于比较
需求:查询 age 小于 18 的员工信息
需求:查询age 小于等于 18 的员工信息
java
@Test
public void testQuery4_4() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.lt("age", 18); //小于 18
//wrapper.le("age", 18); //小于等于18
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- lt
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age < ?)
-- le
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age <= ?)
lt(boolean condition, String column, Obejct value) / le(boolean condition, String column, Obejct value):带开关的小于/小于等于比较
带开关的lt/ le 操作, 使用跟上面操作一样
isNull/isNotNull
isNull 重载2个方法, isNotNull重载2个方法
用法isNull : 列判null条件, sql: where 列 is null
用法isNotNull:列判定不为null条件, sql: where 列 is not null
isNull(String column, Obejct value) / isNotNull(String column, Obejct value):列是否为null/不为null判断
需求:查询 dept_id 为null 的员工信息
需求:查询 dept_id 不为null 的员工信息
java
@Test
public void testQuery4() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.isNull("dept_id"); // 为null
//wrapper.isNotNull("dept_id"); // 不为null
List<Employee> list = employeeMapper.selectList(wrapper);
System.out.println(list);
}
执行后SQL
sql
-- is null
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (dept_id is null)
-- is not null
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (dept_id is not null)
isNull(boolean condition, String column, Obejct value) / isNotNull(Boolean condition, String column, Obejct value):带开关的列是否为null/不为null判断
带开关的null/ isNotNull 操作, 使用跟上面操作一样
in/notIn
in重载4个方法, notIn重载4个方法
用法in: 列在指定列表数据中, sql: where 列 in (值1,值2,值3...)
用法notIn: 列不在指定列表数据中, sql: where 列 not in (值1,值2,值3...)
in(String column, Obejct...value) / notIn(String column, Obejct... value):可变参数方式
需求:查询 id = 1 或者 id = 2 或者 id = 3 的员工信息
需求:查询 id != 1 或者 id != 2 或者 id != 3 的员工信息
java
@Test
public void testQuery5() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.in("id",1L, 2L, 3L); // in
//wrapper.notIn("id",1L, 2L, 3L); //not in
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
-- in
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id IN (?,?,?))
-- not in
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id NOT IN (?,?,?))
in(String column, Collection<?> coll) / notIn(String column, Collection<!> coll):集合方式
java
@Test
public void testQuery5() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.in("id", Arrays.asList(1L, 2L, 3L)); //in
//wrapper.notIn("id",Arrays.asList(1L, 2L, 3L)); //not in
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
跟上面操作一模一样
in(boolean condition, String column, Obejct...value) / notIn(boolean condition, String column, Obejct... value):可变参数方式
in(boolean condition, String column, Collection<!> coll) / notIn(boolean condition, String column, Collection<!> coll):数组的方式
带开关的in/ notIn 操作, 使用跟上面操作一样
inSql/notInSql
inSql重载2个方法, notInSql重载2个方法
用法inSql: 列在指定列表数据中, sql: where 列 in (值1,值2,值3...)
用法notInSql: 列不在指定列表数据中, sql: where 列 not in (值1,值2,值3...)
inSql(String column,String value) / notInSql(String column, String value)
跟之前in /notIn 方法的区别是,方法参数value是一个sql片段
需求:查询 id = 1 或者 id = 2 或者 id = 3 的员工信息
需求:查询 id != 1 或者 id != 2 或者 id != 3 的员工信息
java
@Test
public void testQuery5() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.inSql("id","1, 2, 3"); // in,value是sql片段
//wrapper.notInSql("id","1, 2, 3"); //not in
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
-- in sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id IN (1,2,3))
-- not in sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id NOT IN (1,2,3))
inSql(boolean condition, String column, String value) / notInSql(boolean condition, String column, String value)
带开关的inSql/ notInSql 操作, 使用跟上面操作一样
模糊查询
like/notLike/likeLeft/likeRight
like 相关的方法有8个
用法:模糊查询,sql:
like: where 列 like "%值%"
notLike:where 列 not like "%值%"
likeLeft:where lie like "%值"
likeRight:where lie like "值%"
需求:查询名字中含有 "ye" 字样的员工信息 like
需求:查询名字中不含有 "ye" 字样的员工信息 notLikie
需求:查询名字中以 "ye" 字样结尾员工信息 likeLeft
需求:查询名字中以 "ye" 字样开头员工信息 likeRight
java
@Test
public void testQuery6() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "ye"); // like "%ye%"
//wrapper.notLike("name", "ye"); // not like "%ye%"
//wrapper.likeLeft("name", "ye"); // like "%ye"
//wrapper.likeRight("name", "ye"); // like "fei%"
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE ?)
-- "%ye%" -- like
-- "%ye" -- likeLeft
-- "ye%" -- likeRight
%%
where name like _a; //代表name列满足2个字符串,第二字符是a字样
逻辑运算
and/or/nested
and
用法:逻辑与, sql: where 条件1 and 条件2
需求:查询年龄介于18~30岁的员工信息
java
@Test
public void testQuery8() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//多个条件默认使用and
wrapper.ge("age", 18).le("age", 30);
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age >= ? AND age <= ?)
条件的链式连接,默认是使用and进行连接
or
用法:逻辑或, sql: where 条件1 or 条件2
需求:查询年龄小于18或大于30岁的员工信息
java
@Test
public void testQuery8() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//多个条件使用or
wrapper.lt("age", 18)
.or()
.gt("age", 30);
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age < ? OR age > ?)
多个条件连接默认使用and拼接,如果是or操作需要明确调用or方法。
逻辑条件嵌套
需求:查询名字带有"ye"字样,或者年龄介于18~30岁的员工信息
java
@Test
public void testQuery10() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "ye")
.or(wr -> wr.le("age", 30).ge("age", 18));
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE ? OR (age <= ? AND age >= ?))
需求:查询名字带有"ye"字样,并且年龄小于18或大于30岁的员工信息
java
@Test
public void testQuery11() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "ye")
.and(wr -> wr.lt("age", 18).or().gt("age", 30));
List<Employee> list = employeeMapper.selectList(wrapper);
}
执行后SQL
sql
SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE ? AND (age < ? OR age > ?))
nested
逻辑条件嵌套,等价于sqlwhere添加中的小括号,可以改变条件优先级。
需求:查询名字带有"ye"字样,或者年龄介于18~30岁的员工信息
java
@Test
public void testQuery10() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "ye").or()
.nested(wr -> wr.le("age", 30).ge("age", 18));
List<Employee> list = employeeMapper.selectList(wrapper);
}
需求:查询名字带有"ye"字样,并且年龄小于18或大于30岁的员工信息
java
@Test
public void testQuery11() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.like("name", "ye")
.nested(wr -> wr.lt("age", 18).or().gt("age", 30));
List<Employee> list = employeeMapper.selectList(wrapper);
}
分组查询
group by/having
group by
重载的方法有3个
分组函数,指定列进行封装,可以多个也可以1个
having
重置方法2个
用于筛选分组之后条件数据
用法:分组查询, sql: group by 列 having 条件
需求:查询每个部门员工个数
java
@Test
public void testQuery12() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("dept_id", "count(id) count");
wrapper.groupBy("dept_id");
List<Map<String, Object>> mapList = employeeMapper.selectMaps(wrapper);
mapList.forEach(System.out::println);
}
执行后SQL
sql
SELECT dept_id,count(id) count FROM employee GROUP BY dept_id
需求:查询每个部门员工个数,筛选出个数大于3的部门
java
@Test
public void testQuery13() {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.select("dept_id", "count(id) count");
wrapper.groupBy("dept_id");
//wrapper.having("count > 3");
wrapper.having("count > {0}", 3);
List<Map<String, Object>> mapList = employeeMapper.selectMaps(wrapper);
mapList.forEach(System.out::println);
}
执行后SQL
sql
-- count > 3
SELECT dept_id,count(id) count FROM employee GROUP BY dept_id HAVING count > 3
-- count > {0}
SELECT dept_id,count(id) count FROM employee GROUP BY dept_id HAVING count > ?
自定义SQL
上面的wrapper查询,针对简单sql场景非常简便,但是如果业务相对复杂,需要要求更灵活的SQL时(比如多表关联查询,动态sql等),wrapper有点力不从心了,此时可以使用自定义sql方式。这种方式不是啥新鲜货,其实就是还原之前的Mybatis的XxxxMapper.xml写法。
单表查询
Mapper接口
java
public interface EmployeeMapper extends BaseMapper<Employee> {
List<Employee> listByXmlSingle(); //自定义方法
}
Mapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.xxx.mp.mapper.EmployeeMapper" >
<resultMap id="BaseResultMap" type="cn.xxx.mp.domain.Employee" >
<id column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="admin" jdbcType="BIT" property="admin" />
<result column="dept_id" property="deptId" />
</resultMap>
<select id="listByXmlSingle" resultMap="BaseResultMap">
select id, name, password, email, age, admin, dept_id
from employee
</select>
</mapper>
测试
java
@Test
public void testQuery14() {
List<Employee> list = employeeMapper.listByXmlSingle();
}
关联查询
Mapper接口
java
public interface EmployeeMapper extends BaseMapper<Employee> {
List<Employee> listByXmlJoin();
}
Mapper.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="cn.xxx.mp.mapper.EmployeeMapper" >
<resultMap id="BaseResultMap" type="cn.xxx.mp.domain.Employee" >
<id column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="age" jdbcType="INTEGER" property="age" />
<result column="admin" jdbcType="BIT" property="admin" />
<result column="dept_id" property="deptId" />
<result column="d_id" property="dept.id" />
<result column="d_name" property="dept.name" />
<result column="d_sn" property="dept.sn" />
</resultMap>
<select id="listByXmlJoin" resultMap="BaseResultMap">
select e.*, d.id d_id, d.name d_name, d.sn d_sn from employee e left join department d on e.dept_id = d.id
</select>
</mapper>
测试
java
@Test
public void testQuery14() {
List<Employee> list = employeeMapper.listByXmlJoin();
}
到这常用的条件方法就介绍到这,剩下的条件方法同理即可,技巧就是sql的关键语法就是Wrapper对象的方法。
通用Service接口
概念
Java项目一般使用三层结构开发:
表现层:接收请求,调用业务方法处理请求,响应请求
业务层:也叫服务层,实现业务逻辑,调用持久层实现数据组合操作
持久层:完成数据的CRUD操作
前面讲的Mapper接口操作属于持久层,如果项目加入服务层,那代码该如何构建呢?
传统的业务层
使用MyBatis-Plus之前,传统业务层构建方式:以员工操作为例子
步骤1:构建员工业务层服务接口
java
public interface IEmployeeService {
void save(Employee employee);
void update(Employee employee);
void delete(Long id);
Employee get(Long id);
List<Employee> list();
}
步骤2:实现员工业务层接口
java
@Service
public class EmployeeServiceImpl implements IEmployeeService {
@Autowired
private EmployeeMapper mapper;
@Override
public void save(Employee employee) {
mapper.insert(employee);
}
@Override
public void update(Employee employee) {
mapper.updateById(employee); //必须全量更新
}
@Override
public void delete(Long id) {
mapper.deleteById(id);
}
@Override
public Employee get(Long id) {
return mapper.selectById(id);
}
@Override
public List<Employee> list() {
return mapper.selectList(null);
}
}
步骤3:实现服务方法测试
传统业务层服务方法需要自己调用Mapper接口方法去实现,相对麻烦。再看MyBatis-Plus提供简化操作
MyBatis-Plus业务层
步骤1:构建员工业务层服务接口
java
/**
*1>自定义一个业务服务接口:IEmployeeService,继承父接口:IService
*2>明确指定父接口泛型:当前接口操作实体对象:Employee
*/
public interface IEmployeeService extends IService<Employee> {
}
步骤2:实现员工业务层接口
java
/**
*1>定义服务接口实现类,实现IEmployeeService接口
*2>继承通用的父接口实现类:ServiceImpl
*3>明确指定通用的父接口实现类2个泛型:
* 1:当前服务类操作的实体对象对应的Mapper接口:EmployeeMapper
* 2:当前服务类操作的实体对象:Employee
*/
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {
}
步骤3:实现服务方法测试
java
@SpringBootTest
public class ServiceTest {
@Autowired
private IEmployeeService employeeService;
@Test
public void testSave(){
Employee employee = new Employee();
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("dafei@163.com");
employee.setName("dafei");
employee.setPassword("111");
employeeService.save(employee);
}
@Test
public void testUpdate(){
Employee employee = new Employee();
employee.setId(1327139013313564673L);
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("dafei@163.com");
employee.setName("zhangxiaosan");
employee.setPassword("111");
employeeService.updateById(employee);
}
@Test
public void testDelete(){
employeeService.removeById(11L);
}
@Test
public void testGet(){
System.out.println(employeeService.getById(11L));
}
@Test
public void testList(){
List<Employee> employees = employeeService.list();
employees.forEach(System.err::println);
}
}
常用服务层api
上面就是IService接口提供的实现方法,几乎涵盖了数据库常规操作。
方法解析
思考一个问题,IService接口是怎么实现的?
以IEmployeeService 接口中的getById() 方法为例子
java
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
default T getById(Serializable id) {
return getBaseMapper().selectById(id);
}
源码上,getById是一个接口默认方法,默认实现是调用getBaseMapper()对象去执行selectById方法
java
/**
* 获取对应 entity 的 BaseMapper
*
* @return BaseMapper
*/
BaseMapper<T> getBaseMapper();
getBaseMapper方法也是IService接口定义的方法,继续追踪其接口实现:
ServiceImpl类的getBaseMapper方法
java
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
@Autowired
protected M baseMapper;
@Override
public M getBaseMapper() {
return baseMapper;
}
}
从上面代码可以看到baseMapper是使用@Autowired 方式从spring容器中注入的,具体类型是泛型对象M, 而在定义EmployeeServiceImpl类时,具体代码:
java
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {
}
可以确认,以EmployeeServiceImpl为例子, 那么ServiceImpl中M的泛型就是EmployeeMapper类型,那么ServiceImpl可以等价
java
public class ServiceImpl<EmployeeMapper extends BaseMapper<T>, T> implements IService<T> {
@Autowired
protected EmployeeMapper baseMapper;
@Override
public EmployeeMapper getBaseMapper() {
return baseMapper;
}
}
最后IService方法中getById
java
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
default T getById(Serializable id) {
return getBaseMapper().selectById(id);
}
等价于:
java
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
default T getById(Serializable id) {
//其中的baseMapper就是employeeMapper
return baseMapper.selectById(id);
}
到这,可以确定IService接口中方法底层都是使用Mapper接口方法实现,那么IService 接口方法操作就可以同理可得啦。
使用小建议
MyBatis-Plus使用小建议
1>简单,单表操作项目首选MyBatis-Plus
2>复杂,多表操项目选择MyBatis-Plus +MyBatis
3>追求代码结构清爽,追求极致性能,代码有重构要求项目不建议使用MyBatis-Plus