# 微服务

## Mybatis-Plus

### 快速开始

**1.引入依赖**

```XML
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
</dependency>
```

由于这个starter包含对mybatis的自动装配，因此完全可以替换掉Mybatis的starter。

**2.定义mapper**

修改mp-demo中的`com.itheima.mp.mapper`包下的`UserMapper`接口，让其继承`BaseMapper`：

```text
public interface UserMapper extends BaseMapper<User> {
}
```



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 | 指定主键类型 |

```text
@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，表示是数据库字段，若

```text
@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**

```text
/**查询出名字中带o的，存款大于等于1000元的人的id，username,info,balance
 * SELECT id,username,info,balance
 * FROM user
 * WHERE username LIKE ? AND balance >=?
 */
@Test
void testQueryWrapper(){
    QueryWrapper<User> wrapper =new QueryWrapper<User>()
            .select("id","username","info","balance")
            .like("username","o")
            .ge("balance",1000);
    //查询
    List<User> users=userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
```



```text
//更新用户名为jack的用户的余额为2000
@Test
void testUpdateByQueryWrapper() {
    // 1.构建查询条件 where name = "Jack"
    QueryWrapper<User> wrapper = new QueryWrapper<User>().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)
```

```text
@Test
void testUpdateWrapper() {
    List<Long> ids = List.of(1L, 2L, 4L);
    // 1.生成SQL
    UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
            .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

```text
@Test
void testLambdaQueryWrapper() {
    // 1.构建条件 WHERE username LIKE "%o%" AND balance >= 1000
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.lambda()
            .select(User::getId, User::getUsername, User::getInfo, User::getBalance)
            .like(User::getUsername, "o")
            .ge(User::getBalance, 1000);
    // 2.查询
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
```



**总之，推荐使用LambdaQueryWrapper，若要使用set，才用LambdaUpdateWrapper。普通的QueryWrapper用得少**



#### 自定义sql

可以让我们利用Wrapper生成查询条件，再结合Mapper.xml编写SQL

1.先在业务层利用wrapper创建条件，传递参数

```text
@Test
void testCustomWrapper() {
    // 1.准备自定义查询条件
    List<Long> ids = List.of(1L, 2L, 4L);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);

    // 2.调用mapper的自定义方法，直接传递Wrapper
    userMapper.deductBalanceByIds(200, wrapper);
}
```

2. 自定义mapper层把wrapper和其他业务参数传进去，自定义sql语句书写sql的前半部分，后面拼接。

```text
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<User> {
    @Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
    void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}
```

这里wrapper前面必须写@Param("ew")

${ew.customSqlSegment}可以自动拼接前面写的条件语句



#### Mapper层常用方法

**查询：**

selectById：根据主键 ID 查询单条记录。

selectBatchIds：根据主键 ID 批量查询记录。

selectOne：根据指定条件查询单条记录。

```text
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", "alice");
User user = userMapper.selectOne(queryWrapper);

```

selectList：根据指定条件查询多条记录。

```text
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", 18);
List<User> users = userMapper.selectList(queryWrapper);

```



**插入：**

insert：插入一条记录。

```text
User user = new User();
user.setUsername("alice");
user.setAge(20);
int rows = userMapper.insert(user);

```

**更新**

updateById：根据主键 ID 更新记录。

```text
User user = new User();
user.setId(1L);
user.setAge(25);
int rows = userMapper.updateById(user);

```

update：根据指定条件更新记录。

```text
UpdateWrapper<User> 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：根据指定条件删除记录。

```text
QueryWrapper<User> 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`：

```text
package com.itheima.mp.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;

public interface IUserService extends IService<User> {
    // 拓展自定义方法
}
```

然后，编写`UserServiceImpl`类，继承`ServiceImpl`，实现`UserService`：

```text
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<UserMapper, User>
                                                                                                        implements IUserService {
}
```

