1.简介
1.1 操作步骤
1.2 mybatis-plus mapper编写规则
2.注解介绍
2.1 常用注解
2.2 mybatis-plus通用Mapper接口
3.条件构造器
4.高级查询
4.1 列投影 select
4.2 排序
4.3 分组
4.4 条件查询
5.mybatis-plus业务层定义
5.1 实现分页逻辑
mybatis-plus :为简化开发而生
MyBatis-plus 是在Mybatis的基础上进行二次开发的具有MyBatis所有功能, 也添加了不少好用的功能(再不改变原有框架基础上,进行简化),一句话就是牛逼,用就对了
优点:
读懂官方这张图就理解个大概了
首先mybatis-plus 会扫描domain包下的实体类,扫描后,通过反射内省得到该实体对象的类名,字段名,进而分析自己数据中的表名与字段进行对比,然后拼接 表名 与列名 ,sql随之出来,注入到mybatis容器中
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.4.3version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.4.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.17version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.22version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.16version>
<scope>providedscope>
dependency>
dependencies>
4.改造完成后,新建domain包,mapper包
@Getter
@Setter
@ToString
public class Employee {
@TableId(type = IdType.AUTO) //id自动增长
private Long id;
private String name;
private String password;
private String email;
private int age;
@TableField("`admin`")
private int admin;
private Long deptId;
}
mybatis-plus 会自动的将dept_id 中的 _ 转换成java中的驼峰命名法 deptId
5.启动类贴上对应注解 ,然后就可以进行测试了
public interface EmployeeMapper extends BaseMapper<Employee> {
}
编写对应测试类,注意:Springboot2.3版本后,测试用的是Junit5
以员工Mapper为例
问题1:EmployeeMapper接口并没有编写CRUD方法,为什么测试类中可以直接使用
EmployeeMapper接口继承BaseMapper接口,可以继承BaseMapper接口中所有crud方法
问题2:项目中并没有编写EmployeeMapper.xml,也没有crud sql操作,为什么能进行crud操作
mybatis-plus框架帮忙写了 ,怎么写的?
以查询所有数据为例子 :
接口方法:employeeMapper.selectList()
执行sql:select id,name,age from employee
分析: sql语句中表名跟domain中类名一致
sql语句中列名跟domain中字段名一致
原理: mybatis-plus启动后悔解析BaseMapper接口汇总指定的泛型类型(实体),通过这个类型字节码对象
反射内省得到该实体对象的类名,字段名,进而拼接 表名 与列名 ,sql随之出来
@TableField(value="") //当前这个字段映射指定名字的列
@TableField(exist=false) //不将当前字段作为表的映射列
当数据库表名与实体类中的名字不一样时,可以使用
@TableName("表名") //当前类名映射表名 这张表
标记当前属性映射表主键
value="" 映射表名
type=IdType.AUTO :指定id操作 (AUTO主键自动增长)
IdType.NONE:
无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
乐观锁注解、标记 @Verison 在字段上
作用:用于标记乐观锁操作字段 ---- 后续补充
mybatis-plus 做添加操作时,如果属性值为空,不会将属性作为insert sql中的列
updateById(id)
:根据 ID 修改update(entity, wrapper)
:根据 whereEntity 条件,更新记录实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)1.传入的参数(实体)对象,如果属性值为空(null),那么该属性不参与sql 列拼接
2.如果传入参数(实体)对象属性为基本属性,它们有默认值,mybatis-plus认为是属性值不为null,这些属性会参与 sql 列拼接
如何不想让有默认值的基本属性参与 sql 列拼接?
方案一:将基本属性字段设置包装类型
方案二:使用BaseMapper中的update(null,wrapper) [部分字段更新方式]
实体对象设置为null ,wrapper设置更新条件
UpdateWrapper<实体类> wrapper= new UpdateWrapper<>();
wrapper.eq() :可以暂时理解为sql where拼接条件
wrapper.set() :可以暂时理解为sql set拼接条件
两个参数 : 正常set操作
三个参数: 参数一:booean为true才拼接后面的set操作
wrapper.setSql("age=18"); //不需要占位符,直接塞sql片段
使用区别
updateById 适用范围:
update适用范围:
deleteById(id)
:根据 ID 删除deleteBatchIds(idList)
:删除(根据ID 批量删除)deleteByMap(map)
:根据 columnMap 条件,删除记录delete(wrapper)
:根据 entity 条件,删除记录selectById(id)
:根据 ID 查询selectBatchIds(idList)
:查询(根据ID 批量查询)selectByMap(map)
:查询(根据 columnMap 条件)selectCount(wrapper)
:根据 Wrapper 条件,查询总记录数 返回值是 IntegerselectList(wrapper)
:根据 entity 条件,查询全部记录selectMaps(wrapper)
:根据 Wrapper 条件,查询全部记录,使用场景 :没法使用对象封装数据的时候,可以使用map封装QueryWrapper<Employee> wrapper =new QueryWrapper<>();
List<Map<String,Object>> mapList =employeeMapper.selectMaps(wrapper);
selectPage(page, wrapper)
:根据 entity 条件,查询全部记录(并翻页)分页操作//分页拦截器
在启动类中加入拦截配置
//分页逻辑
QueryWrapper<Employee> wrapper =new QueryWrapper<>();
IPage :mybatis-plus分页对象等价之前的PageResult 或者PageInfo
Ipage<Employee> page =new Page (2,3) //参数1:当前页,参数2:每页显示条数
employeeMapper.selectPage(page,wrapper);
启动类配置分页拦截
//分页拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true); //合理化
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
Wrapper:条件构造抽象类
叫做:sql片段更准确
推荐使用LambdaUpdateWrapper更新 LambdaQueryWrapper查询
LambdaUpdateWrapper :编译阶段避免了列名之类的出错
LambdaUpdateWrapper<Employee> wrapper =new LambdaUpdateWrapper<>();
Wrapper.eq(Employee::getId,1L);
Wrapper.eq(Employee::getAge,1L);
------------------
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getName,"yue").eq(Employee::getAge,18);
@SpringBootTest
public class MapperTest {
@Autowired
private EmployeeMapper employeeMapper;
@Test //添加
public void testSave(){
Employee employee = new Employee();
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("[email protected]");
employee.setName("小罗");
employee.setPassword("111");
employeeMapper.insert(employee);
}
//----------------------------------------------------------
@Test //更新
public void testUpdate(){
Employee employee = new Employee();
employee.setId(22L);
employee.setAdmin(1);
employee.setAge(18);
employee.setDeptId(1L);
employee.setEmail("[email protected]");
employee.setName("xuan");
employee.setPassword("111");
employeeMapper.updateById(employee);
}
@Test
//演示updateById ,不为null的数据丢失
public void testUpdate2(){
Employee employee = new Employee();
employee.setId(22L);
// employee.setAge(18);
employee.setDeptId(2L);
employee.setName("kent");
employeeMapper.updateById(employee);
}
@Test //部分更新
public void testUpdate3(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id",22l);
wrapper.set("dept_id",3L);
wrapper.set("name","qqq");
employeeMapper.update(null,wrapper);
//lambda
LambdaUpdateWrapper<Employee> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(Employee::getId,"22L");
lambdaUpdateWrapper.set(Employee::getAge,18);
lambdaUpdateWrapper.set(Employee::getDeptId,2L);
lambdaUpdateWrapper.set(Employee::getName,"yue");
employeeMapper.update(null,lambdaUpdateWrapper);
}
//-------------------------------------------------------------------------
@Test
//删除单条
public void testDelete(){
employeeMapper.deleteById(23L);
}
@Test
//批量删除 DELETE FROM employee WHERE id IN ( ? , ? , ? )
public void testDelete2(){
employeeMapper.deleteBatchIds(Arrays.asList(24L,25L,26L));
}
@Test
//根据条件删除Map DELETE FROM employee WHERE name = ? AND age = ?
public void testDelete3(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","ff");
map.put("age",18);
employeeMapper.deleteByMap(map);
}
@Test
//根据 entity 条件,删除记录 DELETE FROM employee WHERE (id = ?)
public void testDelete4(){
UpdateWrapper wrapper = new <Employee>UpdateWrapper();
wrapper.eq("id",28L);
employeeMapper.delete(wrapper);
}
//---------------------------------------------------------------
//查询操作
@Test //根据id查询
public void testGet(){
System.out.println(employeeMapper.selectById(22L));
}
@Test //查询多个员工
public void testList2(){
List<Employee> employees = employeeMapper.selectBatchIds(Arrays.asList(1L, 2L));
employees.forEach(System.out::println);
}
//查询(根据 columnMap 条件)
@Test
public void testList3(){
HashMap<String, Object> map = new HashMap<>();
map.put("id",20L);
List<Employee> employees = employeeMapper.selectByMap(map);
employees.forEach(System.out::println);
}
//根据 Wrapper 条件,查询总记录数
@Test //SELECT COUNT( 1 ) FROM employee
public void testList4(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
employeeMapper.selectCount(wrapper); //没有给条件相当于传null
}
//根据 entity 条件,查询全部记录
//需求: 查询满足条件的所有的员工信息, 返回List
@Test
public void testList5(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.out::println);
}
@Test //查询全部员工
public void testList(){
System.out.println(employeeMapper.selectList(null));
}
//根据 Wrapper 条件,查询全部记录 Maps
@Test
public void testList6(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.eq("name","yue");
List<Map<String, Object>> mapList = employeeMapper.selectMaps(wrapper);
mapList.forEach(System.out::println);
}
//根据 entity 条件,查询全部记录(并翻页) 分页查询
@Test //查询全部员工
public void testList7(){
//1.分页拦截器,启动类中编写拦截配置
//2.分页逻辑
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
IPage<Employee> page =new Page<>(2,3);
employeeMapper.selectPage(page,wrapper);
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());
}
//使用LambdaQueryWrapper查询
//需求:查询name=yue, age=18的用户
@Test //查询全部员工
public void testList8(){
LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getName,"yue").eq(Employee::getAge,18);
employeeMapper.selectList(wrapper);
}
//----------------------------------------------------------------------------------
// UpdateWrapper set setSql使用
@Test
public void testSet(){
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id",20L);
//wrapper.set("name","zhuzhu");
wrapper.setSql("name='qqqq'");
employeeMapper.update(null,wrapper);
}
//需求:将id=1的员工age改为18, 如果传入uname变量值不等于null或者“”,修改为员工name为uname变量值
@Test
public void testSet2(){
String uname="hello";
UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
wrapper.eq("id",20L);
if (StringUtils.hasText(uname)){
wrapper.set("name",uname);
}
employeeMapper.update(null,wrapper);
}
}
// 列投影
//需求:查询所有员工 ,返回员工name ,age列
@Test
public void testQuery(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//wrapper.select("name","age"); //使用列投影
wrapper.select("name , age"); //sql片段
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
// 排序
//需求:查询所有员工age 正序, age相同 --id倒序排
@Test
public void testQuery2(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.orderByAsc("age").orderByDesc("id");
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
// 分组
//需求:以部门id进行分组查询,查询每个部门员工个数
@Test
public void testQuery3(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.groupBy("dept_id");
wrapper.select("dept_id","count(id) count");
List<Map<String, Object>> maps = employeeMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
//需求:以部门id进行分组查询,查询每个部门员工个数 部门个数大于3的 having
@Test
public void testQuery4(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.groupBy("dept_id");
wrapper.select("dept_id","count(id) count");
//wrapper.having("count>3");
wrapper.having("count>{0}",3);
List<Map<String, Object>> maps = employeeMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
allEq /eq /ne
allEq : 全等匹配
allEq : 全等匹配(带条件过滤的)
eq:单个参数判断是否相等
ne: 不等于
//比较运算符 allEq eq ne
@Test
public void testQuery5(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
// WHERE (id = ? AND age = ?)
wrapper.eq("id",21L).eq("age",18);
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
@Test
public void testQuery6(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
HashMap<String, Object> map = new HashMap<>();
map.put("name","yue");
map.put("age",18);
wrapper.allEq(map);
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
@Test //查询姓名 不等于yue ,age 不为18的值
public void testQuery7(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//WHERE (name <> ? AND age <> ?)
wrapper.ne("name","yue").ne("age",18);
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
gt / ge / lt /le /between / or
gt : 大于 > lt:小于 <
ge:大于等于 >= le:小于等于 <=
between : BETWEEN 值1 AND 值2
or : 拼接 OR
// gt / ge / lt /le /between / or
@Test
public void testQuery8(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
//WHERE (age > ? AND age < ?)
wrapper.gt("age",18).lt("age",40);
//WHERE (age >= ? AND age <= ?)
wrapper.ge("age",18).le("age",40);
//WHERE (age BETWEEN ? AND ?)
wrapper.between("age",18,45);
//WHERE (age > ? OR age < ?)
//注意:多个条件默认是and需要or,调用or()
wrapper.gt("age",40).or().lt("age",18);
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
isNull isNotNull ,in inSql ,like likeLeft
like: LIKE '%值%
notLike : NOT LIKE '%值%'
likeLeft : LIKE '%值'
//isNull isNotNull ,in inSql ,like likeLeft
@Test
public void testQuery9(){
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
wrapper.isNull("age"); //where age is null
wrapper.isNotNull("age"); //where age is not null
wrapper.in("id",1L,20L,19L);//where in in(1,20,19)
wrapper.inSql("id","1,2,3"); //sql片段
wrapper.like("name","y"); //where name like '%y%'
wrapper.likeLeft("name","罗"); //where name like '%罗'
List<Employee> employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
嵌套逻辑拼接
wrapper.like("name","x").and(wr->wr.lt().or().gt())
//需求:查询所有员工 ,返回员工name ,age列
wrapper.select("name","age"); //列投影:列表查出指定列,其他列不查
wrapper.select("name, age"); //sql片段,把片段塞进sql查询语句
//需求:查询所有员工age 正序, age相同 --id倒序排
wrapper.orderByAsc("age").orderByDesc("id")
//参数1:满足排序条件 参数2:排序策略(true:正序,false:倒序)
wrapper.orderBy(true,true,"age")
//需求:以部门id进行分组查询,查询每个部门员工个数
employeeMapper.selectMaps()
wrapper.select("dept_id","count(id) count")
wrapper.group("dept_id");
将大于3的部门筛选
wrapper.having("count>3")
或者 wrapper.having("count>{0}",3)
比较运算符
//需求,查询name=xuan ,age=18
1. wrapper.eq("name","xuan").eq("age",18) // AND操作
2. map.put("name","xuan")
map.put("age",18)
wrapper.allEq(map) //AND操作
3.ne //<>不等于操作
wrapper.ne("age",18)
4.between("age","12","13") age between 2 and 3
5.wrapper.isNull("age") //where age is null
6.wrapper.isNotNull("age") //where age is not null
7.wrapper.in("id",1L,2L) //where in in(1,2)
wrapper.inSql("id","1, 2"); //sql片段
模糊查询
//查询name中含有x字样的员工
wrapper.like("name","x") //where name like '%x%'
wrapper.likeLeft("name","x") // like '%x'
逻辑运算
大于等于 ge() 小于等于 le() 多个条件默认是and需要or,调用or()
lt() .or(). gt()
嵌套逻辑拼接
wrapper.like("name","x").and(wr->wr.lt().or().gt())
mybatis-plus是没有办法做多表关联查询,即无法使用join,但是它支持额外sql
如果需要关联查询:如果采用额外sql方式,直接用mybatis-plus
mybatis-plus 通用service层接口:
EmployeeServiceImpl extends ServiceImpl
mybatis-plus 通用service层实现类定义规则:
public interface IEmployeeService extends IService<Employee> {
IPage<Employee> query(EmployeeQueryObject qo);
}
EmployeeServiceImpl实现类
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper, Employee> implements IEmployeeService {
/*@Autowired
private EmployeeMapper employeeMapper;*/
@Override
public IPage<Employee> query(EmployeeQueryObject qo) {
QueryWrapper<Employee> wrapper = new QueryWrapper<>();
IPage<Employee> page = new Page<>(qo.getCurrentPage(),qo.getPageSize());
return super.page(page,wrapper);
//return super.getBaseMapper().selectPage(page, queryWrapper);
//return employeeMapper.selectPage(page,wrapper);
}
}
测试类
//service 分页操作
@Test
public void testMapper3(){
EmployeeQueryObject qo = new EmployeeQueryObject();
qo.setPageSize(3);
qo.setCurrentPage(2);
IPage<Employee> page = employeeService.query(qo);
System.out.println("当前页:" + page.getCurrent());
System.out.println("总页数:" + page.getPages());
System.out.println("每页显示条数:" + page.getSize());
System.out.println("总记录数:" + page.getTotal());
System.out.println("当前页显示记录:" + page.getRecords());
employeeService.query(qo);
}