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, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
建好了
创建一个新的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("[email protected]");
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字段上加上
-
3.做测试。
-
更新操作
/**
* 更新操作
*/
@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);
}