目录
2.2、在 mapper 对应的 xml 配置文件编写 SQL
2.2.2、在 mapper 对应的 xml 配置文件编写 SQL
1、分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
1.1、添加配置类
@Configuration
@MapperScan("com.zyj.mybatisplus.mapper") //扫描mapper接口所在的包,也可以在对应的mapper接口添加@Mapper注解,就不用写这句
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
1.2、测试
测试方法:
@SpringBootTest
public class MyBatisPlusPluginsTest {
@Autowired
private UserMapper userMapper;
@Test
public void testPage(){
Page<User> page = new Page<>(2, 3);
// SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?,?
userMapper.selectPage(page, null);
System.out.println("getRecords: " + page.getRecords()); // 获取此页的信息
System.out.println("getPages: " + page.getPages()); // 获取总页数
System.out.println("getTotal: " + page.getTotal()); // 获取总的记录数
System.out.println("hasNext: " + page.hasNext()); // 是否有下一页
System.out.println("hasPrevious: " + page.hasPrevious()); // 是否有上一页
}
}
输出结果:
==> Preparing: SELECT COUNT(*) AS total FROM t_user WHERE is_deleted = 0
==> Parameters:
<== Columns: total
<== Row: 6
<== Total: 1
==> Preparing: SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?,?
==> Parameters: 3(Long), 3(Long)
<== Columns: id, name, age, email, is_deleted
<== Row: 4, 小明, 21, [email protected], 0
<== Row: 5, Billie, 24, [email protected], 0
<== Row: 9, 小红, null, [email protected], 0
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7fd8c559]
getRecords: [User(id=4, name=小明, age=21, [email protected], isDeleted=0), User(id=5, name=Billie, age=24, [email protected], isDeleted=0), User(id=9, name=小红, age=null, [email protected], isDeleted=0)]
getPages: 2
getTotal: 6
hasNext: false
hasPrevious: true
2、自定义分页
2.1、在 mapper 接口中定义方法
注:若使用 MyBatis-Plus 提供的分页对象 Page,则 Page 对象必须位于第一个参数的位置
/**
* 通过年龄查询用户信息并分页
* @param page MyBatis-Plus提供的分页对象,必须位于第一个参数的位置
* @param age
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
2.2、在 mapper 对应的 xml 配置文件编写 SQL
2.2.1、开启类型别名
在 application.yml 中开启类型别名
mybatis-plus:
type-aliases-package: com.zyj.mybatisplus.pojo #设置类型别名所对应的包
2.2.2、在 mapper 对应的 xml 配置文件编写 SQL
<!-- Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age); -->
<select id="selectPageVo" resultType="User">
select uid, user_name, age, email from t_user where is_deleted = 0 and age > #{age}
</select>
2.3、测试
测试方法:
@Test
public void testPageVo(){
Page<User> page = new Page<>(1, 3);
userMapper.selectPageVo(page, 20);
System.out.println("getRecords: " + page.getRecords()); // 获取此页的信息
System.out.println("getPages: " + page.getPages()); // 获取总页数
System.out.println("getTotal: " + page.getTotal()); // 获取总的记录数
System.out.println("hasNext: " + page.hasNext()); // 是否有下一页
System.out.println("hasPrevious: " + page.hasPrevious()); // 是否有上一页
}
输出结果:
==> Preparing: SELECT COUNT(*) AS total FROM t_user WHERE is_deleted = 0 AND age > ?
==> Parameters: 20(Integer)
<== Columns: total
<== Row: 3
<== Total: 1
==> Preparing: select uid, user_name, age, email from t_user where is_deleted = 0 and age > ? LIMIT ?
==> Parameters: 20(Integer), 3(Long)
<== Columns: uid, user_name, age, email
<== Row: 3, Tom, 28, [email protected]
<== Row: 4, 小明, 21, [email protected]
<== Row: 5, Billie, 24, [email protected]
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@772861aa]
getRecords: [User(id=null, name=null, age=28, [email protected], isDeleted=null), User(id=null, name=null, age=21, [email protected], isDeleted=null), User(id=null, name=null, age=24, [email protected], isDeleted=null)]
getPages: 1
getTotal: 3
hasNext: false
hasPrevious: false
3、乐观锁
3.1、场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
3.2、乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
3.3、模拟修改中途
3.3.1、添加表及测试数据
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);
3.3.2、创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
3.3.3、添加 mapper 接口
@Mapper
public interface ProductMapper extends BaseMapper<Product>{
}
3.3.4、乐观锁实现流程
数据库中添加version字段
取出记录时,获取当前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;
3.3.5、MyBatisPlus 实现乐观锁
3.3.5.1、实体类对应属性添加 @Version 注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
@Version // 标识该属性为乐观锁版本号的字段
private Integer version;
}
3.3.5.2、在配置类添加乐观锁插件
@Configuration
@MapperScan("com.zyj.mybatisplus.mapper") //扫描mapper接口所在的包,也可以在对应的mapper接口添加@Mapper注解,就不用写这句
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
3.3.5.3、测试
测试方法:
@Test
public void test01(){
// 小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询到的价格:" + productLi.getPrice());
// 小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询到的价格:" + productWang.getPrice());
// 小李将商品价格增加50
// UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
// 小王将商品价格减30
productWang.setPrice(productWang.getPrice() - 30);
productMapper.updateById(productWang);
// 老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("老板查询到的价格:" + productBoss.getPrice());
}
输出结果:
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@41b1f51e]
小李查询到的价格:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a583586] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@975629453 wrapping com.mysql.cj.jdbc.ConnectionImpl@709ed6f3] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7a583586]
小王查询到的价格:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64d4f7c7] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@927159199 wrapping com.mysql.cj.jdbc.ConnectionImpl@709ed6f3] will not be managed by Spring
==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64d4f7c7]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6cd3ad8a] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1964514128 wrapping com.mysql.cj.jdbc.ConnectionImpl@709ed6f3] will not be managed by Spring
==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6cd3ad8a]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f254608] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@787156891 wrapping com.mysql.cj.jdbc.ConnectionImpl@709ed6f3] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 150, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f254608]
老板查询到的价格:150
3.3.5.4、优化测试:使小王修改失败后重新修改
测试方法:
@Test
public void test01(){
// 小李查询商品价格
Product productLi = productMapper.selectById(1);
System.out.println("小李查询到的价格:" + productLi.getPrice());
// 小王查询商品价格
Product productWang = productMapper.selectById(1);
System.out.println("小王查询到的价格:" + productWang.getPrice());
// 小李将商品价格增加50
// UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
productLi.setPrice(productLi.getPrice() + 50);
productMapper.updateById(productLi);
// 小王将商品价格减30
productWang.setPrice(productWang.getPrice() - 30);
int result = productMapper.updateById(productWang);
if(result == 0){
// 若操作失败,重试
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice() - 30);
productMapper.updateById(productNew);
}
// 老板查询商品价格
Product productBoss = productMapper.selectById(1);
System.out.println("老板查询到的价格:" + productBoss.getPrice());
}
输出结果:
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3dd818e8]
小李查询到的价格:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c26273d] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@976725249 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 100, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1c26273d]
小王查询到的价格:100
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@268cbb86] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@501855493 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@268cbb86]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b9a77c8] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@2055276126 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
<== Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@b9a77c8]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75181b50] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@761533964 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 150, 1
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75181b50]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2eeb0f9b] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@454841229 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
==> Parameters: 外星人笔记本(String), 120(Integer), 2(Integer), 1(Long), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2eeb0f9b]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6325f352] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@365211514 wrapping com.mysql.cj.jdbc.ConnectionImpl@20ab3e3a] will not be managed by Spring
==> Preparing: SELECT id,name,price,version FROM t_product WHERE id=?
==> Parameters: 1(Integer)
<== Columns: id, name, price, version
<== Row: 1, 外星人笔记本, 120, 2
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6325f352]
老板查询到的价格:120