2025-03-18 12:46:59 +08:00
|
|
|
|
# 微服务
|
|
|
|
|
|
|
|
|
|
## 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`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
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 | 指定主键类型 |
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@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`就是条件构造的抽象类,其下有很多默认实现,继承关系如图:
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
**QueryWrapper**
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/**查询出名字中带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);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
//更新用户名为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)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@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
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@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创建条件,传递参数
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
@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的前半部分,后面拼接。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
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:根据指定条件查询单条记录。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
|
|
|
|
queryWrapper.eq("username", "alice");
|
|
|
|
|
User user = userMapper.selectOne(queryWrapper);
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
selectList:根据指定条件查询多条记录。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
|
|
|
|
queryWrapper.ge("age", 18);
|
|
|
|
|
List<User> 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<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:根据指定条件删除记录。
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
|
|
|
|
queryWrapper.eq("username", "alice");
|
|
|
|
|
|
|
|
|
|
int rows = userMapper.delete(queryWrapper);
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### IService
|
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
2025-03-19 18:31:37 +08:00
|
|
|
|

|
2025-03-18 12:46:59 +08:00
|
|
|
|
|
|
|
|
|
由于`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<User> {
|
|
|
|
|
// 拓展自定义方法
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
然后,编写`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<UserMapper, User>
|
|
|
|
|
implements IUserService {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|