一、why
不做重复的 CRUD 操作
问题1:EmployeeMapper 没有写 crud 方法,为什么在测试类中可以使用?
因为 EmployMapper 接口继承 BaseMapper 接口 ,该接口定义了一系列 crud 方法
问题2:代码不需要写 crud sql 语句,那为什么可以进行 crud 数据操作?
因为 Mybatis-Plus 在项目启动的时候执行 sql 解析
获取 BaseMapper 接口中指定的 Employee 泛型
解析泛型数据,得到 employee 的字节码对象
使用内省方法解析 employee 字节码对象,得到类和属性名
Mybatis-Plus 以类名作为表名,属性名作为列名,进行 sql 拼接,最终得到对应 sql
二、what
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
三、how
添加依赖
org.springframework.boot
spring-boot-starter-parent
2.4.3
com.baomidou
mybatis-plus-boot-starter
3.4.0
com.alibaba
druid-spring-boot-starter
1.1.17
mysql
mysql-connector-java
8.0.22
org.springframework.boot
spring-boot-starter-test
org.projectlombok
lombok
1.18.16
provided
Mysql 8 注意时区问题,配置文件的 url 加上 serverTimezone=UTC
application.properties
#mysql
spring.datasource.url=jdbc:mysql:///mybatis-plus_demo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
spring.datasource.username=admin
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
日志
在 application.properties
中有两种配置 sql 打印日志方式
第一种:
logging.level.cn.jere.plus.mapper=debug
第二种:
# 推荐使用,会自动换行,看 sql 语句比较舒服
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
1、常用注解
@TableName
作用:指定当前实体类映射哪张数据库表, 默认是跟实体类名一致
@TableName("t_employee")
public class Employee {
//.....
}
@TableField
作用:指定当前属性映射数据库表哪一列, 默认是跟属性名一致
@TableName("employee")
public class Employee {
@TableField(value="eName",exist = false)
private String name;
}
exist 属性表示当前属性是否映射数据库列。实体类中若该属性在数据库没有的对应的列,要加exist=false
@TableId
作用:标记当前属性映射表主键。
@TableName("employee")
public class Employee {
@TableId(type = IdType.AUTO) // IdType.AUTO 数据库ID自增
private Long id;
IdType.ASSIGN_UUID 默认的 id 策略,使用雪花算法生成 long 类型的唯一 id(雪花算法用于分布式微服务)
@Version
作用:用于标记乐观锁操作字段
2、通用 Mapper 接口
insert
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
update
-
updateById (要注意有没有基本数据类型字段)
使用场景:1. 条件是 id 是更新操作;2. 全量更新
// 需求: 将id=1用户名字修改为jere
@Test
public void updateById() {
Employee employee = new Employee();
employee.setId(1L);
employee.setName("jere");
employeeMapper.updateById(employee);
}
sql : UPDATE employee SET name=?, age=?, admin=? WHERE id=?
tips : updateById 在拼接 sql 时,把所有非 null 字段都进行 set 拼接
-
update
使用场景:1. 条件不仅仅是 id 更新场景(多条件);2. 部分字段更新
@Test
public void update() {
UpdateWrapper wrapper = new UpdateWrapper<>();
wrapper.eq("id",1L);
wrapper.set("name","jere24");
employeeMapper.update(null,wrapper); //
}
sql : UPDATE employee SET name=? WHERE (id = ?)
wrapper.条件 .set更新字段
delete
-
deleteById(id)
// 需求:删除id=20的员工信息 @Test public void deleteById() { employeeMapper.deleteById(20L); // DELETE FROM employee WHERE id=? }
-
deleteBatchIds(idList)
// 需求:删除 id=20,id=21 的员工信息 @Test public void deleteBatchIds() { employeeMapper.deleteBatchIds(Arrays.asList("20L","21L")); // DELETE FROM employee WHERE id IN ( ? , ? ) }
-
deleteByMap(map)
// 需求:删除name=jere并且age=18的员工信息 @Test public void testDeleteByMap() { HashMap
map = new HashMap(); map.put("name","jere"); map.put("age",18); employeeMapper.deleteByMap(map); // DELETE FROM employee WHERE name = ? AND age = ? } -
delete(wrapper)
//需求:删除name=dafei并且age=18的员工信息 @Test public void testDeleteWrapper() { UpdateWrapper
wrapper = new UpdateWrapper<>(); wrapper.eq("name","dafei").eq("age",18); employeeMapper.delete(wrapper); // DELETE FROM employee WHERE (name = ? AND age = ?) }
select
selectById(id)
-
selectBatchIds(idList)
// 需求:查询id=1,id=2的员工信息 @Test public void testSelectBatchIds(){ employeeMapper.selectBatchIds(Arrays.asList(1L,2L)); // SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE id IN ( ? , ? ) }
-
selectByMap(map)
// 需求: 查询name=dafei, age=18的员工信息 @Test public void testSelectByMap(){ HashMap
map = new HashMap<>(); map.put("name","dafei"); map.put("age",18); employeeMapper.selectByMap(map); // SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE name = ? AND age = ? } -
selectCount(wrapper)
// 需求: 查询满足条件的所有的员工个数 @Test public void testSelectCount(){ QueryWrapper
wrapper = new QueryWrapper(); employeeMapper.selectCount(wrapper); // SELECT COUNT( 1 ) FROM employee employeeMapper.selectCount(null); // 查询全部 SELECT COUNT( 1 ) FROM employee }
-
selectList(wrapper)
//需求: 查询满足条件的所有的员工信息, 返回List
@Test public void testSelectList(){ QueryWrapper wrapper = new QueryWrapper(); employeeMapper.selectList(wrapper).forEach(System.err::println); // SELECT id,name,password,email,age,admin,dept_id FROM employee }
问题:什么时候使用 selectList ? 什么时候使用 selectMaps ?
如果sql执行完之后的列能封装成实体对象,选用 selectList,如果不能则使用 selectMaps
-
selectMaps(wrapper)
用了 group by 返回的列没办法封装到实体对象中,不能用selectList(它的返回值是泛型为实体类的List集合),所以只能用selectMaps
可以看成:对象即 Map , Map 即对象
//需求: 查询满足条件的所有的员工信息, 返回List
-
selectPage(page, wrapper)
// 需求:查询第二页员工数据, 每页显示3条, (分页返回的数据是实体对象) @Test public void testSelectPage(){ QueryWrapper
wrapper = new QueryWrapper<>(); //参数1:当前页, 参数2:每页显示条数 Page page = new Page<>(2, 3); employeeMapper.selectPage(page, wrapper); // SELECT id,name,password,email,age,admin,dept_id FROM employee LIMIT ?,? 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()); }
-
selectOne(wrapper)【拓展】
//需求: 查询满足条件的所有的员工, 取第一条 @Test public void testSelectOne(){ QueryWrapper
wrapper = new QueryWrapper<>(); Employee employee = employeeMapper.selectOne(wrapper); // 查出结果条数大于1,则抛出异常 Expected one result (or null) to be returned by selectOne(), but found: 19 System.out.println(employee); }
-
selectObjs(wrapper)【拓展】
//需求: 查询满足条件的所有的员工, 返回排在第一的列所有数据, 没特别指定, 一般返回时id @Test public void testSelectObjs(){ QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.select("name"); //挑选返回的列 SELECT name FROM employee List
-
selectMapsPage(page, wrapper)【拓展】
@Test public void testSelectMapsPage(){ QueryWrapper
wrapper = new QueryWrapper<>(); //参数1:当前页, 参数2:每页显示条数 IPage
3、条件构造器
继承体系
更新操作
UpdateWrapper 更新
@Test
public void testUpdate2() {
UpdateWrapper wrapper = new UpdateWrapper();
wrapper.eq("id", 1).set("name", "jere");
employeeMapper.update(null, wrapper); // UPDATE employee SET name=? WHERE (id = ?)
}
@Test
public void testUpdate2() {
UpdateWrapper wrapper = new UpdateWrapper();
wrapper.eq("id", 1);
wrapper.setSql("name='jere'");// sql 片段方式, 上面用 set 是占位符形式
employeeMapper.update(null, wrapper); // UPDATE employee SET name='jere' WHERE (id = ?)
}
LambdaUpdateWrapper 更新
@Test
public void testUpdate3() {
LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper();
wrapper.eq(Employee::getId, 1L).set(Employee::getName,"Jere");
employeeMapper.update(null, wrapper); // UPDATE employee SET name=? WHERE (id = ?)
}
查询操作
QueryWrapper 查询
// 需求:查询name=jere, age=18的用户
@Test
public void testQuery1(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("name", "jere").eq("age", 18);
System.out.println(employeeMapper.selectList(wrapper));
}
LambdaQueryWrapper 查询
// 需求:查询name=dafei, age=18的用户
@Test
public void testQuery3(){
LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Employee::getName, "dafei").eq(Employee::getAge, 18);
System.out.println(employeeMapper.selectList(wrapper));
}
工具类 Wrappers
作用:用于创建各种类型的wrapper
@Test
public void testWrappers(){
//update
UpdateWrapper updateWrapper1 = new UpdateWrapper<>();
UpdateWrapper updateWrapper2 = Wrappers.update();
LambdaUpdateWrapper lambdaUpdateWrapper1 = new LambdaUpdateWrapper<>();
LambdaUpdateWrapper lambdaUpdateWrapper2 = Wrappers.lambdaUpdate();
//UpdateWrapper -->LambdaUpdateWrapper
LambdaUpdateWrapper lambdaUpdateWrapper3 = updateWrapper1.lambda();
//select
QueryWrapper QueryWrapper1 = new QueryWrapper<>();
QueryWrapper QueryWrapper2 = Wrappers.query();
LambdaQueryWrapper lambdaQueryWrapper1 = new LambdaQueryWrapper<>();
LambdaQueryWrapper lambdaQueryWrapper2 = Wrappers.lambdaQuery();
//QueryWrapper -->LambdaQueryWrapper
LambdaQueryWrapper lambdaQueryWrapper3 = QueryWrapper1.lambda();
}
4、高级查询
列投影
select
// 需求:查询所有员工, 返回员工name, age列
@Test
public void testQuery1() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.select("name,age"); // 用别名就封装不到 employee 对象了
List employees = employeeMapper.selectList(wrapper);
employees.forEach(System.err::println);
}
排序
orderByAsc / orderByDesc
orderBy
// 需求:查询所有员工信息按age正序排, 如果age一样,按id正序排
@Test
public void testQuery2() {
QueryWrapper wrapper = new QueryWrapper();
boolean flag = true;
// 参数1:当前参数为 true 时,才执行排序逻辑
// wrapper.orderByDesc(flag,"age");
// 参数1:是否排序开关,true表示排序,false表示不排序
// 参数2:是否正序排,true表示正序排,false表示倒序排
// 参数3:排序的列
wrapper.orderBy(flag, true, "age", "id");
// SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC,id ASC
List employees = employeeMapper.selectList(wrapper);
}
// 需求:查询所有员工信息按age正序排, 如果age一样, 按id倒序排
@Test
public void testQuery3() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.orderByAsc("age").orderByDesc("id");
// SELECT id,name,password,email,age,admin,dept_id FROM employee ORDER BY age ASC,id DESC
employeeMapper.selectList(wrapper);
}
分组查询
groupBy
having
// 需求: 以部门id进行分组查询,查每个部门员工个数
@Test
public void testQuery4() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.select("dept_id,count(1)");
wrapper.groupBy("dept_id");
List
group by 什么,则只能 select 什么,统计函数除外
// 需求: 以部门id进行分组查询,查每个部门员工个数,将大于3人的部门过滤出来
@Test
public void testQuery5() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.select("dept_id,count(1) count");
wrapper.groupBy("dept_id").having("count(1)>3");
List
条件查询
1. 比较运算符
allEq / eq / ne
//需求:查询name=dafei,age=18的员工信息
@Test
public void testQuery6() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("name", "Jere").eq("age", 18);
List employeeList = employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND age = ?)
}
链式编程返回的是自己,然后继续执行后面的
// 需求:查询满足条件员工信息,注意传入的map条件中,包含a的列才参与条件查询
@Test
public void testQuery7() {
QueryWrapper wrapper = new QueryWrapper<>();
Map map = new HashMap<>();
map.put("name", "dafei");
map.put("age", 18);
wrapper.allEq((k, v) -> k.contains("a"), map);
employeeMapper.selectList(wrapper);
//SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name = ? AND age = ?)
}
//需求:查询name !=dafei员工信息
@Test
public void testQuery8() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.ne("name", "Jere");
List employees = employeeMapper.selectList(wrapper);
}
gt / ge / lt / le
// 需求:查询 age 大于18岁员工信息
@Test
public void testQuery9() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.gt("age", 18);
employeeMapper.selectList(wrapper);
}
between / notBetween
//需求:查询年龄介于18~30岁的员工信息
@Test
public void testQuery10() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.between("age", 18, 30);
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age BETWEEN ? AND ?)
}
//需求:查询年龄小于18或者大于30岁的员工信息【用between实现】
@Test
public void testQuery11() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.notBetween("age", 18, 30);
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age NOT BETWEEN ? AND ?)
}
isNull / isNotNull
// 需求: 查询dept_id 为null 员工信息
@Test
public void testQuery12() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.isNull("dept_id");
employeeMapper.selectList(wrapper);
//SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (dept_id IS NULL)
}
// 需求: 查询dept_id 为不为null 员工信息
@Test
public void testQuery13() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.isNotNull("dept_id");
employeeMapper.selectList(wrapper);
//SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (dept_id IS NOT NULL)
}
in/notIn / inSql / notInSql
// 需求: 查询id为1, 2 的员工信息
@Test
public void testQuery14() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.in("id", 1, 2);
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id IN (?,?))
}
// 需求: 查询id不为1, 2 的员工信息
@Test
public void testQuery15() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.notIn("id", 1, 2);
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id NOT IN (?,?))
}
//需求: 查询id为1, 2 的员工信息
@Test
public void testQuery16() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.inSql("id", "1,2");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id IN (1,2))
}
//需求: 查询id不为1, 2 的员工信息
@Test
public void testQuery17() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.notInSql("id", "1,2");
employeeMapper.selectList(wrapper); // SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (id NOT IN (1,2))
}
exists / notExists【拓展】
@Test
public void testQuery18() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.exists("select id from department where sn='cwb'");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (EXISTS (select id from department where sn='cwb'))
}
2. 模糊查询
like / notLike
//需求: 查询name中含有fei字样的员工
@Test
public void testQuery19() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.like("name", "fei");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee where (name LIKE '%fei%');
}
// 需求: 查询name中不含有fei字样的员工
@Test
public void testQuery20() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.notLike("name", "fei");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name NOT LIKE '%fei%');
}
likeLeft / likeRight
// 需求: 查询name以fei结尾的员工信息
@Test
public void testQuery21() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.likeLeft("name", "fei");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE '%fei');
}
// 需求: 查询姓王的员工信息
@Test
public void testQuery22() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.likeRight("name", "王");
employeeMapper.selectList(wrapper);
// SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE '王%')
}
3. 逻辑运算符
or / and
注意:需要加括号的话在or / and(里面使用 lambda 表达式)
// 需求: 查询age = 18 或者 name=Jere 或者 id =1 的用户
@Test
public void testQuery24() {
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("age",18);
wrapper.or();
wrapper.eq("name","Jere");
wrapper.or();
wrapper.eq("id",1);
employeeMapper.selectList(wrapper);
//SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (age = ? OR name = ? OR id = ?)
}
// 需求:查询name含有re字样的,或者 年龄在18到30之间的用户
@Test
public void testQuery25() {
/* QueryWrapper wrapper = new QueryWrapper();
wrapper.like("name","re");
wrapper.or();
wrapper.between("age",18,30);
employeeMapper.selectList(wrapper);*/
QueryWrapper wrapper = new QueryWrapper();
wrapper.like("name","re");
wrapper.or(wp->wp.ge("age",18).le("age",30));
employeeMapper.selectList(wrapper);
//SELECT id,name,password,email,age,admin,dept_id FROM employee WHERE (name LIKE ? OR (age >= ? AND age <= ?))
}
自定义SQL
-
mapper.xml方式
-
注解方式【拓展】
@Select("select e.* from employee e") List
listByAnnoSingle(); @Select("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") @Results({ @Result(column="d_id", property = "dept.id"), @Result(column="d_name", property = "dept.name"), @Result(column="d_sn", property = "dept.sn") }) List
listByAnnoJoin();
5、通用 Service 接口
1. 定义
Mybatis-Plus 服务层接口定义
- 自定义一个接口继承 IService 接口
- 明确指定操作实体对象的泛型
public interface IEmployeeService extends IService {
//....
}
Mybatis-Plus 服务层实现类定义
- 自定义一个类,继承 ServiceImpl 类,同时实现自定义服务成接口
- 明确指定两个泛型:第一个是操作实体类的 mapper 接口,第二个是操作的实体类
@Service
@Transactional
public class EmployeeServiceImpl extends ServiceImpl implements IEmployeeService {
//....
}
2. 分页
步骤1:在启动类配置分页插件
//分页
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true); //合理化
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
步骤2:编写分页代码
@Getter
@Setter
public class QueryObject {
private int currentPage = 2;// 当前页
private int pageSize = 3;// 每页显示条数
private String keyword;// 关键字
}
//2>query(EmployeeQuery)方法定义
//3>query(EmployeeQuery)方法实现
@Override
public IPage query(QueryObject qo) {
IPage page = new Page<>(qo.getCurrentPage(), qo.getPageSize()); //设置分页信息
QueryWrapper wrapper = Wrappers.query(); //拼接条件
return super.page(page, wrapper);
}
//需求:查询第2页员工信息, 每页显示3条, 按id排序
@Test
public void testPage(){
QueryObject qo = new QueryObject();
qo.setPageSize(3);
qo.setCurrentPage(2);
IPage 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());
}
3. 事务
贴 @Transactional 注解