MyBatisPlus
什么是MybatisPlus
一个开源项目,非侵入式,帮助简化Mybatis的使用。
如何使用
0. 准备工作
建立数据库mybatis_plus
建表与导入数据:
1 | DROP TABLE IF EXISTS user; |
1.新建项目,添加依赖
新建项目没啥说的,新建个SpringBoot的项目就行。
添加依赖,pom.xml文件中依赖如下:
1 | <dependencies> |
其中需要安装Idea的Lombok的插件,我早就装好了。
2.application配置文件
这个配置文件里连接数据库的地方,有点需要注意的
1 | com.mysql.cj.jdbc.Driver = |
- driver-class-name处,mysql8以前,用com.mysql.jdbc.Driver,之后需要加上这个cj,变成com.mysql.cj.jdbc.Driver,不然会报警告。。。
- url处,在mysql8以后,需要加上那个时区:?serverTimezone=GMT%2B8
1) 简单·的实现查询
建立entity与mapper文件夹,同时建立User.java实体类与UserMapper.java类
User类中包括简单的信息,然后写上Lombok的@Data注解就有了getter和setter方法以及有参无参的构造方法:
1 | package com.songx64.mpdemo1010.entity; |
UserMapper则是接口,集成了BaseMapper,这个是mybatisplus里面的一个接口,实现了基本的增删改查之类的。继承时候需要一个模板T。
1 | package com.songx64.mpdemo1010.mapper; |
注释里写了部分东西了。
之后还需要改启动类里面的一点东西,就是加个@MapperScan注解,扫描某个路径下的Mapper。
1 |
|
当然这个@MapperScan注解也可以不加,而是在UserMapper接口中加上个@Mapper注解,也应该是一样的效果。
2)查询的单元测试
编写单元测试,进行简单的查询。
1 | package com.songx64.mpdemo1010; |
就只是自动注入了一个UserMapper,然后直接调用里面的selectList。
这里有个小技巧,写userMapper.selectList(null).var然后回车,IDEA就会自动生成一个变量: List
users = userMapper.selectList(null)也就是这一句。
3)简单的插入
再在单元测试里加个添加的方法:
1 |
|
运行结果如下:
这个就是输出的日志,可以看出插入成功了。
这里要提的就是这个Id主键,没有给他手动设置,他还是插入了一个值:“1294276830076878850”。这个是MybatisPlus自动生成的。
生成策略下面讲一下。
4) 生成策略
可能面试会问到相关的的东西。
参考网址:https://www.cnblogs.com/haoxinyue/p/5208136.html
生成策略有以下几种
1.自增主键
Auto Increment
优点:
1)简单,代码方便,性能可以接受。
2)数字ID天然排序,对分页或者需要排序的结果很有帮助。
缺点:
分表存储,每次都需要得到上一张表的末尾ID不方便。
2.UUID
优点:唯一。
缺点:排序不方便。
3. Redis生成
4. MybatisPlus自带算法,Twitter的Snowflake算法
结果是一个long型的ID。
5) Id操作
在实体类User.java中,加上一个注解:
1 |
|
其中 ‘@TableId(type = IdType.XXX)’ 是固定的。
去看IdType的源码:
1 | /** |
其中NONE是不设定,需要人手动输入的。
6) 自动填充
在enity实体类User.java中的字段上加入注解@TableField(fill = FieldFill.XXX)
1
2
3
4
private Date createTime;
private Date updateTime;实现接口MetaObjectHandler, 重写里面的方法中insertFill和updateFill . 根据名字来设定值.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package com.songx64.mpdemo1010.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author Admin
*/
public class MyMetaObjectHandler implements MetaObjectHandler {
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
7)乐观锁
什么是乐观锁
乐观锁是数据库中的概念,为了解决某些问题。主要解决:丢失更新问题。
问题
数据库中,如果不考虑事务隔离性,会产生什么问题?
- 读问题:
- 脏读
- 不可重复读
- 幻读
- 写问题
- 丢失更新
丢失更新问题举例
解决方案
- 悲观锁
- 乐观锁
悲观锁是只能有一个人进行操作。
乐观锁是通过版本号来进行控制,可能有多个人操作但有可能操作失败。
乐观锁举例
通过版本号,version字段来进行控制
mp代码实现乐观锁
给数据库加个version字段
给实体类加个version属性,并带上@Version注解
写个配置类,加入@Configuration注解。之后在类中加入乐观锁插件(复制代码)
1
2
3
4
5
6
7
8
9
10
public class MpConfig {
/**
* 乐观锁插件
*/
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}为了效果明显,先给数据库中的version一个默认的值,这里依旧通过上述的mp的自动填充fill方式实现。
加入@TableField(fill = FieldFill.INSERT)注解
去meta元数据的那里加入插入默认值
1
this.setFieldValByName("version", 1, metaObject);
测试
测试的时候必须“先查再改”,如果直接手动设置值的话version是不会改变的。
首先通过insertTest插入一个新的记录,可以看到其version为1.
1 | /** |
经过上述测试,发现修改年龄之后,记录的version变为2 。说明乐观锁生效。
8) 查询Select
1. 通过id来查询
1 | User user = userMapper.selectById(1300975732545150977L); |
2.通过多个id批量查询
1 | /** |
3.通过Map查询
1 | /** |
构建map的语句:HashMap<String,Object> map = new HashMap<>();
关于最后一句输出语句:
System.out::print :是方法引用
方法引用是当你想把一个方法当作一个“函数指针”传给别的方法用时有用的。
例如说,我有个ArrayList想把里面每个元素都打印出来,每个元素一行。
那么Java 8之前会这样写:
1
2
3 for (ElementType e : list) {
System.out.println(e);
}从Java 8开始,使用ArrayList的新API加上lambda表达式,我们可以这样写:
1 list.forEach(e -> System.out.println(e));而这里的lambda表达式的内容其实只不过就是把参数传给了println()方法,而没有做任何别的事情,所以可以进一步简写为:
1 list.forEach(System.out::println);仅此而已。
重点:
- System.out是一个PrintStream实例的引用;System.out::println 是对一个实例方法的引用
- 该引用同时指定了对实例(System.out)的引用以及对方法(PrintStream::println)的引用
- System.out::println 不是 System.out.println 的等价物;前者是一个方法引用表达式,而后者不能单独作为一个表达式,而必须在后面跟上由圆括号包围的参数列表来构成方法调用表达式。
- System.out::println 可以看作 lambda表达式 e -> System.out.println(e) 的缩写形式。
所以说以后想要遍历list集合的时候,就直接写 ’list.forEach(System.out::println())’ 就行了
4.分页
与PageHelper写法相似。。。
配置分页插件,还是去Config配置类里面加上。(复制代码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MpConfig {
/**
* 乐观锁插件
*/
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 分页插件
*/
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}写测试方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 分页查询方法测试
*/
public void testPage() {
//1.创建page对象
// 参数:current当前页数,size每页多少条记录
Page<User> page = new Page<>(1, 5);
//2.进行查询,查询到的数据会全部封装在传过来的page对象里面
//参数:page对象,wrapper查询条件(暂时写null)
userMapper.selectPage(page, null);
//3.输出测试
System.out.println("当前页:" + page.getCurrent());
System.out.println("Records每页数据list集合:");
page.getRecords().forEach(System.out::println);
System.out.println("Size每页记录数:" + page.getSize());
System.out.println("Total记录总条数:" + page.getTotal());
System.out.println("Pages总页数:" + page.getPages());
System.out.println("是否有下一页? -:" + page.hasNext());
System.out.println("是否有上一页? -:" + page.hasPrevious());
}其输出结果如下:
当前页:1
Records每页数据list集合:
User(id=1, name=Jone, age=22, email=test1@baomidou.com, createTime=null, updateTime=Wed Sep 02 09:56:47 CST 2020, version=null)
User(id=2, name=Jack, age=20, email=test2@baomidou.com, createTime=null, updateTime=null, version=null)
User(id=3, name=Tom, age=28, email=test3@baomidou.com, createTime=null, updateTime=null, version=null)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com, createTime=null, updateTime=null, version=null)
User(id=5, name=Billie, age=24, email=test5@baomidou.com, createTime=null, updateTime=null, version=null)
Size每页记录数:5
Total记录总条数:10
Pages总页数:2
是否有下一页? -:true
是否有上一页? -:false
控制台sql语句打印:SELECT id,name,age,email,create_time,update_time FROM user LIMIT 0,5 这里的这个Limit
这个是测试的SelectMapsPage,上面的那个是SelectPage,两个方法不一样。
9)删除
0.删除类型
物理删除
物理删除,删除表中的实际数据。删了数据库里就没了。
逻辑删除
逻辑删除,软删除,只是查询不到。数据库中记录仍然存在。通过一个标志位字段deleted来实现。
首先在表中加个boolean字段:deleted
1 | ALTER TABLE `user` ADD COLUMN `deleted` boolean |
1.根据Id删除
deleteById,物理删除
1 | /* 删除操作,物理删除*/ |
2.批量ID删除
deleteBatchIds
1 | /* 批量删除操作,物理删除*/ |
3.Map条件删除
1 | /* Map条件删除操作,物理删除*/ |
4.逻辑删除
Mp自带了逻辑删除的功能。
在数据库表中增加一个deleted字段。这里通过数据库表,设置其默认值为0.
1
alter table user add `deleted` boolean default 0 null;
在实体类User中添加属性,并添加@TableLogic注解
1
2
3
4
5/**
* 逻辑删除字段
*/
private Integer deleted;去配置类中,增加逻辑删除插件
1
2
3
4
5
6
7/**
* 逻辑删除插件
*/
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}(可选)可以去配置文件application.properties中修改mp的配置:
1
2
3
4# mp逻辑删除字段
# 被删除的是1,不被删除的是0。这是mp默认的,所以下面这两句不写也可以。
1 =
0 =如注释所说。这里可以随意修改这两个值。
然后直接去测试就可以了。用的还是原来的方法,不过从物理删除变成了逻辑删除。
依然执行deleteById方法:
1
2
3
4
5
6/* 删除操作,配置了之后变为删除*/
public void testDeleteByID() {
int result = userMapper.deleteById(1301357687774146562L);
System.out.println("影响的行数:" + result);
}然后查看控制台输出的语句:
可以看到,这里执行的是Update语句而不是Delete语句。修改了deleted,也就是含有@TableLogic注解的字段。
而且可以看到,数据库表中的数据没有被删掉而只是修改了标志位:
同时,查询的时候也是一样,直接执行selectAll方法:
10) 性能分析
性能分析拦截器,用于输出每条 SQL 语句及其执行时间
SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题
1.配置插件
(1)参数说明
参数:maxTime: SQL 执行最大时长,超过自动停止运行,有助于发现问题。
参数:format: SQL是否格式化,默认false。
(2)在 MybatisPlusConfig 中配置
1 | /** |
(3)Spring Boot 中设置dev环境
1 | #环境设置:dev、test、prod |
可以针对各环境新建不同的配置文件application-dev.properties
、application-test.properties
、application-prod.properties
也可以自定义环境名称:如test1、test2
- dev:开发环境
- test:测试环境
- prod:生产环境
2.测试运行效果
运行成功情况:其中显示运行时间49ms
改小执行时间,执行失败的情况:这时候虽然会报错,但是仍然会向数据库中插入数据。
Wrapper
这里是用Wrapper的子类,较常用的QueryWrapper,进行了几个方法的演示。
- 新建QueryWrapper对象
- 使用wrapper.xxx()方法,指定查询条件
- 传入wrapper对象,执行查询
1 | /** |
课堂笔记,全
这个是全的笔记,上面的只是列举了几个常用的、可能在项目中用到的方法。
一、wapper介绍
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : Entity 对象封装操作类,不是用lambda语法
UpdateWrapper : Update 条件封装,用于Entity对象更新操作
AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
1
2
3
4
5
6
7 @RunWith(SpringRunner.class)
@SpringBootTest
public class QueryWrapperTests {
@Autowired
private UserMapper userMapper;
}二、AbstractWrapper
**
**注意:以下条件构造器的方法入参中的column
均表示数据库字段1、ge、gt、le、lt、isNull、isNotNull
1
2
3
4
5
6
7
8
9
10 @Test
public void testDelete() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.isNull("name")
.ge("age", 12)
.isNotNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("delete return count = " + result);
}SQL:UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL
2、eq、ne
注意:seletOne返回的是一条实体记录,当出现多条时会报错
1
2
3
4
5
6
7 @Test
public void testSelectOne() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "Tom");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?
3、between、notBetween
包含大小边界
1
2
3
4
5
6
7 @Test
public void testSelectCount() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.between("age", 20, 30);
Integer count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ?
4、allEq
1
2
3
4
5
6
7
8
9
10
11 @Test
public void testSelectList() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
Map<String, Object> map = new HashMap<>();
map.put("id", 2);
map.put("name", "Jack");
map.put("age", 20);
queryWrapper.allEq(map);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ?
5、like、notLike、likeLeft、likeRight
selectMaps返回Map集合列表
1
2
3
4
5
6
7
8
9 @Test
public void testSelectMaps() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.notLike("name", "e")
.likeRight("email", "t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
maps.forEach(System.out::println);
}SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?
6、in、notIn、inSql、notinSql、exists、notExists
in、notIn:
1 notIn("age",{1,2,3})--->age not in (1,2,3)notIn("age", 1, 2, 3)--->age not in (1,2,3)inSql、notinSql:可以实现子查询
- 例:
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)
1
2
3
4
5
6
7
8 @Test
public void testSelectObjs() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//queryWrapper.in("id", 1, 2, 3);
queryWrapper.inSql("id", "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
objects.forEach(System.out::println);
}SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)
7、or、and
注意:这里使用的是 UpdateWrapper
不调用
or
则默认为使用and
连
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 @Test
public void testUpdate1() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or()
.between("age", 20, 30);
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age BETWEEN ? AND ?
8、嵌套or、嵌套and
这里使用了lambda表达式,or中的表达式最后翻译成sql时会被加上圆括号
1
2
3
4
5
6
7
8
9
10
11
12
13
14 @Test
public void testUpdate2() {
//修改值
User user = new User();
user.setAge(99);
user.setName("Andy");
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.or(i -> i.eq("name", "李白").ne("age", 20));
int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}UPDATE user SET name=?, age=?, update_time=?
WHERE deleted=0 AND name LIKE ?
OR ( name = ? AND age <> ? )
9、orderBy、orderByDesc、orderByAsc
1
2
3
4
5
6
7 @Test
public void testSelectListOrderBy() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 ORDER BY id DESC
10、last
直接拼接到 sql 的最后
注意:只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
1
2
3
4
5
6
7 @Test
public void testSelectListLast() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.last("limit 1");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 limit 1
11、指定要查询的列
1
2
3
4
5
6
7 @Test
public void testSelectListColumn() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "name", "age");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}SELECT id,name,age FROM user WHERE deleted=0
12、set、setSql
最终的sql会合并 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的字段
1
2
3
4
5
6
7
8
9
10
11
12
13 @Test
public void testUpdateSet() {
//修改值
User user = new User();
user.setAge(99);
//修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
userUpdateWrapper
.like("name", "h")
.set("name", "老李头")//除了可以查询还可以使用set设置修改的字段
.setSql(" email = '123@qq.com'");//可以有子查询
int result = userMapper.update(user, userUpdateWrapper);
}UPDATE user SET age=?, update_time=?, name=?, email = ‘123@qq.com’ WHERE deleted=0 AND name LIKE ?