目录
分页插件
自定义分页
UserMapper中定义接口方法
UserMapper.xml中编写SQL
测试
乐观锁
乐观锁与悲观锁
模拟修改冲突
乐观锁实现流程
Mybatis-Plus实现乐观锁
分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
- 添加配置类
我们之前实现分页功能的时候,先写了查询功能,然后它会对我们当前的查询功能进行拦截,然后在我们实现功能的基础上,然后进行一些额外的操作,以达到我们额外的功能。
那既然我们当前要配置这样的一个bean,那我们首先什么就需要把咱们的一个对象创建出来,但是这个是用来配置买MP插件,那我们具体要配置什么插件就需要在咱们的插件对象中,再添加一个内部插件,使用interceptor.addInnerInterceptor()方法,方法参数为要使用的插件,当前所使用的是PaginationInnerInterceptor(DbType.MYSQL));
@Configuration
@MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
- 测试类
selectPage方法就是来查询分页数据的,他有两个参数 ,第一个是分页对象,第二个是条件构造器。
@Test
public void testPage(){
//设置分页参数
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
自定义分页
我们当前的查询是我们自定义的时候,那我们现在需要在自己写的sql语句中来通过分页插件实现分页功能,这个时候我们应该如何做?
UserMapper中定义接口方法
根据年龄查询用户列表,并分页显示:
- @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位
- @param age 年龄
接口方法的返回值是谁?我们可以参照select page方法,首先这个方法的返回值是一个IPage,要与他保持一致,且方法的第一个参数为page。
接口方法的返回值一定是一个page对象,这个时候如果我们要想通过这个分页插件来作用于我们自己所写的sql的话,那我们当前这个方法的第一个参数必须得是一个page对象。
IPage<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
UserMapper.xml中编写SQL
<!--SQL片段,记录基础字段-->
<sql id="BaseColumns">id,username,age,email</sql>
<!--IPage<User> selectPageVo(Page<User> page, Integer age);-->
<select id="selectPageVo" resultType="User">
SELECT <include refid="BaseColumns"></include> FROM t_user WHERE age > #{age}
</select>
测试
@Test
public void testSelectPageVo(){
//设置分页参数
Page<User> page = new Page<>(1, 5);
userMapper.selectPageVo(page, 20);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
乐观锁
乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。
同样是小李和小王,因为他们两个是同时操作,所以小李取出的数据是100,小王获取的数据也是100,乐观所一般通过版本号实现,数据库表里面通常会设置一个字段叫做版本号,比如说最初的这个商品数据版本为1,每当来一个用户对这个数据进行更新的操作之后,他就会将我们的数据更新,也会将版本号更新,比如版本号+1。
小李在操作的时候将100改为150,版本号从1改为2,小王在操作的时候,除了要更新这个数据之外,还要把最初所获得的版本号来作为条件,然后进行匹配,而版本号已经发生了变化,小王就无法匹配到100这条数据进行修改,等我们加上了版本号之后,我们以后每一次的修改操作都要先查询当前数据以及所对应的版本号,我们每一次的更新也就多了一个条件,就是当前的版本号,只要在操作之前,版本号发生了变化,那么在修改的时候是一定不成功的。
模拟修改冲突
- 数据库中增加商品表
CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
- 添加数据
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
- 添加实体
import lombok.Data;
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
- 添加mapper
public interface ProductMapper extends BaseMapper<Product> {
}
- 测试
首先我们先通过小李或小王来获取商品的价格,然后我们再通过小李和小王去修改这个价格,然后最终再以老板的角度再次去查询我们最终这个数据库中存储的一个价格。
@Test
public void testConcurrentUpdate() {
//1、小李 100
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王 100
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库 100+50
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库 100-30
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);//小王的操作将小李的操作覆盖掉
//最后的结果
Product p3 = productMapper.selectById(1L);
//价格覆盖,最后的结果:70
System.out.println("最后的结果:" + p3.getPrice());
}
乐观锁实现流程
- 数据库中添加version字段,并在实体类当中标识乐观锁版本号字段(初始为0)
- 添加乐观锁插件
首先小李查询的数据为100,小王查询也是100,再往下小李修改数据+50
可是当小王修改数据时,操作执行不了
所以最终老板所获取的价格就是150
取出记录时,获取当前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND
`version`=1
Mybatis-Plus实现乐观锁
- 修改实体类,添加乐观锁插件配置
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
- 优化测试流程
@Test
public void testConcurrentVersionUpdate() {
//小李取数据
Product p1 = productMapper.selectById(1L);
//小王取数据
Product p2 = productMapper.selectById(1L);
//小李修改 + 50
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改的结果:" + result1);
//小王修改 - 30
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改的结果:" + result2);
if(result2 == 0){
//失败重试,重新获取version并更新
p2 = productMapper.selectById(1L);
p2.setPrice(p2.getPrice() - 30);
result2 = productMapper.updateById(p2);
}
System.out.println("小王修改重试的结果:" + result2);
//老板看价格
Product p3 = productMapper.selectById(1L);
System.out.println("老板看价格:" + p3.getPrice());
}