MyBatisPlus快速入门:简化CRUD操作
一.MyBatisPlus 入门与简介
Mybatis的入门案例与简介,这个和其他课程不一样,其他课程都是先介绍概念,然后再写入门案例,而对于MyBatisPlus的学习,我们将顺序做了调整,主要的原因MyBatisPlus主要是对MyBatis的简化,所有我们先体会下它的简化在哪,然后在学习是什么,以及它帮我们都做那些事。
步骤1: 创建数据库及表
步骤2:创建springboot工程
步骤3: 勾选配置使用
注意:由于MP并未被收录到idea的系统内置配置,无法直接选择加入,需要手动
在pom.xml中配置添加
步骤4:pom.xml补全依赖
步骤5:添加MP的相关配置信息
步骤6:根据数据库表创建实体类
步骤7:创建Dao接口
步骤8:编写引导类
说明:Dao接口要想被容器扫描到,有两种解决方案:
方案一:在Dao接口上添加@Mapper注解,并且确保Dao在引导类所在包或其子包中,
该方法缺点是需要在每一Dao中添加注解。
方案二:在引导类上添加@MapperScan注解,其属性为所要扫描的Dao所在包,该方案
的好处是只需要写一次,则指定包下的所有Dao接口都能被扫描到,@Mapper就可以不写。
步骤9:编写测试类
二,标准数据层开发
2.1 标准的crud使用
2.2 分页功能
分页查询使用的方法:
IPage<T> selectPage(IPage<T> page,wrapper<T> querywrapper)
IPage:用来构建分页查询条件
Wrapper:用来构建条件查询的条件,目前我们没有可之间传null.
IPage:返回值,你会发现构建分页条件和方法的返回值都是IPage.
IPage是一个接口,我们需要找到它的实现类来构建它,具体的实现类进入到Page
中按住ctrl+h,会找到其有一个实现类为page.
步骤1:调用方法传入参数获取返回值
// 创建Ipage分页对象,设置分页参数,1为当前页码,3为每页显示记录数
IPage<User> page = new Page<>(1.3);
// .执行分页查询
us2erDao.selectPage(page,null)
// 获取分页结果
当前页码值:page.getCurrent()
// 每页显示数
page.getSize()
//一共多少页
page.getPages()
//一共多少条数据
page.getTotal()
// 数据
page.getRecords()
步骤2:设置分页拦截器
这个拦截器MP已经为我们提供好了,我们只需要将其配置成spring管理的bean对象即可。
@configuration
public class MybatisPlusConfig{
@Bean
public mybatisPlusInterceptor mybatisPlusInterceptor(){
// 创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor = new MyBatisPludInterceptor():
// 添加分页拦截器
mpInterceptor addInnerInterceptor(new PaginationInnerInterceptor());
retrun mpInterceptor;
}}
步骤3:运行测试程序
如果想查看MP执行的sql哦语句,可以修改application.yml配置文件
mybatis-plus:
configuration:
log-impl:org.apache.ibatis.logging.stdout.stdOutImpl # 打印SQL语句到控制台。
DQL编程控制
3.1.1构建条件查询
1.第一种,QueeryWrapper
@springBootTest
class Mybatisplus02DqlAppplicationTests{
@Test
void testGetAll(){
QueryWrapper qw = new Querywreapper();
qw.lt("age",18);
List<User> userList = userDao.selectList(qw);
System.out.println(userlist);
}
lt:小于(<),最终sql语句为
select id,name,password ,age,tel from user where(age<?)
2.接着来看第三种:LambdaQueryWrapper
@springBootTest
class Mybatisplus02DqlApplicationTests{
@Test
void testGetAll(){
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
gt:大于(>),最终的SQL语句为
select id,name,password,age,tel from user where(age< ? and age>?)
构建多条件的时候,可以支持链式编程
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge.10);
List<User> userList = userDao.selectList(lqw);
System.out.println(userList);
需求:查询数据库表中,年龄小于10或大于30 的数据
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.lt(User::getAge,10).or().gt(User::getAge,30);
List<User> userList = userDao.selectList(lqw);
3.2查询投影
目前我们在查询数据的时候,什么都没有做默认就是查询表中所有字段的内容,我们所说的查询投影即就是不查询所有字段,只查询出指定内容的数据。
select(...)方法用来设置查询字段列,可以设置多个,最终的sql语句为:
select id,name,age from user;
如果使用的不是lambda,就需要手动指定字段
3.2.2聚合查询
count:总记录数
max:最大值
min:最小值
avg:平均值
sum:求和
QueryWrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count");
// select count(*) as count from user;
List<Map<String,Object>> userList = userDao.selectMaps(lqw);
3.2.3分组查询
需求:分组查询,完成group by 的查询使用
Querywrapper<User> lqw = new QueryWrapper<User>();
lqw.select("count(*) as count,tel");
lqw.groupBy("tel");
List<Map<String,Object>> list = userDao.selectMaps(lqw);
System.out.println(list);
groupBy为分组,最终的sql语句为:
select count(*) as count,tel from user group by tel;
聚合与分组查询,无法使用lambda表达式来完成
3.3 查询条件
MP的查询条件有很多:
范围匹配(> , = , between)
模糊匹配(like)
空判定(null)
包含性匹配(in)
分组(group)
排序(order)
3.3.1 等值查询
需求:根据用户名和密码查询用户信息
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
lqw.wq(User::getName,"Jerry").eq(User::getPassword,"jerry");
User loginUser = userDao.selectOne(lqw);
eq:相当于=对应的sql语句为:
select id,name,password,age,tel from user where(name=? and password=?)
selectList: 查询结果为多个或者单个
selectOne:查询结果为单个
3.3.2范围查询
需求:对年龄进行范围查询,使用lt(),le(),gt(),ge(),between()进行范围查询
LambdaQueryWrapper<User>lqw = new LambdaQueryWrapper<User>();
lqw.between(User::getAge,10,30);
List<User> userList = userDao.selectList(lqw);
gt():大于(>)
ge():大于等于(>=)
lt(): 小于(<)
lte():小于等于(<=)
between():between?and?
3.3.3模糊查询
需求:查询表中name属性的值以3开头的用户信息,使用like进行模糊查询
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<User>();
lqw.likeLeft(User::getName,"J");
// select id, name,password,age,tel from user where (name like ?)
List<User> userList = userDao.selectList(lqw);
like():前后加百分号,如%J%
likeLeft():前面加百分号,如%J
likeRight():后面加百分号,如J%
3.3.4排序查询
需求:查询所有数据,然后按照id降序
LambdaQueryWrapper<User> lwq = new LambdaQueryWrapper<>();
/*
condition:条件,返回boolean.
当condition为true,进行排序,如果为false,则不排序
isAsc:是否为升序,true为升序,false为降序
columns:需要操作的列
lwq.orderBy(true,true,user::getId);
*/
除了上面演示的这种实现方式,还有其他的排序方法:
orderBy排序
condition:条件,true则添加排序,false则不添加排序
isAsc:是否为升序,true升序,false降序
columns:排序字段,可以有多个
orderByAsc/Desc(单个column):按照字段进行升序/降序
orderByAsc/Desc(多个column):按照多个字段进行升序/降序
3.4映射匹配兼容性
问题一:表字段与编码属性设计不同步
MP给我们提供了一共注解@TableField,使用该注解可以实现模型类属性名和表的列名之间的映射关系。
问题二:彪马中添加了数据库中未定义的属性
当模型中多了一个数据库表不存在的字段,就会导致生成的sql语句中在select的时候查询了数据库不存在的字段,程序就会报错------>通过@TableField的exist属性进行解决。
问题三:采用默认查询开发了更多字段查看权限
问题四:表名与编码开发设计不同步--->@TableName解决
四.DML编码控制
4.1 id生成策略控制
在这里我们有需要用到MP的一个注解@TableId
4.1.1 ID生成策略对比
NONE: 不设置id生成策略,MP不自动生成,约等于INPUT,所以这两种方式都需要用户手动设置,但是手动设置第一个问题是容易出现相同的ID造成主键冲突,为了保证主键不冲突就需要做很多判定,实现起来比较复杂.
AUTO:数据库ID自增,这种策略适合在数据库服务器只有1台的情况下使用,不可作为分布式ID使用.
ASSIGN_UUID:可以在分布式的情况下使用,而且能够保证唯一,但是生成的主键是32位的字符串,长度过长占用空间而且还不能排序,查询性能也慢.
ASSIGN_ID:可以在分布式的情况下使用,生成的是Long类型的数字,可以排序性能也高,但是生成的策略和服务器时间有关,如果修改了系统时间就有可能导致出现重复主键(雪花算法).
拓展:
分布式ID是什么?
当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储比如订单表就有可能被存储在不同的服务器上
如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
这个时候就需要一个全局唯一ID,这个ID就是分布式ID。
雪花算法:
雪花算法(SnowFlake),是Twitter官方给出的算法实现 是用Scala写的。其生成的结果是一个64bit大小整数,它的结构如下图:
1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所
以最高位固定为0。
2. 41bit-时间戳,用来记录时间戳,毫秒级
3. 10bit-工作机器id,用来记录工作机器id,其中高位5bit是数据中心ID其取值范围0-31,低位5bit是
工作节点ID其取值范围0-31,两个组合起来最多可以容纳1024个节点
4. 序列号占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID
4.2 多记录操作
根据ID批量删除,参数是一个集合,可以存放多发个id值。
需求:根据传入的id集合将数据库表中的数据删除掉。
根据ID 批量查询,参数是一个集合,可以存放多个id值。
需求:根据传入的ID集合查询用户信息
4.3 逻辑删除
物理删除:业务数据从数据库中丢弃,执行的是delete操作.
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中,执行的是update操作.
步骤1:修改数据库表添加deleted列
字段名可以任意,内容也可以自定义,比如0代表正常,值为0正常。
步骤2:实体类添加属性
(1)添加与数据库表的列对应的一个属性名,名称可以任意,如果和数据表列名对不上,可以使用@TableField进行关系映射,如果一致,则会自动对应.
(2)标识新增的字段为逻辑删除字段,使用@TableLogic.
从测试结果来看,逻辑删除最后走的是update操作,会将指定的字段修改成删除状态对应的值。思考逻辑删除,对查询有没有影响呢?
执行查询操作,MP的逻辑删除会将所有的查询都添加一个未被删除的条件,也就是已经被删除的数据是不应该被查询出来的。
在配置文件中添加全局配置,如下:
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted # 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
介绍完逻辑删除,逻辑删除的本质为:
逻辑删除的本质其实是修改操作。如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
4.4 乐观锁
4.4.1 概念
在讲解乐观锁之前,我们还是先来分析下问题:业务并发现象带来的问题:秒杀
假如有100个商品或者票在出售,为了能保证每个商品或者票只能被一个人购买,如何保证不会出现超买或者重复卖对于这一类问题,其实有很多的解决方案可以使用第一个最先想到的就是锁,锁在一台服务器中是可以解决的,但是如果在多台服务器下锁就没有办法控制,比如12306有两台服务器在进行卖票,在两台服务器上都添加锁的话,那也有可能会导致在同一时刻有两个线程在进行卖票,还是会出现并发问题。
我们接下来介绍的这种方式是针对于小型企业的解决方案,因为数据库本身的性能就是个瓶颈,如果对其并发量超过2000以上的就需要考虑其他的解决方案了。
简单来说,乐观锁主要解决的问题是当要更新一条记录的时候,希望这条记录没有被别人更新。
4.4.2 实现思路
乐观锁的实现方式:
数据库表中添加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提供的乐观锁的实现原理分析。
4.4.3 实现步骤
步骤1:数据库表添加列
列名可以任意,比如使用version,给列设置默认值为1
步骤2:在模型类中添加对应的属性
根据添加的字段列名,在模型类中添加对应的属性值
步骤3:添加乐观锁的拦截器
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new
OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
步骤4:执行更新操作
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
User user = new User();
user.setId(3L);
user.setName("Jock666"); user.setVersion(1);
userDao.updateById(user); }
}
所以要想实现乐观锁,首先第一步应该是拿到表中的version,然后拿version当条件在将version加1更新回到数据库表中,所以我们在查询的时候,需要对其进行查询......
private UserDao userDao;
@Test
void testUpdate(){
//1.先通过要修改的数据id将当前数据查询出来 User user = userDao.selectById(3L); //2.将要修改的属性逐一设置进去
user.setName("Jock888");
userDao.updateById(user);
}
}
模拟加锁状况:
@Autowired
private UserDao userDao;
@Test
void testUpdate(){
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗? }
}