1.MybatisPlus简介
MybatisPlus是全自动的ORM框架,按照开闭原则,在不影响Mybatis现有功能的情况下,实现了对Mybatis的功能增强,简单来说,MybatisPlus是Mybatis的增强工具,只做增强,不作改变,简化开发,提高效率
Mybatis 和 MybatisPlus 的区别:
MyBatis:
- 所有SQL语句全部自己写
- 手动解析实体关系映射转换为MyBatis内部对象注入容器
- 不支持Lambda形式调用
MybatisPlus:
- 强大的条件构造器,满足各类使用需求
- 内置的Mapper,通用的Service,少量配置即可实现单表大部分CRUD操作
- 支持Lambda形式调用
- 提供了基本的CRUD功能,连SQL语句都不需要编写
- 自动解析实体关系映射转换为MyBatis内部对象注入容器
2.MybatisPlus快速开始
使用MybatisPlus之前先创建相关数据库及实体类:
数据库:
实体类:
@Data
public class User {
private Long id;
private String username;
private String password;
private String phone;
//详细信息
private String info;
// 使用状态(1正常 2冻结)
private Integer status;
// 账户余额
private Integer balance;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
2.1 引入依赖
引入依赖:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
由于这个starter包含对mybatis的自动装配,因此完全可以替换掉Mybatis的starter
2.2 定义Mapper
为了简化单表CRUD,MybatisPlus提供了一个基础的BaseMapper接口,其中已经实现了单表的CRUD
因此我们自定义的Mapper只要实现了这个BaseMapper,就无需自己实现单表CRUD了
创建UserMapper接口,让其继承BaseMapper:
package com.qing.mp.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qing.mp.domain.po.User;
public interface UserMapper extends BaseMapper<User> {
}
2.3 CRUD操作
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
//增加用户信息
@Test
void testInsert() {
User user = new User();
user.setId(5L);
user.setUsername("Lucy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userMapper.insert(user);
}
//根据id查询用户
@Test
void testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
//修改用户数据
@Test
void testUpdateById() {
User user = new User();
user.setId(5L);
user.setBalance(20000);
userMapper.updateById(user);
}
//删除用户
@Test
void testDelete() {
userMapper.deleteById(5L);
}
}
3.MybatisPlus常见注解
在上述的快速入门案例中,我们仅仅引入了依赖,继承了BaseMapper就能使用MybatisPlus进行CRUD了,非常简单
但是问题来了: MybatisPlus如何知道我们要查询的是哪张表?表中有哪些字段呢?
UserMapper在继承BaseMapper的时候指定了一个泛型User实体类
Mybatisplus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息
MybatisPlus就是根据实体类的信息来推断出表的信息,从而生成SQL的。默认情况下:
- MybatisPlus会把实体的类名驼峰转下划线作为表名
- MybatisPlus会把实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
- MybatisPlus会把名为id的字段作为主键
但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息
3.1 @TableName
作用:用于标记实体类对应的数据库表名
使用@TableName注解时,需要在实体类上添加该注解,并指定对应的表名
示例:
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
}
3.2 @TableId
作用:用于标记实体类属性对应的数据库主键
使用@TableId注解时,可以指定主键的属性,例如主键的类型、是否为自增、是否为全局唯一标识符等
示例:
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private Integer userAge;
}
3.3 @TableField
作用:用于标记实体类属性对应的数据库字段名
使用@TableField注解的常见场景:
- 成员变量名与数据库字段不一致
- 成员变量名以is开头,且是布尔值
- 成员变量名与数据库关键字冲突,如order
- 成员变量不是数据库字段
示例:
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
@TableField("isMarried")
private Boolean isMarried;
@TableField("`order`")
private Integer order;
@TableField(exist = false)
private String address;
}
4.MybatisPlus核心功能
在入门案例中都是以id为条件的简单CRUD,一些复杂条件的SQL语句就要用到一些更高级的功能才能实现
4.1 条件构造器
除了新增以外,修改、删除、查询的SQL语句都需要指定where条件。因此BaseMapper中提供的相关方法除了以id作为where条件以外,还支持更加复杂的where条件
Wrapper是条件构造的抽象类,其下有很多默认实现,继承关系如图:
4.1.1 QueryWrapper
无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件
示例:
1.查询出名字中带o的,存款大于等于1000元的人。代码如下:
@Test
void testQueryWrapper() {
// 1.构建查询条件 where name like "%o%" AND balance >= 1000
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.select("id", "username", "info", "balance")
.like("username", "o")
.ge("balance", 1000);
// 2.查询数据
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
2.更新用户名为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);
}
4.1.2 UpdateWrapper
基于BaseMapper中的update方法更新时只能直接赋值,对于一些复杂的需求就难以实现。
例如:更新id为1,2,4的用户的余额,扣200,对应的SQL应该是:
UPDATE user SET balance = balance - 200 WHERE id in (1, 2, 4)
SET的赋值结果是基于字段现有值的,这个时候就要利用UpdateWrapper中的setSql功能了:
@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);
}
4.1.3 LambdaQueryWrapper
无论是QueryWrapper还是UpdateWrapper在构造条件的时候都需要写死字段名称,这在编程规范中是不推荐的
因此MybatisPlus又提供了一套基于Lambda的Wrapper,包含两个:LambdaQueryWrapper和LambdaUpdateWrapper
针对4.1.1中的例1可做如下调整:
@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);
}
4.2 自定义SQL
在上述代码中:
这种写法在某些企业是不允许的,因为SQL语句最好都维护在持久层,而不是业务层。就当前案例来说,由于条件是in语句,只能将SQL写在Mapper.xml文件,利用foreach来生成动态SQL, 比较麻烦,假如查询条件更复杂,动态SQL的编写也会更加复杂
MybatisPlus提供了自定义SQL功能,可以让我们利用Wrapper生成查询条件,再结合mapper.xml编写SQL
对于这个案例,我们可以利用MybatisPlus的Wrapper来构建复杂的where语句,然后自定义sql语句中的剩下部分:
1.基于wrapper构建where条件
@Test
void testCustomWrapper() {
// 1.准备自定义查询条件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>().in(User::getId, ids);
// 2.调用mapper的自定义方法,直接传递Wrapper
userMapper.deductBalanceByIds(money, wrapper);
}
2.在mapper方法参数中用Param注解声明wrapper变量名称,必须是ew
public interface UserMapper extends BaseMapper<User> {
void deductBalanceByIds(@Param("money") int money, @Param("ew") QueryWrapper<User> wrapper);
}
3.自定义SQL,并使用wrapper条件
-
直接在方法上面使用注解编写
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); } -
在UserXml.xml中编写
<update id="updateBalanceByIds"> UPDATE tb_user SET balance = balance - #{money} ${ew.customSqlSegment} </update>
4.3 IService接口
MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService,默认实现为ServiceImpl,其中封装的方法可以分为以下几类:
save:新增remove:删除update:更新get:查询单个结果list:查询集合结果count:计数page:分页查询
继承关系如下:
4.3.1 IService接口的基本用法
首先,定义IUserService,继承IService:
public interface IUserService extends IService<User> {
// 拓展自定义方法
}
然后,编写UserServiceImpl类,继承ServiceImpl,实现UserService:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
4.3.2 IService接口实例应用
接下来,我们快速实现下面的实例接口:
| 编号 | 接口 | 请求方式 | 请求路径 | 请求参数 | 返回值 |
|---|---|---|---|---|---|
| 1 | 新增用户 | POST | /users | 用户表单实体 | 无 |
| 2 | 删除用户 | DELETE | /users/{id} | 用户id | 无 |
| 3 | 根据id查询用户 | GET | /users/{id} | 用户id | 用户VO |
| 4 | 根据id批量查询 | GET | /users | 用户id集合 | 用户VO集合 |
| 5 | 根据id扣减余额 | PUT | /users/{id}/deduction/{money} | 用户id、扣减金额 | 无 |
首先,接口需要两个实体:
- UserFormDTO:代表新增时的用户表单
- UserVO:代表查询的返回结果
UserFormDTO:
@Data
@ApiModel(description = "用户表单实体")
public class UserFormDTO {
@ApiModelProperty("id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("密码")
private String password;
@ApiModelProperty("注册手机号")
private String phone;
@ApiModelProperty("详细信息,JSON风格")
private String info;
@ApiModelProperty("账户余额")
private Integer balance;
}
UserVO:
@Data
@ApiModel(description = "用户VO实体")
public class UserVO {
@ApiModelProperty("用户id")
private Long id;
@ApiModelProperty("用户名")
private String username;
@ApiModelProperty("详细信息")
private String info;
@ApiModelProperty("使用状态(1正常 2冻结)")
private Integer status;
@ApiModelProperty("账户余额")
private Integer balance;
}
然后,按照Restful风格编写Controller接口方法:
@Api(tags = "用户管理接口")
@RequiredArgsConstructor
@RestController
@RequestMapping("users")
public class UserController {
private final IUserService userService;
@PostMapping
@ApiOperation("新增用户")
public void saveUser(@RequestBody UserFormDTO userFormDTO){
// 1.转换DTO为PO
User user = BeanUtil.copyProperties(userFormDTO, User.class);
// 2.新增
userService.save(user);
}
@DeleteMapping("/{id}")
@ApiOperation("删除用户")
public void removeUserById(@PathVariable("id") Long userId){
userService.removeById(userId);
}
@GetMapping("/{id}")
@ApiOperation("根据id查询用户")
public UserVO queryUserById(@PathVariable("id") Long userId){
// 1.查询用户
User user = userService.getById(userId);
// 2.处理vo
return BeanUtil.copyProperties(user, UserVO.class);
}
@GetMapping
@ApiOperation("根据id集合查询用户")
public List<UserVO> queryUserByIds(@RequestParam("ids") List<Long> ids){
// 1.查询用户
List<User> users = userService.listByIds(ids);
// 2.处理vo
return BeanUtil.copyToList(users, UserVO.class);
}
}
可以看到上述四个接口都直接在controller即可实现,无需编写任何service代码,非常方便
不过,一些带有业务逻辑的接口则需要在service中自定义实现了。例如第五个接口:根据id扣减用户余额
首先在UserController中定义一个方法:
@PutMapping("{id}/deduction/{money}")
@ApiOperation("扣减用户余额")
public void deductBalance(@PathVariable("id") Long id, @PathVariable("money")Integer money){
userService.deductBalance(id, money);
}
然后是UserService接口:
public interface IUserService extends IService<User> {
void deductBalance(Long id, Integer money);
}
最后是UserServiceImpl实现类:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deductBalance(Long id, Integer money) {
// 1.查询用户
User user = getById(id);
// 2.判断用户状态
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("用户状态异常");
}
// 3.判断用户余额
if (user.getBalance() < money) {
throw new RuntimeException("用户余额不足");
}
// 4.扣减余额
baseMapper.deductMoneyById(id, money);
}
}
最后是mapper:
@Update("UPDATE user SET balance = balance - #{money} WHERE id = #{id}")
void deductMoneyById(@Param("id") Long id, @Param("money") Integer money);