md_files/自学/微服务.md

407 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 微服务
## 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元的人的idusername,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 {
}
```