该文章持续不定期更新中,最后一次更新于2020年6月22日
说明:
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆,T
为任意实体对象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)
值 | 描述 |
---|---|
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 进行赋值
这需要你的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,对应创建时间和更新时间
我们有两种操作,一个是在数据库操作,一个是在代码中操作,前者不推荐!
需求:当要更新一条记录的时候,希望这条记录没有被别人更新
我们使用一个场景来帮助理解
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1多万。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
乐观锁实现方式:
简单理解就是在多线程的时候,如果需要去更新一条数据的时候刚好这条数据被别人更新了,版本验证不对,就更新失败。
示例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);
}
特别说明:
newVersion = oldVersion + 1
newVersion
会回写到 entity
中updateById(id)
与 update(entity, wrapper)
方法update(entity, wrapper)
方法下, wrapper
不能复用!!! @Test
public void testSelectById(){
User user = userMapper.selectById(12);
System.out.println(user);
}
@Test
public void testSelect(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1,12));
System.out.println("------------------------");
users.forEach(System.out::println);
}
比如当我们想查年龄为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);
}
}
@Test
public void testDeleteById(){
userMapper.deleteById(1243823657826996248L);
}
@Test
public void testDeleteByMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","乐心湖是小白");
userMapper.deleteByMap(map);
}
@Test public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1240620674645544961L,124062067464554496 2L));
}
在数据表中增加一个deleted字段,默认为0
SpringBoot 配置方式:
实体类字段上加上@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 #全局逻辑删除字段值
如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。