# 微服务 ## Mybatis-Plus ### 快速开始 **1.引入依赖** ```XML com.baomidou mybatis-plus-boot-starter 3.5.3.1 ``` 由于这个starter包含对mybatis的自动装配,因此完全可以替换掉Mybatis的starter。 **2.定义mapper** 修改mp-demo中的`com.itheima.mp.mapper`包下的`UserMapper`接口,让其继承`BaseMapper`: ``` public interface UserMapper extends BaseMapper { } ``` MybatisPlus如何知道我们要查询的是哪张表?表中有哪些字段呢? **约定大于配置** **泛型中的User**就是与数据库对应的PO. MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下: - MybatisPlus会把PO实体的**类名**驼峰转下划线作为**表名** - MybatisPlus会把PO实体的所有**变量名**驼峰转下划线作为表的**字段名**,并根据变量类型推断字段类型 - MybatisPlus会把名为**id**的字段作为**主键** 但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。 **3.常见注解** **@TableName** - 描述:表名注解,标识实体类对应的表 - 使用位置:实体类 ```Java @TableName("user") public class User { private Long id; private String name; } ``` **@TableId** - 描述:主键注解,标识实体类中的主键字段 - 使用位置:实体类的主键字段 `TableId`注解支持两个属性: | **属性** | **类型** | **必须指定** | **默认值** | **描述** | | :------- | :------- | :----------- | :---------- | :----------- | | value | String | 否 | "" | 主键字段名 | | type | Enum | 否 | IdType.NONE | 指定主键类型 | ``` @TableName("user") public class User { @TableId(value="id",type=IdType.AUTO) private Long id; private String name; } ``` 必须指定type=IdType.AUTO,默认是雪花算法算出一个随机的id(插入操作时) **@TableField** 一般情况下我们并不需要给字段添加`@TableField`注解,一些特殊情况除外: - 成员变量名与数据库字段名不一致 - 成员变量是以`isXXX`命名,按照`JavaBean`的规范,`MybatisPlus`识别字段时会把`is`去除,这就导致与数据库不符。 - 成员变量名与数据库一致,但是与数据库的**关键字(如order)**冲突。使用`@TableField`注解给字段名添加转义字符:```` 支持的其它属性如下: exist:默认为true,表示是数据库字段,若 ``` @TableField(exist=false) private String address; ``` 将自动跳过address的增删查改,因为它不被视为字段。 ```Java @TableName("user") public class User { @TableId private Long id; private String name; private Integer age; @TableField("isMarried") private Boolean isMarried; @TableField("`order`") private String order; } ``` ### 常见配置 大多数的配置都有默认值,因此我们都无需配置。但还有一些是没有默认值的,例如: - 实体类的别名扫描包 - 全局id类型 要改也就改这两个即可 ```YAML mybatis-plus: type-aliases-package: com.itheima.mp.domain.po global-config: db-config: id-type: auto # 全局id类型为自增长 ``` ### 核心功能 #### 条件构造器 除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以`id`作为`where`条件以外,还支持更加复杂的`where`条件。 `Wrapper`就是条件构造的抽象类,其下有很多默认实现,继承关系如图: ![image-20240813112049624](https://pic.bitday.top/i/2025/03/19/u7fwe0-2.png) ![image-20240813134824946](https://pic.bitday.top/i/2025/03/19/u7f24w-2.png) **QueryWrapper** ``` /**查询出名字中带o的,存款大于等于1000元的人的id,username,info,balance * SELECT id,username,info,balance * FROM user * WHERE username LIKE ? AND balance >=? */ @Test void testQueryWrapper(){ QueryWrapper wrapper =new QueryWrapper() .select("id","username","info","balance") .like("username","o") .ge("balance",1000); //查询 List users=userMapper.selectList(wrapper); users.forEach(System.out::println); } ``` ``` //更新用户名为jack的用户的余额为2000 @Test void testUpdateByQueryWrapper() { // 1.构建查询条件 where name = "Jack" QueryWrapper wrapper = new QueryWrapper().eq("username", "Jack"); // 2.更新数据,user中非null字段都会作为set语句 User user = new User(); user.setBalance(2000); userMapper.update(user, wrapper); } ``` **UpdateWrapper** 基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。 例如:更新id为`1,2,4`的用户的余额,扣200,对应的SQL应该是: ```Java UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4) ``` ``` @Test void testUpdateWrapper() { List ids = List.of(1L, 2L, 4L); // 1.生成SQL UpdateWrapper wrapper = new UpdateWrapper() .setSql("balance = balance - 200") // SET balance = balance - 200 .in("id", ids); // WHERE id in (1, 2, 4) // 2.更新,注意第一个参数可以给null,也就是不填更新字段和数据, // 而是基于UpdateWrapper中的setSQL来更新 userMapper.update(null, wrapper); } ``` **LambdaQueryWrapper** 无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,会出现字符串`魔法值`。这在编程规范中显然是不推荐的。 那怎么样才能不写字段名,又能知道字段名呢? 其中一种办法是基于变量的`gettter`方法结合反射技术。因此我们只要将条件对应的字段的`getter`方法传递给MybatisPlus,它就能计算出对应的变量名了。而传递方法可以使用JDK8中的`方法引用`和`Lambda`表达式。 因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个: - LambdaQueryWrapper - LambdaUpdateWrapper ``` @Test void testLambdaQueryWrapper() { // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000 QueryWrapper wrapper = new QueryWrapper<>(); wrapper.lambda() .select(User::getId, User::getUsername, User::getInfo, User::getBalance) .like(User::getUsername, "o") .ge(User::getBalance, 1000); // 2.查询 List users = userMapper.selectList(wrapper); users.forEach(System.out::println); } ``` **总之,推荐使用LambdaQueryWrapper,若要使用set,才用LambdaUpdateWrapper。普通的QueryWrapper用得少** #### 自定义sql 可以让我们利用Wrapper生成查询条件,再结合Mapper.xml编写SQL 1.先在业务层利用wrapper创建条件,传递参数 ``` @Test void testCustomWrapper() { // 1.准备自定义查询条件 List ids = List.of(1L, 2L, 4L); QueryWrapper wrapper = new QueryWrapper().in("id", ids); // 2.调用mapper的自定义方法,直接传递Wrapper userMapper.deductBalanceByIds(200, wrapper); } ``` 2. 自定义mapper层把wrapper和其他业务参数传进去,自定义sql语句书写sql的前半部分,后面拼接。 ``` package com.itheima.mp.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.itheima.mp.domain.po.User; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Update; import org.apache.ibatis.annotations.Param; public interface UserMapper extends BaseMapper { @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}") void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper wrapper); } ``` 这里wrapper前面必须写@Param("ew") ${ew.customSqlSegment}可以自动拼接前面写的条件语句 #### Mapper层常用方法 **查询:** selectById:根据主键 ID 查询单条记录。 selectBatchIds:根据主键 ID 批量查询记录。 selectOne:根据指定条件查询单条记录。 ``` QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", "alice"); User user = userMapper.selectOne(queryWrapper); ``` selectList:根据指定条件查询多条记录。 ``` QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.ge("age", 18); List users = userMapper.selectList(queryWrapper); ``` **插入:** insert:插入一条记录。 ``` User user = new User(); user.setUsername("alice"); user.setAge(20); int rows = userMapper.insert(user); ``` **更新** updateById:根据主键 ID 更新记录。 ``` User user = new User(); user.setId(1L); user.setAge(25); int rows = userMapper.updateById(user); ``` update:根据指定条件更新记录。 ``` UpdateWrapper updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("username", "alice"); User user = new User(); user.setAge(30); int rows = userMapper.update(user, updateWrapper); ``` **删除操作** deleteById:根据主键 ID 删除记录。 deleteBatchIds:根据主键 ID 批量删除记录。 delete:根据指定条件删除记录。 ``` QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("username", "alice"); int rows = userMapper.delete(queryWrapper); ``` #### IService ![image-20240815092311650](https://pic.bitday.top/i/2025/03/19/u7g3qb-2.png) ![image-20240815092324887](https://pic.bitday.top/i/2025/03/19/u7gmfr-2.png) ![image-20240815092338012](https://pic.bitday.top/i/2025/03/19/u7frqa-2.png) ![image-20240815092352179](https://pic.bitday.top/i/2025/03/19/u7gubw-2.png) ![image-20240815092420201](https://pic.bitday.top/i/2025/03/19/u7gbph-2.png) ![image-20240815092604848](https://pic.bitday.top/i/2025/03/19/u7f9pf-2.png) 由于`Service`中经常需要定义与业务有关的自定义方法,因此我们不能直接使用`IService`,而是自定义`Service`接口,然后继承`IService`以拓展方法。同时,让自定义的`Service实现类`继承`ServiceImpl`,这样就不用自己实现`IService`中的接口了。 首先,定义`IUserService`,继承`IService`: ``` package com.itheima.mp.service; import com.baomidou.mybatisplus.extension.service.IService; import com.itheima.mp.domain.po.User; public interface IUserService extends IService { // 拓展自定义方法 } ``` 然后,编写`UserServiceImpl`类,继承`ServiceImpl`,实现`UserService`: ``` package com.itheima.mp.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.itheima.mp.domain.po.User; import com.itheima.mp.domain.po.service.IUserService; import com.itheima.mp.mapper.UserMapper; import org.springframework.stereotype.Service; @Service public class UserServiceImpl extends ServiceImpl implements IUserService { } ```