基于MyBatisPlus完成标准Dao的增删改查功能
掌握MyBatisPlus中的分页及条件查询构建
掌握主键ID的生成策略
MybatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提供效率。
开发方式
参考我的这篇博客–>springboot整合mybatis的方式
create database if not exists mybatisplus_db character set utf8;
use mybatisplus_db;
CREATE TABLE user (id bigint(20) primary key auto_increment,name varchar(32) not null,password varchar(32) not null,age int(3) not null ,tel varchar(32) not null
);
insert into user values(1,'Tom','tom',3,'18866668888');
insert into user values(2,'Jerry','jerry',4,'16688886666');
com.baomidou mybatis-plus-boot-starter 3.4.1
com.alibaba druid 1.1.16
说明:serverTimezone是用来设置时区,UTC是标准时区,和咱们的时间差8小时,所以可以将其修改为Asia/Shanghai
public class User { private Long id;private String name;private String password;private Integer age;private String tel;//setter...getter...toString方法略
}
@Mapper
public interface UserDao extends BaseMapper {
}
@SpringBootApplication
//@MapperScan("com.example.dao")
public class Mybatisplus01Application {public static void main(String[] args) {SpringApplication.run(Mybatisplus01Application.class, args);}
}
**说明:**Dao接口要想被容器扫描到,有两种解决方案:
@Mapper
注解,并且确保Dao处在引导类所在包或其子包中 @MapperScan
注解,其属性为所要扫描的Dao所在包 @Mapper
就可以不写。这里以查询所有为例子
@SpringBootTest
class Mybatisplus01ApplicationTests {@AutowiredUserDao userDao;@Testvoid testGetAll() {List users = userDao.selectList(null);System.out.println(users);}
}
结果:
跟之前整合MyBatis相比,我们不需要在DAO接口中编写方法和SQL语句了,只需要继承BaseMapper
接口即可。整体来说简化很多。
对于标准的CRUD功能都有哪些以及MP都提供了哪些方法可以使用呢?
我们先来看张图:
int insert (T t)
T:泛型,新增用来保存新增数据
int:返回值,新增成功后返回1,没有新增成功返回的是0
在测试类中进行新增操作:
@Testvoid testSave() {User user = new User();user.setName("一只呆呆木");user.setPassword("!666");user.setAge(18);user.setTel("001");userDao.insert(user);}
但是数据中的主键ID,有点长,那这个主键ID是如何来的?我们更想要的是主键自增,应该是5才对,这个就涉及到mp的id生成策略的配置,后面有说~
int deleteById (Serializable id)
Serializable:参数类型
思考:参数类型为什么是一个序列化类?
从这张图可以看出,
String和Number是Serializable的子类,
Number又是Float,Double,Integer等类的父类,
能作为主键的数据类型都已经是Serializable的子类,
MP使用Serializable作为参数类型,就好比我们可以用Object接收任何数据类型一样。
int:返回值类型,数据删除成功返回1,未删除数据返回0。
@Testvoid testDelete() {userDao.deleteById(1599248419765915649L);//这里的id后面要加L}
int updateById(T t);
T:泛型,需要修改的数据内容,注意因为是根据ID进行修改,所以传入的对象中需要有ID属性值
int:返回值,修改成功后返回1,未修改数据返回0
在测试类中进行修改操作:
@Testvoid testUpdate() {User user = new User();user.setId(1L);user.setName("Tom888");user.setPassword("tom888");userDao.updateById(user);}
修改前:
修改后:
**说明:**修改的时候,只修改实体对象中有值的字段。
T selectById (Serializable id)
在测试类中进行查询操作:
@Testvoid testGetById() {User user = userDao.selectById(4L);System.out.println(user);}
List selectList(Wrapper queryWrapper)
在测试类中进行查询操作:
@Testvoid testGetAll() {List users = userDao.selectList(null);System.out.println(users);}
我们所调用的方法都是来自于DAO接口继承的BaseMapper类中。里面的方法有很多,这里列了几个基本的增删改查的。
使用分页查询我们需要设置分页拦截器,否则将会查询所有
IPage selectPage(IPage page, Wrapper queryWrapper)
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类,可以进入到IPage类中按ctrl+h,会找到其有一个实现类为Page
。
测试类:
//分页查询@Testvoid testSelectPage(){//1 创建IPage分页对象,设置分页参数,1为当前页码,3为每页显示的记录数IPage page=new Page<>(1,3);//2 执行分页查询userDao.selectPage(page,null);//3 获取分页结果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());}
使用分页查询我们需要设置分页拦截器,否则将会查询所有
我们明明查的一页中有三条数据,但是却给我们查到了所有的数据,这显然没有达到分页的目的,这个时候我们就需要设置分页拦截器。
设置分页拦截器:
这个拦截器MP已经为我们提供好了,我们只需要将其配置成Spring管理的bean对象即可。
创建一个config配置包,然后创建拦截器
@Configuration
public class MybatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//1 创建MybatisPlusInterceptor拦截器对象MybatisPlusInterceptor mpInterceptor=new MybatisPlusInterceptor();//2 添加分页拦截器mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mpInterceptor;}
}
运行测试程序:
在进行查询的时候,我们的入口是在Wrapper这个类上,因为它是一个接口,所以我们需要去找它对应的实现类,关于实现类也有很多,说明我们有多种构建查询条件对象的方式,
@Testvoid testGetByCon(){QueryWrapper qw = new QueryWrapper();qw.lt("age",18);List userList = userDao.selectList(qw);System.out.println(userList);}
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
@Testvoid testGetByCon2(){QueryWrapper qw = new QueryWrapper();qw.lambda().lt(User::getAge, 18);//添加条件List userList = userDao.selectList(qw);System.out.println(userList);}
SELECT id,name,password,age,tel FROM user WHERE (age < ?)
**注意:**构建LambdaQueryWrapper的时候泛型不能省。
@Testvoid testGetByCon3(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.lt(User::getAge, 18);List userList = userDao.selectList(lqw);System.out.println(userList);}
需求:查询数据库表中,年龄在10岁到30岁之间的用户信息
@Testvoid testGetByCons(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.lt(User::getAge, 30).gt(User::getAge, 10);List userList = userDao.selectList(lqw);System.out.println(userList);}
需求:查询数据库表中,年龄小于10或年龄大于30的数据
@Testvoid testGetAll(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);List userList = userDao.selectList(lqw);System.out.println(userList);}
or
关键字,不加默认是and
,最终的sql语句为:SELECT id,name,password,age,tel FROM user WHERE (age < ? OR age > ?)
查询投影即不查询所有字段,只查询出指定内容的数据。
@Testvoid testGetSome(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.select(User::getId,User::getName,User::getAge);List userList = userDao.selectList(lqw);System.out.println(userList);}
SELECT id,name,age FROM user
@Testvoid testGetSome(){QueryWrapper lqw = new QueryWrapper();lqw.select("id","name","age","tel");List userList = userDao.selectList(lqw);System.out.println(userList);}
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
@Testvoid testGetByFunc(){QueryWrapper lqw = new QueryWrapper();//lqw.select("count(*) as count");//SELECT count(*) as count FROM user//lqw.select("max(age) as maxAge");//SELECT max(age) as maxAge FROM user//lqw.select("min(age) as minAge");//SELECT min(age) as minAge FROM user//lqw.select("sum(age) as sumAge");//SELECT sum(age) as sumAge FROM userlqw.select("avg(age) as avgAge");//SELECT avg(age) as avgAge FROM userList
@Testvoid testGetGroup(){QueryWrapper lqw = new QueryWrapper();lqw.select("count(*) as count,tel");lqw.groupBy("tel");List
SELECT count(*) as count,tel FROM user GROUP BY tel
注意:
需求:根据用户名和密码查询用户信息
@Testvoid testGetEqu(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");User loginUser = userDao.selectOne(lqw);System.out.println(loginUser);}
=
,对应的sql语句为SELECT id,name,password,age,tel FROM user WHERE (name = ? AND password = ?)
selectList:查询结果为多个或者单个
selectOne:查询结果为单个
对年龄进行范围查询,使用lt()、le()、gt()、ge()、between()进行范围查询
@Testvoid testGetByRange(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.between(User::getAge, 18, 30);//SELECT id,name,password,age,tel FROM user WHERE (age BETWEEN ? AND ?)List userList = userDao.selectList(lqw);System.out.println(userList);}
查询表中name属性的值以
一
开头的用户信息,使用like进行模糊查询
@Testvoid testGetFuzzy(){LambdaQueryWrapper lqw = new LambdaQueryWrapper();lqw.likeLeft(User::getName, "一");//SELECT id,name,password,age,tel FROM user WHERE (name LIKE ?)List userList = userDao.selectList(lqw);System.out.println(userList);}
查询所有数据,然后按照id降序
@Testvoid testGetSort() {LambdaQueryWrapper lwq = new LambdaQueryWrapper<>();/*** condition :条件,返回boolean,当condition为true,进行排序,如果为false,则不排序* isAsc:是否为升序,true为升序,false为降序* columns:需要操作的列*/lwq.orderBy(true, false, User::getId);List users = userDao.selectList(lwq.orderBy(true, false, User::getId));System.out.println(users);}
除了上面介绍的这几种查询条件构建方法以外还会有很多其他的方法,比如isNull,isNotNull,in,notIn等等方法可供选择,具体参考官方文档
的条件构造器来学习使用,具体的网址为:
<条件构造器 | MyBatis-Plus (baomidou.com)>
前面我们已经能从表中查询出数据,并将数据封装到模型类中,这整个过程涉及到一张表和一个模型类:
之所以数据能够成功的从表中获取并封装到模型对象中,原因是表的字段列名和模型类的属性名一样。
当表的列名和模型类的属性名发生不一致,就会导致数据封装不到模型对象,这个时候就需要其中一方做出修改,那如果前提是两边都不能改又该如何解决?
MP给我们提供了一个注解@TableField
,使用该注解可以实现模型类属性名和表的列名之间的映射关系
当模型类中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序运行就会报错,错误信息为:
Unknown column ‘多出来的字段名称’ in ‘field list’
具体的解决方案用到的还是@TableField
注解,它有一个属性叫exist
,设置该字段是否在数据库表中存在,如果设置为false则不存在,生成sql语句查询的时候,就不会再查询该字段了。
@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。
public class User {private Long id;private String name;@TableField(value="pwd",select=false)private String password;private Integer age;private String tel;@TableField(exist=false)private Integer online;
}
我们在查询时,其实并不是我们指定的查询表明,而是当我们设计好实体类时,在查询时,会默认把实体类名首字母变小写然后作为查询的表名,比如这里创建的User实体类,而我们的数据库表正好也是user,索引转换后刚好对应,就成功查询到啦,但是入过数据库表不是user呢?再转换就会出错,所以需要我们设置一个别名
解决方案是使用MP提供的另外一个注解@TableName
来设置表与模型类之间的对应关系。
前面新增成功后,主键ID是一个很长串的内容,我们更想要的是按照数据库表字段进行自增长,在解决这个问题之前,我们先来分析下ID该如何选择:
不同的表应用不同的id生成策略
MP中主键生成策略需要使用一个MP注解@TableId
public class User {@TableId(type = IdType.AUTO)private Long id;private String name;@TableField(value="pwd",select=false)private String password;private Integer age;private String tel;@TableField(exist=false)private Integer online;
}
因为之前生成主键ID的值比较长,会把MySQL的自动增长的值变的很大,所以需要将其调整为目前最新的id值。
在修改前先把这个自增勾去掉点一下保存然后再勾上,然后再整为目前最新的id值,如果不这样可能会出现修改了新的id值后插入仍然没用的情况
根据你数据表中的数据设置新的id值
@TableId(type = IdType.AUTO)private Long id;private String name;private String password;private Integer age;private String tel;
@TableId(type = IdType.INPUT)private Long id;private String name;private String password;private Integer age;private String tel;
把这里的自动递增去掉,然后保存设置
需要自己手动设置id,不设置会报错
@Testvoid testSave() {User user = new User();user.setId(6L);user.setName("一只小可爱~");user.setPassword("!666");user.setAge(18);user.setTel("001");userDao.insert(user);}
@TableId(type = IdType.ASSIGN_ID)private Long id;private String name;private String password;private Integer age;private String tel;
@Testvoid testSave() {User user = new User();user.setName("一只小可爱~");user.setPassword("!666");user.setAge(18);user.setTel("001");userDao.insert(user);}
**注意:**这种生成策略,不需要手动设置ID,如果手动设置ID,则会使用自己设置的值。生成的ID就是一个Long类型的数据。
使用uuid需要注意的是,主键的类型不能是Long,而应该改成String类型,否则报错,注意实体类中也要做出修改!
所以需要修改id为varchar类型的,并且长度要大于32,因为UUID生成的主键为32位,如果长度小的话就会导致插入失败。
@TableId(type = IdType.ASSIGN_UUID)private String id;private String name;private String password;private Integer age;private String tel;
@Testvoid testSave() {User user = new User();user.setName("一只小可爱~");user.setPassword("!666");user.setAge(18);user.setTel("001");userDao.insert(user);}
介绍了这些主键ID的生成策略,我们以后该用哪个呢?
nt deleteBatchIds(@Param(Constants.COLLECTION) Collection extends Serializable> idList);
@Testvoid testDeleteByIds(){//删除指定多条数据List list = new ArrayList<>();list.add(3L);list.add(4L);userDao.deleteBatchIds(list);}
对于删除操作业务问题来说有:
MP中逻辑删除具体该如何实现?
deleted
列字段名可以任意,内容也可以自定义,比如0
代表正常,1
代表删除,可以在添加列的同时设置其默认值为0
正常
标识新增的字段为逻辑删除字段,使用@TableLogic
private Long id;private String name;private String password;private Integer age;private String tel;@TableLogic(value = "0", delval = "1")private Integer deleted;
@Testvoid testDeleteById(){userDao.deleteById(1L);}
MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。
如果还是想把已经删除的数据都查询出来该如何实现呢?
这就需要我们手动书写查询sql了
@Mapper
public interface UserDao extends BaseMapper {//查询所有数据包含已经被删除的数据@Select("select * from user")public List selectAll();
}
@Testvoid testGetAll() {
List users = userDao.selectAll();System.out.println(users);}
乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。
乐观锁的实现方式:
- 数据库表中添加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提供的乐观锁的实现原理分析。
具体的实现步骤如下:
列名可以任意,比如使用version
,给列设置默认值为1
根据添加的字段列名,在模型类中添加对应的属性值
private Long id;private String name;private String password;private Integer age;private String tel;@TableLogic(value = "0", delval = "1")private Integer deleted;@Versionprivate Integer version;
@Configuration
public class MpConfig {@Beanpublic MybatisPlusInterceptor mpInterceptor() {//1.定义Mp拦截器MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();//2.添加乐观锁拦截器mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mpInterceptor;}
}
要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询
@Testvoid testUpdate() {User user = userDao.selectById(2L);user.setName("mumu");user.setPassword("666");userDao.updateById(user);}
看看能不能实现多个人修改同一个数据的时候,只能有一个人修改成功。
@Testvoid testUpdate() {User user = userDao.selectById(2L);User user1 = userDao.selectById(2L);user.setName("mumu");user.setPassword("666");userDao.updateById(user);user1.setName("mumu");user1.setPassword("666999");userDao.updateById(user1);}
运行前:
运行后:
我们发现这个时候只执行了user的修改,而user1的并没成功修改,说明乐观锁实现了
感谢各位大佬观看,如有问题可以评论私信哦,互相学习~谢谢