官网
MybatisPlus无侵入和方便快捷.
MybatisPlus不仅仅可以简化单表操作,而且还对Mybatis的功能有很多的增强。可以让我们的开发更加的简单,高效。
通过今天的学习,我们要达成下面的目标:
- 能利用MybatisPlus实现基本的CRUD
- 会使用条件构建造器构建查询和更新语句
1.MybatisPlus实现基本的CRUD
快速开始
初始化工程: 配置文件
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
logging:
level:
com.itheima: debug
pattern:
dateformat: HH:mm:ss
mybatis:
mapper-locations: classpath*:mapper/*.xml
实体类
@Data
public class User {
/**
* 用户id
*/
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;
}
dao层/mapper层
public interface UserMapper{
void saveUser(User user);
void deleteUser(Long id);
void updateUser(User user);
User queryUserById(@Param("id") Long id);
List<User> queryUserByIds(@Param("ids") List<Long> ids);
}
mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mp.mapper.UserMapper">
<insert id="saveUser" parameterType="com.itheima.mp.domain.po.User">
INSERT INTO `user` (`id`, `username`, `password`, `phone`, `info`, `balance`)
VALUES
(#{id}, #{username}, #{password}, #{phone}, #{info}, #{balance});
</insert>
<update id="updateUser" parameterType="com.itheima.mp.domain.po.User">
UPDATE `user`
<set>
<if test="username != null">
`username`=#{username}
</if>
<if test="password != null">
`password`=#{password}
</if>
<if test="phone != null">
`phone`=#{phone}
</if>
<if test="info != null">
`info`=#{info}
</if>
<if test="status != null">
`status`=#{status}
</if>
<if test="balance != null">
`balance`=#{balance}
</if>
</set>
WHERE `id`=#{id};
</update>
<delete id="deleteUser" parameterType="com.itheima.mp.domain.po.User">
DELETE FROM user WHERE id = #{id}
</delete>
<select id="queryUserById" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user
WHERE id = #{id}
</select>
<select id="queryUserByIds" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user
<if test="ids != null">
WHERE id IN
<foreach collection="ids" open="(" close=")" item="id" separator=",">
#{id}
</foreach>
</if>
LIMIT 10
</select>
</mapper>
现在我们来改造!用mp快速体验一下:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.mp.domain.po.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface UserMapper extends BaseMapper<User> {
}
把那些方法全部都删除了,然后把mapper.xml文件也一并删除.我们继承的这个"爸爸"里面这些方法它都有,所以我们直接去测试类测试即可.

import com.itheima.mp.domain.po.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.util.List;
@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);
}
@Test
void testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
@Test
void testQueryByIds() {
List<User> users = userMapper.selectBatchIds(List.of(1L, 2L, 3L, 4L));
users.forEach(System.out::println);
}
@Test
void testUpdateById() {
User user = new User();
user.setId(5L);
user.setBalance(20000);
userMapper.updateById(user);
}
@Test
void testDeleteUser() {
userMapper.deleteById(5L);
}
}
全部都能测试成功,而且updateById语句如果你没有修改其他的字段那么其他字段不会修改.这样实现单表增删改查全部搞定.
常见注解
MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息.
@Data
@TableName("user")
public class User {
/**
* 用户id
*/
@TableId(type = IdType.AUTO)//默认使用雪花算法生成id
private Long id;
/**
* 用户名
*/
// @TableField("`username`")
private String username;
/**
* 密码
*/
// @TableField(exist = false)
private String password;
/**
* 注册手机号
*/
private String phone;
/**
* 详细信息
*/
private String info;
/**
* 使用状态(1正常 2冻结)
*/
private Integer status;
/**
* 账户余额
*/
private Integer balance;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
常见配置
但是其实大部分都是默认配置具体可以看官网那些是默认配置
mybatis-plus:
type-aliases-package: com.itheima.mp.domain.po
global-config:
db-config:
id-type: auto
2.使用条件构建造器构建查询和更新语句
核心功能:
条件构造器
@Test
void testQueryWrapper(){
Wrapper<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);
}
debug信息:
14:40:41 DEBUG 26264 — [ main] c.i.mp.mapper.UserMapper.selectList : ==> Preparing: SELECT id,username,info,balance FROM user WHERE (username LIKE ? AND balance >= ?)
14:40:41 DEBUG 26264 — [ main] c.i.mp.mapper.UserMapper.selectList : > Parameters: %o%(String), 1000(Integer)
14:40:41 DEBUG 26264 — [ main] c.i.mp.mapper.UserMapper.selectList : < Total: 1
@Test
void testUpdateWrapper(){
//1.要更新的数据
User user = new User();
user.setBalance(2000);
//2.更新的条件
QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");
//3.执行更新
userMapper.update(user,wrapper);
}
debug日志信息:
14:45:32 DEBUG 15964 — [ main] com.itheima.mp.mapper.UserMapper.update : ==> Preparing: UPDATE user SET balance=? WHERE (username = ?)
14:45:32 DEBUG 15964 — [ main] com.itheima.mp.mapper.UserMapper.update : > Parameters: 2000(Integer), jack(String)
14:45:32 DEBUG 15964 — [ main] com.itheima.mp.mapper.UserMapper.update : < Updates: 1
@Test
void testUpdateWrapper(){
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance + 200")
.in("id", List.of(1L, 2L, 3L, 4L));
userMapper.update(null,wrapper);
}
但是这种写法是硬编码.
推荐Lambda
@Test
void testLambdaQueryWrapper(){
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.select(User::getId, User::getUsername,User::getInfo, User::getBalance)
.like(User::getUsername, "o")
.ge(User::getBalance, 1000);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
自定义SQL
我们可以利用MybatisPlus的wrapper来构建where条件,然后自己定义SQL语句中剩下部分.
这样确实方便但是这是在业务层中,这在很多企业中是不允许的.
@Test
void testCustomSql(){
List<Long>ids = List.of(1L, 2L, 3L, 4L);
int amount = 200;
QueryWrapper<User> wrapper = new QueryWrapper<User>().in("id", ids);
userMapper.updateBalanceByIds(wrapper, amount);
}
public interface UserMapper extends BaseMapper<User> {
@Update("UPDATE user set balance = balance + #{amount} ${ew.customSqlSegment}")
void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
}
Service接口
@SpringBootTest
class IUserServiceTest {
@Autowired
private IUserService userService;
@Test
void testSave(){
User user = new User();
// user.setId(5L);
user.setUsername("yyy");
user.setPassword("123");
user.setPhone("18688990011");
user.setBalance(200);
user.setInfo("{\"age\": 24, \"intro\": \"英文老师\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
userService.save(user);
}
@Test
void testQuery(){
List<User> users = userService.listByIds(List.of(1L, 2L, 3L));
users.forEach(System.out::println);
}
}
我们发现Iservice 和 baseMapper其实有很多方法都是重复的,从功能上是一致的,那我们应该如何选择?这里给出一个案例来进行讲解:
<!--swagger-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.1.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
然后配置swagger信息
knife4j:
enable: true
openapi:
title: 用户管理接口文档
description: "用户管理接口文档"
email: zhanghuyi@itcast.cn
concat: 虎哥
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- com.itheima.mp.controller
@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;
}
@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;
}
@Api(tags = "用户管理接口")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;// 注入userService 有final才注入
// public UserController(IUserService userService) {
// this.userService = userService;
// }
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO){
//1.DTO拷贝1到PO
User user = BeanUtil.copyProperties(userDTO, User.class);
//2.新增
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("/{id}")
public void DeleteUser(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id){
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("/{id}")
public UserVO queryUser(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id){
User user = userService.getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
return userVO;
}
@ApiOperation("根据id批量查询用户接口")
@PostMapping
public List<UserVO> queryUser(@RequestParam("ids") List<Long> ids){
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users, UserVO.class);
}
}
以后在大多数场景下直接用IService就可以了,因为这几个没有业务逻辑。
第五个接口是有业务的接口,要判断状态,判断余额数量等等。。
在controller中
@ApiOperation("扣减用户余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deduction(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id,
@ApiParam(name = "money", value = "扣减金额", required = true) @PathVariable("money") Integer money) {
userService.deduction(id, money);
}
service层
public interface IUserService extends IService<User> {
void deduction(Long id, Integer money);
}
serviceImpl
@Service
public class UserServiceImlp extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public void deduction(Long id, Integer money) {
//1. 根据id查询用户
User user = this.getById(id);
//2.检验用户状态
if(user==null || user.getStatus()!=1){
throw new RuntimeException("用户状态异常");
}
//3.检验余额是否充足
if(user.getBalance()<money){
throw new RuntimeException("余额不足");
}
//4.扣除余额
baseMapper.deductBalance(id,money);
}
}
mapper:
public interface UserMapper extends BaseMapper<User> {
@Update("UPDATE user set balance = balance + #{amount} ${ew.customSqlSegment}")
void updateBalanceByIds(@Param(Constants.WRAPPER) QueryWrapper<User> wrapper, @Param("amount") int amount);
@Update("UPDATE user set balance = balance - #{money} WHERE id = #{id}")
void deductBalance(@Param("id") Long id,@Param("money") Integer money);
}
import cn.hutool.core.bean.BeanUtil;
import com.itheima.mp.domain.dto.UserFormDTO;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.vo.UserVO;
import com.itheima.mp.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "用户管理接口")
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final IUserService userService;// 注入userService 有final才注入
// public UserController(IUserService userService) {
// this.userService = userService;
// }
@ApiOperation("新增用户接口")
@PostMapping
public void saveUser(@RequestBody UserFormDTO userDTO){
//1.DTO拷贝1到PO
User user = BeanUtil.copyProperties(userDTO, User.class);
//2.新增
userService.save(user);
}
@ApiOperation("删除用户接口")
@DeleteMapping("/{id}")
public void DeleteUser(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id){
userService.removeById(id);
}
@ApiOperation("根据id查询用户接口")
@GetMapping("/{id}")
public UserVO queryUser(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id){
User user = userService.getById(id);
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
return userVO;
}
@ApiOperation("根据id批量查询用户接口")
@PostMapping
public List<UserVO> queryUser(@RequestParam("ids") List<Long> ids){
List<User> users = userService.listByIds(ids);
return BeanUtil.copyToList(users, UserVO.class);
}
@ApiOperation("扣减用户余额接口")
@PutMapping("/{id}/deduction/{money}")
public void deduction(@ApiParam(name = "id", value = "用户id", required = true) @PathVariable("id") Long id,
@ApiParam(name = "money", value = "扣减金额", required = true) @PathVariable("money") Integer money) {
userService.deduction(id, money);
}
}
query:
@Data
@ApiModel(description = "用户查询条件实体")
public class UserQuery {
@ApiModelProperty("用户名关键字")
private String name;
@ApiModelProperty("用户状态:1-正常,2-冻结")
private Integer status;
@ApiModelProperty("余额最小值")
private Integer minBalance;
@ApiModelProperty("余额最大值")
private Integer maxBalance;
}
controller:
@ApiOperation("根据复杂条件查询用户接口")
@GetMapping("/list")
public List<UserVO> queryUserByCondition(UserQuery query){
List<User>users = userService.queryUsers(query.getName(),query.getStatus(),query.getMinBalance(),query.getMaxBalance())
return BeanUtil.copyToList(users, UserVO.class);
}
IUservice:
public interface IUserService extends IService<User> {
void deduction(Long id, Integer money);
List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance);
}
serviceimpl
public List<User> queryUsers(String name, Integer status, Integer minBalance, Integer maxBalance) {
return lambdaQuery()
.like(name != null, User::getUsername, name)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(name != null, User::getBalance, maxBalance)
.list();
}
@Service
public class UserServiceImlp extends ServiceImpl<UserMapper, User> implements IUserService {
@Transactional
public void deduction(Long id, Integer money) {
//1. 根据id查询用户
User user = this.getById(id);
//2.检验用户状态
if(user==null || user.getStatus()!=1){
throw new RuntimeException("用户状态异常");
}
//3.检验余额是否充足
if(user.getBalance()<money){
throw new RuntimeException("余额不足");
}
//4.扣除余额
// baseMapper.deductBalance(id,money);
int remainBalance = user.getBalance()-money;//乐观锁 compare and set
lambdaUpdate()
.set(User::getBalance,remainBalance)
.set(remainBalance==0,User::getStatus,2)
.eq(User::getId,id)
.eq(User::getBalance,user.getBalance()) //乐观锁 让并发安全性更好
.update();//不要忘记这一条
}
}
上面哪一种一条条执行,每一次提交都是一次网络请求,每次网络请求都要耗费一定的时间,而下面每次插入1000条,采取预编译方案,相当于每1000次才发一次网络请求
如何把我们的sql语句变成这样?用动态SQL,foreach,或者还是使用mp的批处理指令,但是你会发现这个sql语句不对啊,但其实这个不对只是缺少了一个参数rewriteBatchedStatements=true
参数,这个配置是mysql的配置,默认为false所以我们之前以为的批处理实际上都是一条条的插入,所以我们要改这个配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
在url后面加上这个参数即可