文章目录
- SpringBoot整合MyBatis-Plus详解(二)
- MyBatis-Plus简介
- 条件构造器和常用接口⭐
- Wrapper介绍
- QueryWrapper(Mapper接口提供的)和QueryChainWrapper(Service接口提供的)
- 案例1:组装查询条件
- 案例2:组装排序条件
- 案例3:组装删除条件
- 案例4:条件的优先级
- 案例5:指定查询的字段(默认是查询全部字段)
- 案例6:实现子查询
- UpdateWrapper
- condition
- 未使用condition
- 使用condition来化简上面的代码(相当于if语句)
- LambdaQueryWrapper
- LambdaUpdateWrapper
- MyBatis Plus插件⭐
- 插件1:MyBatis Plus分页插件
- 编写MyBatis Plus插件配置类
- 测试分页
- xml自定义分页
- 在UserMapper中自定义接口方法
- UserMapper.xml中编写SQL
- 测试分页
- 插件2:MyBatis Plus乐观锁插件
- 模拟修改冲突
- 数据库中增加商品表和插入数据
- 新建实体类
- 新建mapper接口
- 测试
- Mybatis Plus实现乐观锁
- 给乐观锁标记属性加上@Version注解
- 在配置类中配置乐观锁插件
- 测试修改冲突
- MyBatis Plus代码生成器⭐
- 引入maven依赖
- 编写代码快速生成方法(生成t_user表的代码)
- MyBatisX插件⭐
- 在IDEA上安装MyBatisX插件
- MyBatisX插件功能1:Mapper和XML之间跳转
- MyBatisX插件功能2:代码生成
SpringBoot整合MyBatis-Plus详解(二)
项目所在仓库
MyBatis-Plus简介
简介
- MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为
简化开发、提高效率而生
MyBatis-Plus特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
MyBatis-Plus支持的数据库
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
MyBatis-Plus的架构设计
条件构造器和常用接口⭐
Wrapper介绍
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- eq相等 、 ne不相等、 gt大于、 lt小于 、 ge大于等于 、 le 小于等于
QueryWrapper(Mapper接口提供的)和QueryChainWrapper(Service接口提供的)
案例1:组装查询条件
@Test
void test01(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
//方式1:QueryWrapper
// QueryWrapper<User> queryWrapper=new QueryWrapper<>();
//
// queryWrapper.like("username","a")
// .between("age",20,30)
// .isNotNull("email");
//
//
// List<User> users = userMapper.selectList(queryWrapper);
//
// users.forEach(System.out::println);
//方式2:QueryChainWrapper
QueryChainWrapper<User> chainWrapper = userService.query()
.like("username", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> users = chainWrapper.list();
users.forEach(System.out::println);
}
案例2:组装排序条件
@Test
void test02(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
// SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,uid ASC
//方式1:QueryWrapper
// QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.orderByDesc("age")
// .orderByAsc("uid");
//
// List<User> users = userMapper.selectList(queryWrapper);
//
// users.forEach(System.out::println);
//方式2:QueryChainWrapper
QueryChainWrapper<User> userQueryChainWrapper = userService.query()
.orderByDesc("age")
.orderByAsc("uid");
List<User> userList = userQueryChainWrapper.list();
userList.forEach(System.out::println);
}
案例3:组装删除条件
@Test
void test03(){
//删除email为空的用户
//DELETE FROM t_user WHERE (email IS NULL)
//QueryWrapper
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
userMapper.delete(queryWrapper);
}
案例4:条件的优先级
@Test
void test04(){
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
//UPDATE t_user SET age=?, email=? WHERE (username LIKE ? AND (age > ? OR email IS NULL))
//lambda表达式内的逻辑优先运算
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like("username","a")
.and(i -> i.gt("age", 20).or().isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("666333@qq.com");
userMapper.update(user,queryWrapper);
}
案例5:指定查询的字段(默认是查询全部字段)
@Test
void test05(){
//查询用户信息的username和age字段
//SELECT username,age FROM t_user WHERE is_deleted=0
//方式1:QueryWrapper
// QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//
// queryWrapper
// .select("username","age"); //指定查询的字段(默认是查找全部字段)
//
// List<User> users = userMapper.selectList(queryWrapper);
//
// users.forEach(System.out::println);
//方式2:QueryChainWrapper
List<User> list = userService
.query()
.select("username", "age") //指定查询的字段(默认是查找全部字段)
.list(); //list方法直接拿到结果集
list.forEach(System.out::println);
}
案例6:实现子查询
@Test
void test06(){
//查询id小于等于3的用户信息
//SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 3))
//方式1:QueryWrapper
// QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// queryWrapper.inSql("uid","select uid from t_user where uid <= 3");
//
// List<User> users = userMapper.selectList(queryWrapper);
//
// users.forEach(System.out::println);
//方式2:QueryChainWrapper
List<User> userList = userService
.query()
.inSql("uid", "select uid from t_user where uid <= 3")
.list();
userList.forEach(System.out::println);
}
UpdateWrapper
@Test
void test07()
{
//好处:当我们使用了UpdateWrapper时,不需要传入entity了,可以通过set方法修改值
//UPDATE t_user SET username=?,age=? WHERE is_deleted=0 AND (uid = ?)
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper
.set("username","新名字111")
.set("age",32)
.eq("uid",5L);
userMapper.update(null,updateWrapper);
}
condition
未使用condition
@Test
void test08()
{
String username=null;
Integer smallAge=15;
Integer bigAge=30;
//SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(username)) { //如果用户名不为空,则添加下面的条件
queryWrapper
.like("username",username);
}
if(smallAge != null){
queryWrapper
.ge("age",smallAge);
}
if(bigAge != null){
queryWrapper
.le("age",bigAge);
}
userMapper.selectList(queryWrapper);
}
使用condition来化简上面的代码(相当于if语句)
@Test
void test09()
{
String username=null;
Integer smallAge=15;
Integer bigAge=30;
//SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like(StringUtils.isNotBlank(username),"username",username)
.ge(smallAge!=null,"age",smallAge)
.le(bigAge!=null,"age",bigAge);
userMapper.selectList(queryWrapper);
}
LambdaQueryWrapper
- 好处就是:使用lambda表达式来代替表的字段,防止我们在开发中写表的字段错误。
@Test
void test10(){
String username=null;
Integer smallAge=15;
Integer bigAge=30;
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(username),User::getName,username);
lambdaQueryWrapper.ge(smallAge!=null,User::getAge,smallAge);
lambdaQueryWrapper.le(bigAge!=null,User::getAge,bigAge);
userMapper.selectList(lambdaQueryWrapper);
}
LambdaUpdateWrapper
- 好处就是:使用lambda表达式来代替表的字段,防止我们在开发中写表的字段错误。
@Test
void test11(){
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper
.set(User::getName,"新名字222")
.set(User::getAge,55)
.eq(User::getUid,5L);
userMapper.update(null,lambdaUpdateWrapper);
}
MyBatis Plus插件⭐
插件1:MyBatis Plus分页插件
编写MyBatis Plus插件配置类
package com.boot.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author youzhengjie 2022-08-17 20:44:12
*/
//Mybatis-Plus配置类
@Configuration
public class MybatisPlusConfiguration {
//mybatis-plus插件都要添加到这个mybatis-plus拦截器上面去。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//配置分页插件PaginationInnerInterceptor,指定数据库类型为MYSQL
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}
测试分页
@Test
void testPage01(){
//例如:total(总的数据记录数)=10,size(每一页的记录数大小)=5
//那么就可以分成(10/5=2)页,分别是第1页和第2页,current就是当前页,因为只有2页,所以current只能写1或者2.
//SELECT uid,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 LIMIT ?,?
Page<User> userPage = new Page<>(2,5);
//第一种写法:
// userMapper.selectPage(userPage,null);
//第二种写法:
userService.page(userPage,null);
//获取分页数据
List<User> list = userPage.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+userPage.getCurrent());
System.out.println("每页显示的条数:"+userPage.getSize());
System.out.println("总记录数:"+userPage.getTotal());
System.out.println("总页数:"+userPage.getPages());
System.out.println("是否有上一页:"+userPage.hasPrevious());
System.out.println("是否有下一页:"+userPage.hasNext());
}
xml自定义分页
在UserMapper中自定义接口方法
package com.boot.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
/**
* @author youzhengjie 2022-08-15 18:42:11
*/
@Mapper
@Repository
// BaseMapper的泛型就是我们需要操作的实体类User
public interface UserMapper extends BaseMapper<User> {
/**
* 根据年龄查询用户列表,分页显示
* @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页(注意page必须放在第一位)
* @param age 年龄
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
}
UserMapper.xml中编写SQL
<?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.boot.dao.UserMapper">
<select id="selectPageVo" resultType="com.boot.entity.User">
select * from t_user where age=#{age}
</select>
</mapper>
测试分页
@Test
void testPage02(){
Page<User> userPage = new Page<>(1,5);
//调用刚刚我们自定义的方法
//select * from t_user where age=? LIMIT ?
userMapper.selectPageVo(userPage,20);
//获取分页数据
List<User> list = userPage.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+userPage.getCurrent());
System.out.println("每页显示的条数:"+userPage.getSize());
System.out.println("总记录数:"+userPage.getTotal());
System.out.println("总页数:"+userPage.getPages());
System.out.println("是否有上一页:"+userPage.hasPrevious());
System.out.println("是否有下一页:"+userPage.hasNext());
}
插件2:MyBatis Plus乐观锁插件
模拟修改冲突
数据库中增加商品表和插入数据
CREATE TABLE t_product
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
number INT(11) DEFAULT 1 COMMENT '商品数量',
version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO t_product (id, name, number) VALUES (1, 'iphone13 pro', 0);
新建实体类
package com.boot.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @author youzhengjie 2022-08-17 21:56:17
*/
@TableName("t_product")
//lombok注解
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class Product implements Serializable {
@TableId("id")
private Long id;
@TableField("name")
private String name;
@TableField("number")
private int number;
private int version;
}
新建mapper接口
package com.boot.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.boot.entity.Product;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
* @author youzhengjie 2022-08-17 22:12:21
*/
@Mapper
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
测试
- 测试前先查看商品库存:
- 开始执行测试代码:
@Test
void test03(){
//A用户获取到的
Product p1 = productMapper.selectById(1L);
//B用户获取到的
Product p2 = productMapper.selectById(1L);
//A用户修改库存
p1.setNumber(p1.getNumber()+20);
productMapper.update(p1,null);
//B用户修改库存
p2.setNumber(p2.getNumber()+30);
productMapper.update(p2,null);
}
- 查看输出日志:
- 再次查看数据库:
Mybatis Plus实现乐观锁
给乐观锁标记属性加上@Version注解
在配置类中配置乐观锁插件
package com.boot.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author youzhengjie 2022-08-17 20:44:12
*/
//Mybatis-Plus配置类
@Configuration
public class MybatisPlusConfiguration {
//mybatis-plus插件都要添加到这个mybatis-plus拦截器上面去。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//配置分页插件PaginationInnerInterceptor,指定数据库类型为MYSQL
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//配置乐观锁插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
测试修改冲突
@Test
void test03(){
//A用户获取到的
Product p1 = productMapper.selectById(1L);
//B用户获取到的
Product p2 = productMapper.selectById(1L);
//A用户修改库存
p1.setNumber(p1.getNumber()+20);
productMapper.update(p1,null);
//B用户修改库存
p2.setNumber(p2.getNumber()+30);
productMapper.update(p2,null);
}
- 查看输出日志:
- 查看数据库:
MyBatis Plus代码生成器⭐
引入maven依赖
<!-- mybatis-plus代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<!-- freemarker依赖 (mybatis-plus指定freemarker引擎时一定要引入这个依赖,不然会报错) -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
编写代码快速生成方法(生成t_user表的代码)
- 查看我们的t_user表:
- 开始执行代码生成程序(通过t_user表生成代码):
package com.boot.generate;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
/**
* @author youzhengjie 2022-08-20 00:28:34
* mybatis-plus代码生成器程序
*/
public class FastGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator
//mysql的数据源配置
.create("jdbc:mysql://localhost:3306/mybatis_plus-db?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false", "root", "18420163207")
.globalConfig(builder -> {
builder.author("youzhengjie") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://mybatis-plus-generator"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.boot") // 设置父包名
.moduleName("gen") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatis-plus-generator")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder
//设置需要生成的表名(该表必须要在我们指定的mysql中存在,mybatis-plus才会根据这个表来生成代码)
.addInclude("t_user")
// 设置过滤表前缀
.addTablePrefix("t_", "c_");
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute(); //执行
}
}
- 输出日志:
- 查看刚刚生成的代码结构:
MyBatisX插件⭐
在IDEA上安装MyBatisX插件
MyBatisX插件功能1:Mapper和XML之间跳转
- 随便进入一个Mapper接口(然后点击跳转):
- 点击XML文件的图标也可以实现跳转。
MyBatisX插件功能2:代码生成
- 1:随便进入Mapper接口,输出你想生成的方法:
- 2:查看生成的方法和代码: