MybatisPlus——为便捷开发而生

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)
eqreal_name = ‘阿大’eq(“real_name”,“阿大”)
ne不等于 <>ne(“real_name”,“阿大”)
gtage > 21gt(“age”,21)
geage>= 22ge(“age”,22)
ltage< 22lt(“age”,22)
leage <= 221le(“age”,21")
betweenage between 0 and 21between(“age”,0,21)
notBetweenage not between 0 and 21notBetween(“age”,0,21)
likereal_name like ‘% 王 %’like(“real_name”,“王”)
notLikereal_name not like ‘% 王 %’notLike(“real_name”,“王”)
likeLeftreal_name like ‘% 王’likeLeft(“real_name”,“王”)
likeRightreal_name like ‘王 %’likeRight(“real_name”,“王”)
isNullgender is nullisNull(“gender”)
isNotNullgender is not nullisNotNull(“gender”)
inname in (1,2,3)in(“name ”,{1,2,3})
notInage not in (1,2,3)notIn(“nick_name”,lists)
inSqlid in (select id from table where id < 3)inSql(“id”,“‘select id from table where id < 3’”)
notInSqlid not in (select id from table where id < 3)notInSql(“id”,“‘select id from table where id < 3’”)
groupBy分组 group by id,namegroupBy(“id”,“name”)
orderByAsc小到大排序 order by id ASC,name ASCorderByAsc(“id ”,“name”)
orderByDesc大到小排序 order by id DESC,name DESCorderByDesc(“id ”,“name”)
orderByorder by id ASC,name ASCorderBy(true,true,“id ”,“name”)
havinghaving sum(age) > 10having(“sum(age) > 10”)
orid = 1 or name = ‘老王’eq(“id”,“1”).or() eq(“name ”,“老王”)
andand (name = ‘李白’ and status <> ‘活着’)and(i->i.eq(“name ”,“李白”).ne(“status”,“活着”))
nested(name = ‘李白’ and status <> ‘活着’)nested(i->i.eq(“age”,21).eq(“status ”,“活着”))
applyname = 李白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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

stu_kk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值