02-mybatis_plus
Mybatis_plus 基础
参考资料
代码和笔记:https://gitee.com/kuangstudy/kuang_livenote/tree/master/【遇见狂神说】MyBatisPlus视频笔记
MyBatisPlus 概述
需要的基础:MyBatis、Spring、SpringMVC
为什么要学习它呢?
MyBatisPlus 可以节省我们大量工作时间,所有的 CRUD 代码它都可以自动化完成!
JPA 、 tk-mapper、MyBatisPlus
MyBatis 本来就是简化 JDBC 操作的!
关键点:学来偷懒
简介
快速入门
使用第三方组件:
1、导入对应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力!
步骤
新建数据库和表
数据库名mybatis_plus
user 表
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) ); INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
建好了
创建一个新的 springboot 文件
-
引入 Spring Boot Starter 父工程:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0+ 版本</version> <relativePath/> </parent> 引入
spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
、h2
依赖:<!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>最新版本</version> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> -
关于版本问题,mp 的版本导入最新版本
- 先进入这个网站:Maven Repository: Search/Browse/Explore (mvnrepository.com)
- 查找到最新版本,为 3.5.2
连接数据库
# mysql 5 驱动不同 com.mysql.jdbc.Driver # mysql 8 驱动不同com.mysql.cj.jdbc.Driver、需要增加时区的配置 serverTimezone=GMT%2B8 spring.datasource.username=root spring.datasource.password=shujuku spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
建实体类包 pojo
package com.example.mp01.pojo; /** * 实体类 */ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data //所有的属性的get set方法 @AllArgsConstructor //有参构造方法 @NoArgsConstructor //无参构造方法 public class User { private Long id; private String name; private Integer age; private String email; }
注解说明
- mapper 接口
package com.example.mp01.Mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.mp01.pojo.User; import org.springframework.stereotype.Repository; /** * 当我继承了BaseMapper<User>之后 所有的CRUD操作都已经编写完成了 */ @Repository //代表持久层 public interface UserMapper extends BaseMapper<User> { }
-
要在主启动类去添加 mapper 接口的扫描
-
@MapperScan("com.example.mp01.Mapper") -
测试类
package com.example.mp01; import com.example.mp01.Mapper.UserMapper; import com.example.mp01.pojo.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import java.util.List; @SpringBootTest class Mp01ApplicationTests { @Autowired private UserMapper userMapper; @Test void contextLoads() { // 参数是一个wrapper ,条件构造器,这里我们先不用nul1 //查询全部用户 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
bug 与解决
bug:java: 错误: 无效的源发行版:17
解决:
bug: java: 无效的目标发行版: 17
解决:
bug:
java: 无法访问 org.springframework.stereotype.Repository
错误的类文件: /D:/MAVENNNN/maven_repository/org/springframework/spring-context/6.0.2/spring-context-6.0.2.jar!/org/springframework/stereotype/Repository.class
类文件具有错误的版本 61.0, 应为 52.0
请删除该文件或确保该文件位于正确的类路径子目录中。
原因:版本问题
我这里的 spring-boot-starter-parent 版本是
3.0.0(推测是太高了)
改成了 2.7.5 就可以了
测试成功
配置日志
原因是原来的控制台只能看到结果,不能看到 sql 语句。
为了看到过程。在 application.properties 配置日志
# 配置日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
效果:
CRUD 扩展
插入操作
//插入 @Test public void testInsert(){ User user = new User(); user.setName("Lovi learning MP"); user.setAge(18); user.setEmail("123@qq.com"); int result = userMapper.insert(user);//mapper插入的时候会帮我们自主生成id System.out.println("==================="); System.out.println(result);//影响的行数 System.out.println(user);//打印出来看看,能看到id值是因为实体类包含了toString方法 }
结果:
主键生成策略
-
主键自增
-
- navicat 设置自动递增
-
- 在实体类 id 字段上加上
@TableId(type = IdType.AUTO)
- 在实体类 id 字段上加上
-
-
做测试
-
/** * 插入操作 */ @Test public void testInsert(){ User user = new User(); user.setName("happy"); user.setAge(99); user.setEmail("465464@qq.com"); int result = userMapper.insert(user);//// mp会帮我们自动生成id(默认是雪花算法) System.out.println("受影响的行数===》"+result);//受影响的行数 System.out.println(user);//发现,id会自动回填,这里会输出id是因为前面实体类有toString方法 } -
结果:
-
-
-
-
更新操作
/** * 更新操作 */ @Test public void testUpdate(){ User user = new User(); //L指的是类型为Long user.setId(1601961740072718357L); user.setAge(16); user.setName("哈哈哈哈哈"); int i = userMapper.updateById(user); System.out.println("受影响的行数===》"+i); }
bug:乱码,不知道说什么。。。(用了 6 种方法都解决不了,凋谢)
bug:关于我执行插入方法的时候,甚至我不执行插入方法,数据库都会给我插入多一条数据,也就是再 build 的时候就已经给我更改掉数据库了,我解决了一天半都没解决,却因为取消委托给 Maven 的 build/run 的权利就解决了,真的太快乐了!
bug:我的 idea 一直很慢,在这次这个项目里,实在是太离谱了。然后也是意外取消了委托。然后就快起来了!
解决方法:
成功解决:
公共字段自动填充
-
数据库新增 create_time 和 update_time 字段
- 默认值为
CURRENT_TIMESTAMP
当前时间戳
- 默认值为
-
实体类设置
-
@TableField(fill = FieldFill.INSERT) private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime;
-
-
新建一个我的元对象处理器类
-
package com.example.mp01.handler; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.util.Date; /** * MP提供的自动填充方法 * 需要实现MetaObjectHandler接口(元对象处理器)或者元数据处理器 * @Component:定义Spring管理Bean(也就是将标注@Component注解的类交由spring管理) */ @Component public class MyMetaObjectHandler implements MetaObjectHandler{ //需要重写插入方法和更新方法 //插入时的操作 @Override public void insertFill(MetaObject metaObject) { //default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) //setFieldValByName 按照名称设置字段值 this.setFieldValByName("creatTime", new Date(), metaObject); this.setFieldValByName("updateTime", new Date(), metaObject); } //更新时的操作 @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("updateTime", new Date(), metaObject); } } -
就成功啦(用测试类测试插入与更新)
-
乐观锁
-
数据库里新建 version 字段
-
实体类 version 字段
-
@Version private Integer version;
-
-
测试
-
//1.测试乐观锁更改版本 @Test public void leGuan(){ //更新值,那么版本就更新 User user = new User(); //1.查询用户信息 user = userMapper.selectById(1L); System.out.println("打印查询到的user!"+user); //2.执行更新操作 user.setName("I love u"); user.setEmail("666.163.com"); user.setAge(20); //修改数据库表 int i = userMapper.updateById(user); System.out.println("受影响的行数===》"+i); } -
运行结果
-
//测试乐观锁,多线程失败的情况 @Test public void leGuan2(){ //更新值,那么版本就更新 User user1 = new User(); //1.查询用户信息 user1 = userMapper.selectById(1L); //2.执行更新操作 user1.setName("线程A"); user1.setEmail("555.163.com"); user1.setAge(20); //模拟有新的B线程抢先操作 User user2 = new User(); //1.查询用户信息 user2 = userMapper.selectById(1L); //2.执行更新操作 user2.setName("线程B"); user2.setEmail("666.163.com"); user2.setAge(20); int i1 = userMapper.updateById(user2); System.out.println("线程B受影响的行数===》"+i1); //线程A int i = userMapper.updateById(user1); System.out.println("线程A受影响的行数===》"+i); } -
运行结果
-
bug:乐观锁版本我在数据库设置明明是 1,但是,每次插入数据的时候,代码层面总是自动帮我插入 0,导致插入的结果是 0
解决:只需要将实体类的 int 改成 integer 就可以了。因为 int 默认值为 0,不设置也会自动帮我插入 0.然后 integer 默认值为 null。不会帮我插进去默认值。然后新增数据的时候,就会走默认值通道。
查询操作
/** * 查询操作 */ //1.查询一条数据(按照id) @Test public void testSeleteById(){ User user = new User(); user=userMapper.selectById(2L); System.out.println(user); } //2.批量查询多条数据 @Test public void testSelect(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L)); users.forEach(System.out::println); } //3.多条件查询数据,符合条件就行 hash map @Test public void testSelectMultiCondition(){ HashMap<String,Object> map1 = new HashMap<>(); //设置查询条件 map1.put("name", "小宝贝"); map1.put("version", 1); List<User> users = userMapper.selectByMap(map1); users.forEach(System.out::println); }
结果:
分页查询
配置类配置分页拦截器
//分页拦截器 @Bean public PaginationInterceptor paginationInterceptor(){ return new PaginationInterceptor(); }
测试类
/** * 分页查询 */ @Test public void testPage(){ //参数1:当前页 //参数2:页面大小 //public Page(long current, long size) Page<User> userPage = new Page<>(1,2); //查出来 userMapper.selectPage(userPage, null); userPage.getRecords().forEach(System.out::println); System.out.println(userPage.getTotal()); }
运行结果
删除操作
/** * 删除操作 */ //1.通过id删除 @Test public void deleteById(){ userMapper.deleteById(1601961740072718372L); } //2.通过id批量删除 @Test public void deleteBatchId(){ List<User> users = new ArrayList<>(); userMapper.deleteBatchIds(Arrays.asList(1601961740072718376L,1601961740072718377L)); } //3.通过map删除 @Test public void deleteMap(){ HashMap<String,Object> map = new HashMap<>(); map.put("name", "小宝贝"); map.put("version", 1); userMapper.deleteByMap(map); }
- 执行前
- 执行后
逻辑删除
-
-
实体类配置
-
//逻辑删除 @TableLogic private Integer isDelete;
-
-
配置类
-
//逻辑删除 @Bean public ISqlInjector iSqlInjector(){ return new LogicSqlInjector(); }
-
-
在
application.properties
类加入配置-
#逻辑删除 已经删除为1,未删除未0 mybatis-plus.global-config.db-config.logic-delete-value=1 mybatis-plus.global-config.db-config.logic-not-delete-value=0
-
-
测试
-
@Test public void deleteById(){ userMapper.deleteById(1601961740072718373L); }
-
-
结果
- (实质执行的是更新操作,数据库还是在的)
性能分析插件
我们在平时的开发中,会遇到一些慢 sql。测试! druid,,,,,
作用:性能分析拦截器,用于输出每条 SQL 语句及其执行时间
MP 也提供性能分析插件,如果超过这个时间就停止运行!
-
设置开发环境
-
#设置开发环境 spring.profiles.active=dev
-
-
配置对应的插件(拦截器)
-
//sql执行效率插件,性能分析插件 @Bean @Profile({"dev","test"})//设置dev test环境开启,保证效率 public PerformanceInterceptor performanceInterceptor(){ //性能拦截器 PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); performanceInterceptor.setMaxTime(10);//ms设置sql执行的最大时间,如果超过了就不执行,而报错 performanceInterceptor.setFormat(true);//是否格式化代码 return performanceInterceptor; }
-
-
测试
-
@Test void contextLoads() { // 参数是一个 Wrapper ,条件构造器(这里先不用,所以就用null) //查询全部用户 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); }
-
-
结果
- 当我改成 100ms 之后问题就解决了
条件构造器
官网介绍和例子:https://baomidou.com/pages/10c804/#abstractwrapper
条件构造器可以自己弄很多复杂的 sql 语句
测试 1--gt ge lt le
/** * gt大于> * ge大于等于>= * lt小于< * le小于等于<= */ @Test public void test1(){ // 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .isNotNull("name") .isNotNull("email") .ge("age", 12); //大于等于 userMapper.selectList(wrapper).forEach(System.out::println); }
测试 2--eq ne
/** * eq等于 * ne不等于 */ @Test public void test2(){ //查询名字为EM的用户,并且age不等于68 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .eq("name", "EM") .ne("age", 68); User user = userMapper.selectOne(wrapper); System.out.println(user); }
测试 3--BETWEEN NOT BETWEEN
/** * BETWEEN 值1 AND 值2===>between("age", 18, 30)--->age between 18 and 30 * NOT BETWEEN 值1 AND 值2===>notBetween("age", 18, 30)--->age not between 18 and 30 */ @Test public void test3(){ //查询年龄在10-20岁的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .between("age",10,20); userMapper.selectList(wrapper).forEach(System.out::println); }
测试 4--模糊查询
/** * 模糊查询 * 假设查a * like 就是%a% * not like 就是不能含有a * likeRight 右边模糊查询 a% * likeLeft 左边模糊查询 %a */ @Test public void test4(){ //模糊查询名字中带有o的用户并且以J开头的用户 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper .like("name","o") .likeRight("name","J"); // userMapper.selectList(wrapper).forEach(System.out::println); //也可以用map的方式输出 List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); maps.forEach(System.out::println); }
测试 5--集合查询与 sql 子查询
/** * 集合查询与子查询 * 集合查询 * 字段 IN (value.get(0), value.get(1), ...) * 例: in("age",{1,2,3})--->age in (1,2,3) * in("age", 1, 2, 3)--->age in (1,2,3) * 字段 NOT IN (value.get(0), value.get(1), ...) * 例: notIn("age",{1,2,3})--->age not in (1,2,3) * notIn("age", 1, 2, 3)--->age not in (1,2,3) * sql语句子查询 * 字段 IN ( sql语句 ) * 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6) * 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3) * 字段 NOT IN ( sql语句 ) * 例: notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6) * 例: notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3) */ @Test public void test5(){ //子查询,id在子查询中查出来 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.in("id",1,2,3); wrapper.notIn("id",1,2,3); wrapper.inSql("id","select id from user where id < 5"); wrapper.notInSql("id","select id from user where id < 5"); //子查询 //输出 // userMapper.selectList(wrapper).forEach(System.out::println); //也可以用map的方式输出 // List<Map<String, Object>> maps = userMapper.selectMaps(wrapper); // maps.forEach(System.out::println); //也可以用Object的方式输出 List<Object> objects = userMapper.selectObjs(wrapper); objects.forEach(System.out::println); }
核心:
wrapper.in("id",1,2,3); wrapper.notIn("id",1,2,3); wrapper.inSql("id","select id from user where id < 5"); wrapper.notInSql("id","select id from user where id < 5");
集合的结果
子查询的结果
测试 6 --分组与排序
/** * 分组与排序 * 1.分组:GROUP BY 字段, ... * 例: groupBy("id", "name")--->group by id,name * 2.排序:ORDER BY 字段, ... ASC * 例: orderByAsc("id", "name")--->order by id ASC,name ASC * 3.排序:ORDER BY 字段, ... DESC * 例: orderByDesc("id", "name")--->order by id DESC,name DESC * 4.排序:ORDER BY 字段, ... * 例: orderBy(true, true, "id", "name")--->order by id ASC,name ASC * 5.HAVING ( sql语句 ) * 例: having("sum(age) > 10")--->having sum(age) > 10 * 例: having("sum(age) > {0}", 11)--->having sum(age) > 11 * */ @Test public void test6(){ QueryWrapper wrapper = new QueryWrapper(); wrapper.select("name","sum(age)"); wrapper.groupBy("name");//通过名字进行分组 wrapper.orderByAsc("name");//排序咯(分组后排序) wrapper.having("sum(age)>100");//把分组里年龄总和大于100的组筛选出来 userMapper.selectList(wrapper).forEach(System.out::println); }
bug:在执行
SELECT id,name,age,email,version,create_time,update_time,is_delete FROM user WHERE is_delete=0 GROUP BY name,version 这个 sql 语句时报错
1055 - Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'mybatis_plus.user.id' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
意思就是:在使用 group by 的时候,select 查询的字段一定都要在 group by 后面的列当中。不然就会报错
错误原因:数据库的版本导致,sql5.7 以上的版本都有这个问题
当我改成:
SELECT name,version FROM user WHERE is_delete=0 GROUP BY name,version
果然就不报错了
错误解决:
先查看当前数据库配置
SELECT @@sql_mode;
我得到的结果为:
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
得把 ONLY_FULL_GROUP_BY 去掉,重新设置值
也就是
SET sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
这个时候再查就可以了
如果要设置全局的就得
SELECT @@GLOBAL.sql_mode;
一样把查出来的结果去掉 ONLY_FULL_GROUP_BY 重新设置
set @@global.sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
扩展一个 sql 语句
SELECT name , sum(age) t from user group by name having t>200
意思是,查找名字对应的年龄总和为大于 200 的组
测试 7--allEq
/** * allEq * 全部eq(或个别isNull) */ @Test public void test7(){ //allEq QueryWrapper wrapper = new QueryWrapper(); //需要用到map Map<String,Object> map = new HashMap<>(); map.put("name", "oh my god"); map.put("email",null); wrapper.allEq(map);//查找的是name=oh my god 并且email为空的数据 // wrapper.allEq(map,false);//查找的是name=oh my god 的数据,不管email空不空都找出来 userMapper.selectList(wrapper).forEach(System.out::println); }
代码生成器
这个也是 mybatisplus 的一个亮点,但是由于,个人积累没有到这一步,所以,晚点再补充学习。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端