Mybatis-Plus基础功能测试使用
该文章持续不定期更新中,最后一次更新于2020年6月22日
Mapper层接口
Service层接口
说明:
- 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分Mapper
层避免混淆, - 泛型
T
为任意实体对象 - 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类 - 对象
Wrapper
为 条件构造器
插入数据
在此之前,请务必开启日志配置。
添加测试方法,我们不设置id,看看会不会报错。
@Test
public void testInsert(){
User user = new User();
user.setName("乐心湖");
user.setAge(18);
user.setEmail("jialna@qq.com");
int insert = userMapper.insert(user);
System.out.println(insert);
System.out.println(user);
}
我们看到插入成功了,观察控制台中的sql语句,发现它自动给我们生成了一个id。
这就是下面说的雪花算法。
主键生成策略
推荐阅读:分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
自3.3.0开始,默认使用雪花算法+UUID(不含中划线);
mybaits-plus中默认的使用的是ID_WORKER,即当然我们也可以自己修改主键生成策略,如主键自增。@TableId(type = IdType.ID_WORKER)
使用的是雪花算法生成,全局唯一id。
雪花算法
snowflflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!
主键自增
我们首先需要在User中对id主键开启自增,然后在User类里的id属性添加一个注解。
@TableId(type = IdType.AUTO)
@TableId
值 |
描述 |
---|---|
AUTO |
数据库自增 |
NONE |
MP set 主键,雪花算法实现 |
INPUT |
需要开发者手动赋值 |
ASSIGN_ID |
MP 分配 ID,Long、Integer、String |
ASSIGN_UUID |
分配 UUID,Strinig |
INPUT 如果开发者没有手动赋值,则数据库通过自增的方式给主键赋值,如果开发者手动赋值,则存入该值。
AUTO 默认就是数据库自增,开发者无需赋值。
ASSIGN_ID MP 自动赋值,雪花算法。
ASSIGN_UUID 主键的数据类型必须是 String,自动生成 UUID 进行赋值
UUID
这需要你的id为String,也就是数据库id改为varchar类型。
生成一个全局唯一的id,带字母和数字。
换了。。。现在叫ASSIGN_UUID
方法 |
主键生成策略 |
主键类型 |
说明 |
---|---|---|---|
nextId |
ASSIGN_ID,ID_WORKER,ID_WORKER_STR |
Long,Integer,String |
支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer |
nextUUID |
ASSIGN_UUID,UUID |
String |
默认不含中划线的UUID生成 |
public enum IdType {
AUTO(0), // 数据库id自增
NONE(1), // 未设置主键
INPUT(2), // 手动输入
ASSIGN_ID(3), // 默认的全局唯一id
ASSIGN_UUID(4), // 全局唯一id uuid
}
更新数据
@Test
public void testUpdate() {
User user = new User();
//通过条件自动拼接动态sql
user.setId(6);
user.setName("乐心湖666");
user.setAge(18);
//注意:updateById 但是参数是一个 对象!
int i = userMapper.updateById(user);
System.out.println(i);
}
自动填充
参考:https://mp.baomidou.com/guide/auto-fill-metainfo.html
我们在数据库表中经常会有两个字段,分别是create_time和update_time,对应创建时间和更新时间
我们有两种操作,一个是在数据库操作,一个是在代码中操作,前者不推荐!
数据库操作(不建议)
- 类型为datetime
- 默认为:CURRENT_TIMESTAMP
- 勾选上自动更新
代码操作
乐观锁
需求:当要更新一条记录的时候,希望这条记录没有被别人更新
我们使用一个场景来帮助理解
场景
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。
乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
简单理解就是在多线程的时候,如果需要去更新一条数据的时候刚好这条数据被别人更新了,版本验证不对,就更新失败。
示例SQL原理:
update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
数据库操作
在表中建立一个字段,叫version,默认为1,注释为乐观锁
插件配置
@Configuration
public class MybatisPlusConfig {
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
在User类中加入version字段,并且必须带上注解@Version
@Version
private Integer version;
测试
@Test
public void testUpdate(){
User user = userMapper.selectById(12L);
user.setAge(11);
userMapper.updateById(user);
}
特别说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
-
newVersion
会回写到entity
中 - 仅支持
updateById(id)
与update(entity, wrapper)
方法 - 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
查询数据
Id查询
@Test
public void testSelectById(){
User user = userMapper.selectById(12);
System.out.println(user);
}
Id批量查询
@Test
public void testSelect(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1,12));
System.out.println("------------------------");
users.forEach(System.out::println);
}
Map查询
比如当我们想查年龄为16岁的人
@Test
public void testSelectByBatchIds(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","乐心湖是小白");
map.put("id","12");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
分页查询
一个非常常用的功能,在MP中能够非常非常简单就帮你搞定。
写一个配置类。
//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
测试第二页的4条记录,效果立竿见影。
注意:查询出来的结果自动返回到了page里,不需要我们重新定义去接收查询结果。
建议:仔细查看官方关于分页的介绍和案例,具体开发实战并不会在这里测试。
@Test
public void testSelectPage(){
/**
* current: 第几页
* size:每页的数量
*/
Page<User> page = new Page<>(2, 4);
userMapper.selectPage(page, null);
for (User user : page.getRecords()) {
System.out.println(user);
}
}
删除数据
Id删除
@Test
public void testDeleteById(){
userMapper.deleteById(1243823657826996248L);
}
Map删除
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","乐心湖是小白");
userMapper.deleteByMap(map);
}
Id批量删除
@Test public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1240620674645544961L,124062067464554496 2L));
}
逻辑删除
- 物理删除 :从数据库中直接移除
- 逻辑删除 :在数据库中没有真的被移除,而是通过一个变量来让他失效
- deleted = 0 ——> deleted = 1
- 管理员可以查看被删除的记录,防止数据的丢失,类似于回收站的功能。
在数据表中增加一个deleted字段,默认为0
SpringBoot 配置方式:
- application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无): mybatis-plus: global-config: db-config: logic-delete-field: flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。 logic-delete-value: 1 # 逻辑已删除值(默认为 1) logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
实体类字段上加上@TableLogic
注解
@TableLogic
private Integer deleted;
效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)
example
删除时 update user set deleted=1 where id =1 and deleted=0
查找时 select * from user where deleted=0
全局逻辑删除:3.3.0开始支持
如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。
使用此配置则不需要在实体类上添加 @TableLogic。
但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除。
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag #全局逻辑删除字段值
附件说明
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。
如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。
- 若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。
- Thinking in SQL系列之数据挖掘C4.5决策树算法
- 设计模式专题(二十四) ——访问者模式
- PHP实用功能——modern PHP读书笔记(一)
- ModernPHP读书笔记(二) ——PHP开发标准
- iBatis.Net(6):Data Map(深入)
- iBatis.Net(5):Data Map(了解)
- ModernPHP读书笔记(三)——PHP的良好实践
- PHP开发过程的那些坑(一) ——对象拷贝
- PHP开发过程的那些坑(二) ——PHP empty函数
- Thinking in SQL系列之数据挖掘Apriori关联分析再现啤酒尿布神话
- PHP开发过程的那些坑(三) ——PHParray_shift函数
- CSS3弹性盒布局
- iBatis.Net(4):DataMapper API
- PHP开发过程的那些坑(四) ——PDO bindParam函数
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 「高并发通信框架Netty4 源码解读(三)」NIO缓冲区Buffer详解
- UML类图符号:各种关系说明以及举例
- 「高并发通信框架Netty4 源码解读(四)」NIO缓冲区之字节缓冲区ByteBuffer详解
- 「influxDB 原理与实践(三)」连续查询
- 为什么使用OPA而不是原生的Pod安全策略?
- 浅入浅出 Java ConcurrentHashMap
- 「高并发通信框架Netty4 源码解读(五)」NIO通道Channel详解
- 图解一致性哈希算法,全网(小区局域网)最通俗易懂
- 「高并发通信框架Netty4 源码解读(六)」NIO通道之Socket通道
- 「高并发通信框架Netty4 源码解读(番外篇)」NIO实现大文件传输
- 「高并发通信框架Netty4 源码解读(七)」NIO通道之Selector选择器
- Python与seo工具脚本,360/搜狗相关搜索词采集源码参考
- 逐行阅读Spring5.X源码(八)Mybatis是如何利用MapperScan完成扫描的?
- KEDA|Kubernetes中基于事件驱动的自动伸缩
- 「高并发通信框架Netty4 源码解读(八)」NIO应用——聊天案例及Reactor线程模式