1、MybatisPlus介绍
MybatisPlus是基于Mybatis框架基础上开发的增强型工具,它的目的是简化开发、提高效率。首先我们先回顾下Spring boot整合Mybatis吧。
2、Spring boot整合Mybatis过程
首先新建一个模块,选择Mybatis Framework 和 MySQL Driver
配置一下jdbc:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/ssm_db?useSSL = false
username: root
password: 12345
然后,我们简单在数据库中建立一个表,就叫做ball,买球的玩意:
然后写一下Pojo实体类和Dao持久层的代码:
package stukk.Pojo;
public class Ball {
private int id;
private String name;
private double money;
@Override
public String toString() {
return "Ball{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
package stukk.Dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import stukk.Pojo.Ball;
@Mapper
public interface BallDao {
@Select("select * from ball where id = #{id}")
Ball getById(int id);
}
然后写一写业务层:
package stukk.Service;
import stukk.Pojo.Ball;
public interface BallService {
Ball getById(int id);
}
@Service
public class BallServiceImpl implements BallService {
@Autowired
BallDao ballDao;
@Override
public Ball getById(int id) {
return ballDao.getById(id);
}
}
然后再test中试一试
package stukk;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import stukk.Service.BallService;
@SpringBootTest
class DemoApplicationTests {
@Autowired
BallService ballService;
@Test
void contextLoads() {
System.out.println(ballService.getById(3));
}
}
ok,接下来来看看Springboot 怎么整合Mybatis PLus
3、Springboot 整合MybatisPlus
由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动在pom.xml中配置添加
我们再重新搭建个spring boot环境,点击pom.xml,然后再里面添加mybatisplus的依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
不同的地方来了,这里我们依然使用Ball这个实体类和数据库表,Dao层的代码直接继承BaseMapper<T> ,然后什么也不写。
package stukk.Dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import stukk.Pojo.Ball;
@Mapper
public interface BallDao extends BaseMapper<Ball> {
}
然后我们发现,对于这个继承的东西,它包括了很多的常用的方法,如图:
继续用test去测试一下,发现没问题。
package stukk.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import stukk.Dao.BallDao;
import stukk.Pojo.Ball;
@Service
public class BallServiceImpl implements BallService {
@Autowired
BallDao ballDao;
@Override
public Ball getById(int id) {
return ballDao.selectById(id);
}
}
package stukk;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import stukk.Service.BallService;
@SpringBootTest
class StukkApplicationTests {
@Autowired
BallService ballService;
@Test
void contextLoads() {
System.out.println(ballService.getById(3));
}
}
从上面这个小小的案例可以看出来,MP对于开发效率提升之大,之简便。MyBatisPlus的官网为:https://mp.baomidou.com/
他的底层还是Mybatis,但是可以方便开发
4、标准数据持久层的开发
接下来我们来看看,这个MP主要提供了啥方法还有一些源码。
4.1、基本的crud
对于这张图的方法,我们挨个来演示下:
4.2、新增——insert
在进行新增之前,我们可以分析下新增的方法:
int insert (T t)
-
T:泛型,新增用来保存新增数据
-
int:返回值,新增成功后返回1,没有新增成功返回的是0
在测试类中,创建一个对象进行保存
执行测试后,数据库表中就会添加一条数据。
4.3、删除——delete
先分析下删除的操作:
int deleteById (Serializable id);
Serializable : 参数类型
String和Number是Serializable的子类,而Number又是Float、Double、Integer等的父类,所以能够作为主键的数据类型都已经是Serializable的子类,就好比我们可以用Object接收任何数据类型一样。
@Override
public boolean deleteByID(int id) {
return ballDao.deleteById(id) == 1;
}
@Test
void deleteTest(){
System.out.println(ballService.deleteByID(4));
}
无了......
4.4、修改——update
在进行修改之前,我们可以分析下修改的方法:
int updateById(T t);
-
T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值。
-
int:返回值,修改成功后返回1,未修改数据返回0。
@Override
public boolean updateById(Ball ball) {
return ballDao.updateById(ball) == 1;
}
@Test
void updateTest(){
Ball ball = new Ball();
ball.setId(3);
ball.setName("气球");
ball.setMoney(2.33);
System.out.println(ballService.updateById(ball));
}
4.5、查询——根据id查询
在进行根据ID查询之前,我们可以分析下根据ID查询的方法:
T selectById (Serializable id)
-
Serializable:参数类型,主键ID的值
-
T:根据ID查询只会返回一条数据
@Override
public Ball selectById(int id) {
return ballDao.selectById(id);
}
@Test
void selectOneTest(){
int id = 3;
System.out.println(ballService.selectById(id));
}
4.6、查询所有
在进行查询所有之前,我们可以分析下查询所有的方法:
List<T> selectList(Wrapper<T> queryWrapper)
-
Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
-
List<T>:因为查询的是所有,所以返回的数据是一个集合
@Override
public List<Ball> selectAll() {
return ballDao.selectList(null);
}
@Test
void SelectAll(){
System.out.println(ballService.selectAll());
}
5、Lombok
代码写到这,我们会发现DAO接口类的编写现在变成最简单的了,里面什么都不用写。反过来看看模型类的编写都需要哪些内容:
-
私有属性
-
setter...getter...方法
-
toString方法
-
构造函数
虽然这些内容不难,同时也都是通过IDEA工具生成的,但是过程还是必须得走一遍,那么对于模型类的编写有没有什么优化方法?就是我们接下来要学习的Lombok。
概念
-
Lombok,一个Java类库,提供了一组注解,简化POJO实体类开发。
使用步骤
1、添加依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
2、写实体类
package stukk.Pojo;
import lombok.*;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Ball {
private int id;
private String name;
private double money;
}
非常之方便!
6、分页功能
上面,我们把基础的增删改查说完了,接下来说一下分页功能,在MP中如何实现分页功能的呢?
6.1、分页查询
分页查询使用后的方法是:
Ipage<T> selectPage(Ipage<T> page, Wrapper<T> queryWrapper)
-
IPage<T>:用来构建分页查询条件
-
Wrapper:用来构建条件查询的条件,目前我们没有可直接传为Null
-
IPage<T>:返回值,你会发现构建分页条件和方法的返回值都是IPage
1、调用方法传入参数获取返回值
@Test
void SelectPageTest(){
IPage<Ball> page = new Page<>(1,3);//当前页码,每页的数量
ballDao.selectPage(page,null);
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());
}
设置拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mybatisPlusInterceptor;
}
}
7、复杂的条件查询
-
MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。
这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到过一个Wrapper
类,这个类就是用来构建查询条件的。那么条件查询如何使用Wrapper来构建呢?
7.1、QueryWrapper
先看看这段代码:
@Test
void TestTiaojian(){//条件查询
QueryWrapper<Ball> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("money",30);
List<Ball> list = ballService.selectByWrapper(queryWrapper);
System.out.println(list);
}
其中,我们利用yml文件中加入的这段代码可以找到SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印SQL日志到控制台
看到了他代表:
SELECT id,name,money FROM ball WHERE (money < ?)
可以看到,lt表示<,下面是它所有的方法以及代表的意思:
函数名 | 说明 | 例子 |
allEq(Map<R, V> params) | 全部 =(或个别 isNull) | allEq(params,true) |
eq | real_name = ‘阿大’ | eq(“real_name”,“阿大”) |
ne | 不等于 <> | ne(“real_name”,“阿大”) |
gt | age > 21 | gt(“age”,21) |
ge | age>= 22 | ge(“age”,22) |
lt | age< 22 | lt(“age”,22) |
le | age <= 221 | le(“age”,21") |
between | age between 0 and 21 | between(“age”,0,21) |
notBetween | age not between 0 and 21 | notBetween(“age”,0,21) |
like | real_name like ‘% 王 %’ | like(“real_name”,“王”) |
notLike | real_name not like ‘% 王 %’ | notLike(“real_name”,“王”) |
likeLeft | real_name like ‘% 王’ | likeLeft(“real_name”,“王”) |
likeRight | real_name like ‘王 %’ | likeRight(“real_name”,“王”) |
isNull | gender is null | isNull(“gender”) |
isNotNull | gender is not null | isNotNull(“gender”) |
in | name in (1,2,3) | in(“name ”,{1,2,3}) |
notIn | age not in (1,2,3) | notIn(“nick_name”,lists) |
inSql | id in (select id from table where id < 3) | inSql(“id”,“‘select id from table where id < 3’”) |
notInSql | id not in (select id from table where id < 3) | notInSql(“id”,“‘select id from table where id < 3’”) |
groupBy | 分组 group by id,name | groupBy(“id”,“name”) |
orderByAsc | 小到大排序 order by id ASC,name ASC | orderByAsc(“id ”,“name”) |
orderByDesc | 大到小排序 order by id DESC,name DESC | orderByDesc(“id ”,“name”) |
orderBy | order by id ASC,name ASC | orderBy(true,true,“id ”,“name”) |
having | having sum(age) > 10 | having(“sum(age) > 10”) |
or | id = 1 or name = ‘老王’ | eq(“id”,“1”).or() eq(“name ”,“老王”) |
and | and (name = ‘李白’ and status <> ‘活着’) | and(i->i.eq(“name ”,“李白”).ne(“status”,“活着”)) |
nested | (name = ‘李白’ and status <> ‘活着’) | nested(i->i.eq(“age”,21).eq(“status ”,“活着”)) |
apply | name = 李白 | apply(“name= ‘李白’) |
last | 最后添加多个以最后的为准,有 sql 注入风险 | last(“limit 1”) |
exists | 拼接 EXISTS (sql 语句) | exists(“select id from table where age = 1”) |
notExists | 拼接 NOT EXISTS (sql 语句) | notExists(“select id from table where age = 1”) |
7.2、Lambda
@Test
void TestTiaojian(){//条件查询
QueryWrapper<Ball> queryWrapper = new QueryWrapper<>();
// queryWrapper.gt("money",30);
queryWrapper.lambda().lt(Ball::getMoney,30);
List<Ball> list = ballService.selectByWrapper(queryWrapper);
System.out.println(list);
}
Lambda表达式:类名::方法名。
注意:构建LambdaQueryWrapper的时候泛型不能省。
此时我们再次编写条件的时候,就不会存在写错名称的情况,但是qw后面多了一层lambda()调用。
7.3、LambdaQueryWrapper
@Test
void TestTiaojian(){//条件查询
QueryWrapper<Ball> queryWrapper = new QueryWrapper<>();
// queryWrapper.gt("money",30);
// queryWrapper.lambda().lt(Ball::getMoney,30);
LambdaQueryWrapper<Ball> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(Ball::getMoney,30);
List<Ball> list = ballService.selectByWrapper(lambdaQueryWrapper);
System.out.println(list);
}
这样子就完美整合了上面两种方法。
7.3、多条件查询
首先我们来查询一下money大于10并且小于 30 的球。
@Test
void TestDuo(){
LambdaQueryWrapper<Ball> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(Ball::getMoney,30);
lambdaQueryWrapper.gt(Ball::getMoney,10);
List<Ball> list = ballService.selectByWrapper(lambdaQueryWrapper);
System.out.println(list);
}
//链式表达式
@Test
void TestDuo(){
LambdaQueryWrapper<Ball> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(Ball::getMoney,30).gt(Ball::getMoney,10);
List<Ball> list = ballService.selectByWrapper(lambdaQueryWrapper);
System.out.println(list);
}
所以分两行就可以表示And了,所以Or怎么表示:
@Test
void TestDuo(){
LambdaQueryWrapper<Ball> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.lt(Ball::getMoney,30).or().gt(Ball::getMoney,10);
List<Ball> list = ballService.selectByWrapper(lambdaQueryWrapper);
System.out.println(list);
}
加个.or().
8、聚合查询
需求:聚合函数查询,完成count、max、min、avg、sum的使用
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
看看MP是怎么搞的。
@Test
void TestJv(){
QueryWrapper<Ball> queryWrapper = new QueryWrapper<>();
// queryWrapper.select("count(*) as count");
// Object ans = ballDao.selectList(queryWrapper);
// queryWrapper.select("avg(money) as avgMoney");
//SELECT avg(age) as avgAge FROM user
// queryWrapper.select("max(money) as max");
// queryWrapper.select("min(money) as min");
queryWrapper.select("sum(money) as sum");
List<Map<String, Object>> userList = ballDao.selectMaps(queryWrapper);
System.out.println(userList);
}
就是这样子。
9、映射匹配
问题1、如果我们的数据库表的字段与代码实体类的属性不一样怎么办?
答:MP中给我们提供了一个注解:@TableField(value=""),使用该注解可以实现模型类属性名和表的列名之间的映射关系。
问题2、代码中实体类的属性比数据库表中字段还要多?
答:当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:Unknown column '多出来的字段名称' in 'field list'具体的解决方案用到的还是@TableField
注解,它有一个属性叫exist
,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
问题3、怎么设置某些属性值不可以被查询出来?
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
问题4、表名不一致
答:解决方案是使用MP提供的另外一个注解@TableName
来设置表与模型类之间的对应关系。
10、乐观锁
乐观锁的实现方式:
数据库表中添加version列,比如默认值给1
第一个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
第二个线程要修改数据之前,取出记录时,获取当前数据库中的version=1
第一个线程执行更新时,set version = newVersion where version = oldVersion
newVersion = version+1 [2]
oldVersion = version [1]
第二个线程执行更新时,set version = newVersion where version = oldVersion
newVersion = version+1 [2]
oldVersion = version [1]
假如这两个线程都来更新数据,第一个和第二个线程都可能先执行
假如第一个线程先执行更新,会把version改为2,
第二个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第二个线程会修改失败
假如第二个线程先执行更新,会把version改为2,
第一个线程再更新的时候,set version = 2 where version = 1,此时数据库表的数据version已经为2,所以第一个线程会修改失败
不管谁先执行都会确保只能有一个线程更新数据,这就是MP提供的乐观锁的实现原理分析。
上面所说的步骤具体该如何实现呢?
添加乐观锁的拦截器:
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
//在实体类中添加
@Version
private Integer version;
官方文档:https://mp.baomidou.com/guide/interceptor-optimistic-locker.html#optimisticlockerinnerinterceptor