1、MybatisPlus介绍

MybatisPlus是基于Mybatis框架基础上开发的增强型工具,它的目的是简化开发、提高效率。首先我们先回顾下Spring boot整合Mybatis吧。

2、Spring boot整合Mybatis过程

首先新建一个模块,选择Mybatis Framework 和 MySQL Driver 

Javafx 使用mybatis java mybatis plus_mybatis

配置一下jdbc:

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?useSSL = false
    username: root
    password: 12345

然后,我们简单在数据库中建立一个表,就叫做ball,买球的玩意:

Javafx 使用mybatis java mybatis plus_学习_02

然后写一下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));
	}
}

Javafx 使用mybatis java mybatis plus_Javafx 使用mybatis_03

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> {
}

然后我们发现,对于这个继承的东西,它包括了很多的常用的方法,如图:

Javafx 使用mybatis java mybatis plus_java_04

 继续用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));
	}

}

Javafx 使用mybatis java mybatis plus_学习_05

从上面这个小小的案例可以看出来,MP对于开发效率提升之大,之简便。MyBatisPlus的官网为:https://mp.baomidou.com/

Javafx 使用mybatis java mybatis plus_学习_06

Javafx 使用mybatis java mybatis plus_java_07

他的底层还是Mybatis,但是可以方便开发

 4、标准数据持久层的开发

接下来我们来看看,这个MP主要提供了啥方法还有一些源码。

4.1、基本的crud

Javafx 使用mybatis java mybatis plus_System_08

对于这张图的方法,我们挨个来演示下:

 4.2、新增——insert

在进行新增之前,我们可以分析下新增的方法:

int insert (T t)
  • T:泛型,新增用来保存新增数据
  • int:返回值,新增成功后返回1,没有新增成功返回的是0

在测试类中,创建一个对象进行保存

Javafx 使用mybatis java mybatis plus_mybatis_09

Javafx 使用mybatis java mybatis plus_mybatis_10

 

 执行测试后,数据库表中就会添加一条数据。

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));
}

Javafx 使用mybatis java mybatis plus_System_11

Javafx 使用mybatis java mybatis plus_mybatis_12

 

无了......

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));
	}

Javafx 使用mybatis java mybatis plus_mybatis_13

 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));
	}

Javafx 使用mybatis java mybatis plus_java_14

 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());
	}

Javafx 使用mybatis java mybatis plus_mybatis_15

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;
    }
}

Javafx 使用mybatis java mybatis plus_java_16

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日志到控制台

Javafx 使用mybatis java mybatis plus_java_17

看到了他代表:

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);
	}

Javafx 使用mybatis java mybatis plus_System_18

所以分两行就可以表示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().

Javafx 使用mybatis java mybatis plus_System_19

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;