MyBatisPlus
- MyBatisPlus简称mp,是mybatis的增强工具,在mybatis的基础上只做增强,不做修改
- 特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
- 构建mybatis-plus-demo
- 导入依赖
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 - 新建application.properties文件
server.port=80 #这里记得配置下时区serverTimezone(东八区),否则可能会报错 spring.datasource.url=jdbc:mysql:///mybatis-plus?useUnicode=true&\ characterEncoding=utf8&serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=admin spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # 配置slq打印日志 logging.level.cn.wolfcode.mp.mapper=debug
- 按照以往常规操作,我们做crud,需要编写mapper接口,并在mapper.xml中编写对应的sql语句,但是在mybatisPlus中,只需创建好对应的mapper接口,继承下 BaseMapper<实体类泛型(如:Employee)>,常规的crud都能在BaseMapper中找到,所以就无须我们在自己的mapper接口中写了(如:EmployeeMapper),具体操作如下
- 当spring容器启动后,mybatisPlus就开始解析EmployeeMapper,上面指定了泛型Employee
- 通过反射拿到类型Employee,然后得到其类名作为数据库表名,得到其属性作为数据库表中对应的列名
- 将解析出来的数据根据调用的方法来拼接出不同的语句.
- mybatisPlus中的方法及注意点:
- 常用注解:
- @TableName表名注解:指当前类映射数据库中哪一张表,默认实体类与表名一致,当不一致的使用时:如数据库表名为t_employee,实体类名为Employee,因为名字不一致,所以直接肯定是映射不到的,此时只需在实体类Employee上贴上@TableName("t_employee")即可,表示该实体类映射到t_employee这张表
- @TableId主键注解:标记当前属性映射表主键:如
@TableName("employee") public class Employee { //value="id"对应数据库的列名,IdType.AUTO表示自增长(还可以有其他的设置方式,如自己输入,详情可查看对应的api) @TableId(value="id", type=IdType.AUTO) private Long id; }
- @TableField字段注解(非主键):指定当前属性映射数据库表哪一列, 默认是跟属性名一致,如:
//其中:exist属性表示当前属性是否映射数据库列,当我们是自定义的属性,就额外加的,不是和数据库对应的,那么就必须@TableField(exist = false)这样设置下,否则会报错 @TableField(value="employename",exist = true) private String name;
- updateById:根据id修改,等价于update employee set xxx=xxx where id = xxx
- updateById的注意点:(它的set片段会根据传入的实体对象(如:employee)对应的属性是否为null来判断是否拼接)
- 当对应的属性为null时,那么set片段中就不会把对应的属性拼接上去(即:执行更新操作后,数据库中对应的列值不会发生变化)
- 当对应的属性不为null时,便会拼接到set片段中,当属性为基本类型时(都有默认值),那么始终都会拼接上去发生修改,那要如何解决这个问题呢?
- 将基本数据类型改为包装类型
- 使用update方法,而不是使用updateById方法
- update:等价于:update employee set xxx = xxx where 其他列= xxx,具体的条件有updateWrapper提供
由上可知update方法,第一个参数(实体类对象)可以为空,条件都写在UpdateWrapper中即可,因为在UpdateWrapper中传入了对应的泛型,且条件和修改都在wrapper.eq和wrapper.set中写了,所以可以这样操作,使用update时,只有将条件设入进来了后才会拼接到对应的set片段中,就不会出现updateById那种自动将基本数据类型设入进set片段的问题了//将id为20的员工,名字更新为laoqi UpdateWrapper
wrapper = new UpdateWrapper<>(); wrapper.eq("id",20); //这种写法的意思是将name值为zs的数据修改器age值为18 //wrapper.eq("name","zs").set("age",18); wrapper.set("name","laoqi"); employeeMapper.update(null,wrapper); - update与updateById如何选用?
- 使用updateById场景
- where条件是id时候update场景
- 进行全量(所有字段)更新时候
- 使用update场景
- where条件是不确定,或者多条件的update场景
- 进行部分字段更新时候
- 使用updateById场景
- 删除相关
- deleteById根据主键删除:
- deleteByMap根据条件删除,多个条件是就只能自动拼接成and了,而不能处理or情况
- delete通过QueryWrapper可以处理and,or等各种复杂的情况
- deleteBatchIds批量删除
public void testDelete(){ //1.根据主键删除:DELETE FROM employee WHERE id=? //employeeMapper.deleteById(21); //2.根据条件删除:DELETE FROM employee WHERE name = ? // HashMap
map = new HashMap<>(); // map.put("name","zs"); // employeeMapper.deleteByMap(map); //3.根据 entity 条件,删除记录,除了更新条件操作使用UpdateWrapper外, // 其余的操作都用QueryWrapper //DELETE FROM employee WHERE (id = ? AND name = ? OR age = ?) QueryWrapper wrapper = new QueryWrapper<>(); wrapper.eq("id",2L); wrapper.eq("name","zs").or().eq("age",18); employeeMapper.delete(wrapper); //4.批量删除:DELETE FROM employee WHERE id IN ( ? , ? , ? ) //employeeMapper.deleteBatchIds(Arrays.asList(33,44,55)); } - select相关:
@Test public void testSelect(){ //1.根据主键查询: // 得到语句:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE id=? // employeeMapper.selectById(20L); //2.根据id批量查询 //得到语句:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE id IN ( ? , ? , ? ) // employeeMapper.selectBatchIds(Arrays.asList(1,2,3)); //3.根据columnMap条件 //得到语句:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE name = ? AND age = ? // HashMap
map = new HashMap<>(); // map.put("name","zs"); // map.put("age",18); // employeeMapper.selectByMap(map); //4. 根据 entity 条件,查询一条记录 //得到的语句:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE (name = ?) //如果查出的结果不止一个就会报错如: //Expected one result (or null) to be returned by selectOne(), but found: 20 // QueryWrapper wrapper = new QueryWrapper<>(); // wrapper.eq("name","zs"); // employeeMapper.selectOne(wrapper); //5.根据 Wrapper 条件,查询总记录数 //得到语句:SELECT COUNT( 1 ) FROM employee这里的count(1)与count(*)效果一样 //所以也可以不要wrapper直接写employeeMapper.selectCount(null)就是表示查所有 // QueryWrapper wrapper = new QueryWrapper<>(); // employeeMapper.selectCount(wrapper); //6. 根据 entity 条件,查询全部记录(selectList会把结果封装成实体类对象) //得到语句:SELECT id,name,password,email,age,admin,dept_id FROM employee //如果wrapper中没有条件,则可以直接写成employeeMapper.selectList(null),查全部 // QueryWrapper wrapper = new QueryWrapper<>(); // List selectList = employeeMapper.selectList(wrapper); // selectList.forEach(System.out::println); //7. 根据 Wrapper 条件,查询全部记录(selectMaps会把结果封装成map) //得到语句: SELECT id,name,password,email,age,admin,dept_id FROM employee //如果查询全部则可以直接写成employeeMapper.selectMaps(null); // QueryWrapper wrapper = new QueryWrapper<>(); // List
- 常用注解:
- 构造器相关:
- 条件构造器:根据名字可以简单理解为条件拼接对象,用于生成 sql 的 where 条件
- Wrapper: 条件构造抽象类,最顶端父类
- AbstractWrapper: 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper: Entity 对象封装操作类,不是用lambda语法
- UpdateWrapper: Update 条件封装,用于Entity对象更新操作
- AbstractLambdaWrapper: Lambda 语法使用 Wrapper统一处理解析 lambda 获取column。
- LambdaQueryWrapper:看名称也能明白就是用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper: Lambda 更新封装Wrapper
- 独有的方法(updateWrapper类型与QueryWrappe类型都是继承的AbstractWrapper,那么各自独有的方法是什么呢?)
- UpdateWrapper与LambdaUpadateWrapper独有的是:set,setSql
- QueryWrapper与LambdaQueryWrapper独有的是:select
//需求:将id=zs @Test public void testUpdate2(){ UpdateWrapper
wrapper = new UpdateWrapper<>(); wrapper.eq("id", 1L); wrapper.set("name", "zs"); //wrapper.setSql("name='zs'"); employeeMapper.update(null, wrapper); } //set与setSql的区别? //set是采用的占位符形式:UPDATE employee SET name=? WHERE (id = ?) //setSql是直接把sql写死的:UPDATE employee SET name='zs' WHERE (id = ?) - LambdaUpdateWrapper更新
@Test public void testUpdate4(){ //将id=1的用户name改为zs //UPDATE employee SET name=? WHERE (id = ?) LambdaUpdateWrapper
wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(Employee::getId, 1L); wrapper.set(Employee::getName,"zs"); employeeMapper.update(null, wrapper); } - 查询
@Test public void testQuery(){ //1:查询所有员工,返回员工name,age列 //得到语句:SELECT name,age FROM employee QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.select("name,age"); //这种写法也可以 // wrapper.select("name","age"); employeeMapper.selectList(wrapper); } @Test public void testQuery1(){ //1:查询所有员工, 返回员工 以a字母开头的列(注意了是列名为a,不是列值) //得到语句:SELECT id,age,admin FROM employee QueryWrapper wrapper = new QueryWrapper<>(); wrapper.select(Employee.class, tableFieldInfo -> tableFieldInfo.getProperty().startsWith("a")); employeeMapper.selectList(wrapper); } - 排序:orderByAsc/orderByDesc
@Test public void testQuery5(){ //1.根据age,id进行升序查询,password降序 //得到语句:SELECT id,name,password,email,age,admin,dept_id // FROM employee ORDER BY age ASC,id ASC,password DESC QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.orderByAsc("age", "id"); //降序 wrapper.orderByDesc("password"); employeeMapper.selectList(wrapper); } - 条件运算符
- 比较运算符
- 全等匹配
@Test public void testQuery8(){ //1.查询name为zs,age为18的员工 QueryWrapper
wrapper = new QueryWrapper<>(); Map map = new HashMap<>(); map.put("name", "zs"); map.put("age", 18); map.put("dept_id", null); //allEq(map)方式得到语句:ELECT id,name,password,email, // age,admin,dept_id // FROM employee WHERE (name = ? AND dept_id IS NULL AND age = ?) wrapper.allEq(map); //allEq(map,false)方式得到语句:SELECT id,name,password,email, // age,admin,dept_id // FROM employee WHERE (name = ? AND age = ?) //null2IsNull:该参数表示为ture时:map中与key对应的value为null时就会 // 调用key is null 的方法,为false时:就直接将value为null的条件剔除了 //null2IsNull默认是为true的,所以我们在 wrapper.allEq(map);方法中 //看到的语句是带isnull的 // wrapper.allEq(map,false); employeeMapper.selectList(wrapper); } - 全等匹配(带条件过滤的)
@Test public void testQuery9() { //需求:查询满足条件员工信息, 注意传入的map条件中, 包含a的列才参与条件查询 QueryWrapper
wrapper = new QueryWrapper<>(); Map map = new HashMap<>(); map.put("name", "zs"); map.put("age", 18); //这样写得到的语句为:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE (age = ?) // wrapper.allEq((k, v) -> k.contains("g"), map); //这样写得到的语句为:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE (name = ? AND age = ?) wrapper.allEq((k, v) -> k.contains("a"), map); //这个表示只有列值为zs的条件才生效,否则被抛弃 //wrapper.allEq((k, v) -> v.equals("zs"), map); //总结:k.contains("xx")表示列名必须为xx的才参与查询 //filter : 过滤函数,是否允许字段传入比对条件中 //例1: allEq((k,v) -> k.indexOf("a") >= 0, // {id:1,name:"老王",age:null})--->name = '老王' and age is null //例2: allEq((k,v) -> k.indexOf("a") >= 0, // {id:1,name:"老王",age:null}, false)--->name = '老 王' //总结:allEq((k,v) -> k.indexOf("a") >= 0,条件,false)当条件后面设置的是false时 //那么条件中value为null的条件就直接丢弃了 employeeMapper.selectList(wrapper); } - 其他相关:
eq::单个参数判断是否相等
ne: 不等于
gt : 大于 >
ge:大于等于 >=
lt:小于 <
le:小于等于 <=
between:位于两者之间,比如年龄在18到30之间( between("age", 18, 30)得到语句--->age between 18 and 30)
notBetween:位于两者之外,用法同上(notBetween("age", 18, 30))
isNull/isNotNull:字段(IS NULL)/( IS NOT NULL),比如( isNull("name")得到语句--->name is null),( isNotNull("name")得到语句--->name is not null)
in/notIn:字段IN (value1, value2, ...)/字段NOT IN (value1, value2, ...)比如:wrapper.in("age", 1L, 2L)得到语句-->age in (1,2);同理notIn得到语句age not in (1,2)
inSql/notInSql:字段 IN ( sql语句 )用法同in与notIn只是insql与notInSql没有使用占位符,是写死的sql语句
like/notLike: LIKE '%值%'(NOT LIKE '%值%'),得到的sql语句为: like("name", "王")得到sql--->name like(not like) '%王%'
likeLeft/likeRight:%在左边或者右边,如: likeLeft("name", "王")得到sql--->name like '%王, likeRight("name", "王")得到sql--->name like '王%'
-
or:是用来拼接or的,主动调用表示下一个条件不是用and连接(默认是用and连接),而是用or连接,举例如下:
@Test public void testQuery24(){ QueryWrapper
wrapper = new QueryWrapper<>(); //age为18并且name为zs,或者id我1的用户 //得到语句为:SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE (age = ? AND name = ? OR id = ?) wrapper.eq("age", 18) .eq("name", "zs") .or() .eq("id", 1L); employeeMapper.selectList(wrapper); } -
嵌套OR:要用or(wr-> wr.ge("age",18).le("age",30));
@Test public void testQuery25(){ //需求:查询name含有zs字样的,或者 年龄在18到30之间的用户 //得到语句: SELECT id,name,password,email,age,admin,dept_id // FROM employee WHERE (name LIKE ? OR (age >= ? AND age <= ?)) QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.like("name","zs").or(wr-> wr.ge("age",18).le("age",30)); employeeMapper.selectList(wrapper); } -
groupBy:分组查询:注音分组查询,查询的只能是分组的条件,这里的就是dept_id,不能是其他列名,否则会报错(MySQL默认显示的是
分组数据的第一条,其他数据库就会报错),如果硬要用其他列名,那么只能在统计函数中,如count(id):@Test public void testQuery26(){ //需求: 以部门id进行分组查询,查每个部门员工个数 //得到语句:SELECT dept_id,count(id) count FROM employee GROUP BY dept_id //注音分组查询,查询的只能是分组的条件,这里的就是dept_id,不能是其他列名,否则会报错(MySQL默认显示的是 // 分组数据的第一条,其他数据库就会报错),如果硬要用其他列名,那么只能在统计函数中,如count(id) QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.groupBy("dept_id"); wrapper.select("dept_id","count(id) count"); employeeMapper.selectList(wrapper); } -
having:分组完后执行的的条件
@Test public void testQuery27() { // 以部门id进行分组查询,查每个部门员工个数, 将大于3人的部门过滤出来 QueryWrapper
wrapper = new QueryWrapper<>(); wrapper.groupBy("dept_id"); wrapper.select("dept_id", "count(id) count"); //得到语句:SELECT dept_id,count(id) count // FROM employee GROUP BY dept_id HAVING count>2 wrapper.having("count>2"); //得到语句:SELECT dept_id,count(id) count // FROM employee GROUP BY dept_id HAVING count>? // wrapper.having("count>{0}",2); employeeMapper.selectList(wrapper); } 自定义SQL:1.在resouces下面建立相同路径的XxxMapper.xml文件,像以前一样写好对应的mapper接口方法和对应的sql,然后新建好对应的service,此时service只需要在接口那里继承下IService<实体类>,便可获得常用的crud方法的声明,同时在对应的接口实现类再继承下ServiceImpl<实体类Mapper,实体类>,便可获得常用crud的实现方法,如果我们有自己额外的需求,还是像以前一样在接口处声明,实现类对应实现即可,这个有一个注意点,就是我们自己在实体类里面定义的数据库表中没有的属性时,一定要加上@TableField(exist = false),否则否则mybatisPlus帮我们实现的crud方法就会报错
-
分页,这里的IPage就等价于之前的PageInfo(分页信息封装对象),里面有各种分页相关信息
@Override public Page
queryPage(EmployeeQueryObject qo) { Page page = new Page<>(qo.getCurrentPage(),qo.getPageSize()); QueryWrapper wrapper = new QueryWrapper<>(); // wrapper.like("name",qo.getKeyWord()); wrapper.like(StringUtils.hasText(qo.getKeyWord()),"name",qo.getKeyWord()); // return super.page(page,wrapper); return super.page(page,wrapper); } @Test public void testPage(){ EmployeeQueryObject qo = new EmployeeQueryObject(); qo.setPageSize(5); qo.setCurrentPage(2); IPage page = employeeService.queryPage(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()); } -
写web项目,导入freemarker后一定要做以下配置
#暴露session对象的属性 spring.freemarker.expose-session-attributes=true #配置为传统模式,空值自动处理(如果不配置,那么页面很多地方显示不出来) spring.freemarker.settings.classic_compatible=true #重新指定模板文件后缀 springboot 2.2.x 后 默认后缀为 .ftlh spring.freemarker.suffix=.ftl
- 全等匹配
- 比较运算符
- 条件构造器:根据名字可以简单理解为条件拼接对象,用于生成 sql 的 where 条件
- 导入依赖