Loading... [tip type="warning"] 该文章持续不定期更新中,最后一次更新于2020年6月22日 [/tip] # Mapper层接口  # Service层接口   > 说明: > > - 通用 Service CRUD 封装[IService](https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/service/IService.java)接口,进一步封装 CRUD 采用 `get 查询单行` `remove 删除` `list 查询集合` `page 分页` 前缀命名方式区分 `Mapper` 层避免混淆, > - 泛型 `T` 为任意实体对象 > - 建议如果存在自定义通用 Service 方法的可能,请创建自己的 `IBaseService` 继承 `Mybatis-Plus` 提供的基类 > - 对象 `Wrapper` 为 [条件构造器](https://mp.baomidou.com/guide/wrapper.html) # 插入数据 在此之前,请务必开启日志配置。 添加测试方法,我们不设置id,看看会不会报错。 ```java @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属性添加一个注解。 ```java @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生成 | ```java public enum IdType { AUTO(0), // 数据库id自增 NONE(1), // 未设置主键 INPUT(2), // 手动输入 ASSIGN_ID(3), // 默认的全局唯一id ASSIGN_UUID(4), // 全局唯一id uuid } ``` # 更新数据 ```java @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 - 勾选上自动更新  ## 代码操作 [Mybatis-Plus 时间自动填充](https://www.xn2001.com/archives/509.html) # 乐观锁 需求:当要更新一条记录的时候,希望这条记录没有被别人更新 我们使用一个场景来帮助理解 ## 场景 一件商品,成本价是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原理: ```sql update tbl_user set name = 'update',version = 3 where id = 100 and version = 2 ``` ## 数据库操作 在表中建立一个字段,叫version,默认为1,注释为乐观锁 ## 插件配置 ```java @Configuration public class MybatisPlusConfig { @Bean public OptimisticLockerInterceptor optimisticLockerInterceptor() { return new OptimisticLockerInterceptor(); } } ``` 在User类中加入version字段,并且必须带上注解@Version ``` java @Version private Integer version; ``` ## 测试 ```java @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查询 ```java @Test public void testSelectById(){ User user = userMapper.selectById(12); System.out.println(user); } ``` ## Id批量查询 ```java @Test public void testSelect(){ List<User> users = userMapper.selectBatchIds(Arrays.asList(1,12)); System.out.println("------------------------"); users.forEach(System.out::println); } ```  ## Map查询 比如当我们想查年龄为16岁的人 ```java @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中能够非常非常简单就帮你搞定。 写一个配置类。 ```java //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里,不需要我们重新定义去接收查询结果。 建议:仔细查看官方关于分页的介绍和案例,具体开发实战并不会在这里测试。 ```java @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删除 ```java @Test public void testDeleteById(){ userMapper.deleteById(1243823657826996248L); } ``` ## Map删除 ```java @Test public void testDeleteByMap(){ HashMap<String, Object> map = new HashMap<>(); map.put("name","乐心湖是小白"); userMapper.deleteByMap(map); } ``` ## Id批量删除 ```java @Test public void testDeleteBatchId(){ userMapper.deleteBatchIds(Arrays.asList(1240620674645544961L,124062067464554496 2L)); } ``` # 逻辑删除 - 物理删除 :从数据库中直接移除 - 逻辑删除 :在数据库中没有真的被移除,而是通过一个变量来让他失效 - deleted = 0 ——> deleted = 1 - 管理员可以查看被删除的记录,防止数据的丢失,类似于回收站的功能。 在数据表中增加一个deleted字段,默认为0 SpringBoot 配置方式: - application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无): ```yaml 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`注解 ```java @TableLogic private Integer deleted; ``` 效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会) ```sql example 删除时 update user set deleted=1 where id =1 and deleted=0 查找时 select * from user where deleted=0 ``` 全局逻辑删除:3.3.0开始支持 如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。 使用此配置则不需要在实体类上添加 @TableLogic。 但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除。 ```yaml mybatis-plus: global-config: db-config: logic-delete-field: flag #全局逻辑删除字段值 ``` ## 附件说明 - 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。 - 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。 如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。 - 若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。<hr class="content-copyright" style="margin-top:50px" /><blockquote class="content-copyright" style="font-style:normal"><p class="content-copyright">版权属于:乐心湖's Blog</p><p class="content-copyright">本文链接:<a class="content-copyright" href="https://www.xn2001.com/archives/419.html">https://www.xn2001.com/archives/419.html</a></p><p class="content-copyright">声明:博客所有文章除特别声明外,均采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh" target="_blank" rel="nofollow noopener noopener" one-link-mark="yes">CC BY-SA 4.0 协议</a> ,转载请注明出处!</p></blockquote> 腾讯云社区邀请各位技术博主加入,福利多多噢! Last modification:June 22nd, 2020 at 11:45 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments Pay by AliPay Pay by WeChat