有兄弟可能就会问了,前文刚整理了一个Mybatis
,现在又出来个Mybatis-plus
是什么鬼啊,害,难言之隐,我很烦,当我们整合完mybatis的时候,去做SoringBoot项目你会发现,几乎没人用mybatis
!大家都是用的mybatis-plus
!那能怎么办吧,继续学呗,咱搞计算机的,不就是不停的学嘛,那接下来我们看看什么是mybatis-plus
吧。(悄悄咪咪,我昨天在知乎上面看到了一篇文章,问题是mybatis是不是凉凉了,回答竟然全都是凉凉心痛啊,明白!经典白学呗)
声明
文章结合了官方文档,以及部分实例思路采用了尚硅谷教学视频:Mybatis-plus
文章目录
什么是Mybatis-plus
MyBatis-Plus (简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
以上是摘自Mybatis-plus官网的一段话,我们直接挑我们关注的重点,它的作用是增强,如何增强?大家看过上文,都应该有一个感触mapper.xml
文件还是太难写了!那么,mybatis-plus
它来了!,他能帮我们轻松解决这些问题~
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 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
配置application.yaml
application.yaml
不用变化,这里我贴一下上文的配置
spring:
# 数据库链接配置
datasource:
url: jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: 用户名
password: 数据库密码
# 链接池
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 链接池初始化大小
initial-size: 5
# 最大活跃数
max-active: 20
# 最小空闲数
min-idle: 5
# 最大等待时间
max-wait: 60000
# mybatis配置
mybatis:
check-config-location: true
# mybatis框架配置文件,对mybatis的生命周期起作用
# config-location: "classpath:mybatis-config.xml"
# mapper映射文件路径
mapper-locations: "classpath:/mappers/*.xml"
# 指定Java对象别名所在的包为所有entity
type-aliases-package: "com.pan.entity.*"
实例测试
创建实体类Student
@Data
public class Student {
private Long id; // 注意我们这里的id类型为Long,因为mybatis-plus的id生成是依据雪花算法,id值较长。
private String name;
private Integer age;
private String hobby;
}
创建数据库相应表
相应的创建数据表的时候id的类型要选择bigint
。
在Mapper包下面添加StudentMapper接口
@Repository
public interface StudentMapper extends BaseMapper<Student> {
}
细心的同学可能已经注意到在这个接口里面,我们继承了一个BaseMapper<Student>
,BaseMapper
是什么?点进去
可以看到,BaseMapper
里面有很多方法,插入、根据id删除、根据一个map条件删除…
所以BaseMapper
其默认提供了一系列的增删改查的基础方法。这样我们就不用手敲mapper.xml
啦~
在测试类下面测试
@SpringBootTest
class SpringBoot01ApplicationTests {
@Autowired
private StudentMapper studentMapper;
@Test
public void testSelectList(){
//selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
studentMapper.selectList(null).forEach(System.out::println);
}
}
**注意注意!**在此之前你在启动类上面加了@MapperScan
包扫描了嘛,是加在启动类上面!
@SpringBootApplication
@MapperScan("com.pan.mapper")
public class SpringBoot01Application {
public static void main(String[] args) {
SpringApplication.run(SpringBoot01Application.class, args);
}
}
启动测试,输出结果
输出结果:
Student(id=1, name=tom, age=18, hobby=篮球)
Student(id=2, name=jack, age=20, hobby=足球)
Student(id=3, name=lucy, age=17, hobby=跳舞)
相应的sql语句:
SELECT id,name,age,hobby FROM student
要是想在控制台看到sql语句需要在application.yaml
里面配置:
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
基础CRUD
上文有提到,BaseMapper
里面包含了基本的CRUD操作,我们来简单的测试几个:
插入
@Test
public void testInsert(){
// 记得在实体类上加 @AllArgsConstructor 注解才有全参构造器
Student stu = new Student(null,"jan",15,"钢琴");
int insert = studentMapper.insert(stu);
System.out.println("执行行数:"+insert);
System.out.println("获取该id:"+stu.getId());
}
执行结果:
执行行数:1
获取该id:1642527932894511105
执行的sql语句:INSERT INTO student ( id, name, age, hobby ) VALUES ( ?, ?, ?, ? )
删除
-
删除有多种,根据id删除
@Test public void testDeleteById(){ //通过id删除用户信息 //DELETE FROM student WHERE id=? int result = studentMapper.deleteById(1642527932894511105L); // 注意长id后面加了个L System.out.println("执行行数:"+result); }
-
根据id批量删除
@Test public void testDeleteBatchIds(){ //通过多个id批量删除 //DELETE FROM student WHERE id IN ( ? , ? , ? ) List<Long> idList = Arrays.asList(1L, 2L, 3L); int result = studentMapper.deleteBatchIds(idList); System.out.println("执行行数:"+result); }
-
根据map条件删除
@Test public void testDeleteByMap(){ //根据map集合中所设置的条件删除记录 //DELETE FROM student WHERE name = ? AND age = ? Map<String, Object> map = new HashMap<>(); map.put("age", 20); map.put("name", "jack"); int result = studentMapper.deleteByMap(map); System.out.println("执行行数:"+result); }
修改
@Test
public void testUpdateById(){
Student student = new Student(4L, "ben", 22, null);
//UPDATE student SET name=?, age=? WHERE id=?
int result = studentMapper.updateById(student);
System.out.println("受影响行数:"+result);
}
查询
-
查询也有多个,全查询
@Test public void testSelectList(){ //查询所有用户信息 //SELECT id,name,age,hobby FROM student List<User> list = studentMapper.selectList(null); list.forEach(System.out::println); }
-
根据id查询
@Test public void testSelectById(){ //根据id查询用户信息 //SELECT id,name,age,hobby FROM student WHERE id=? Student stu = studentMapper.selectById(4L); System.out.println(stu); }
-
根据多个id查询
@Test public void testSelectBatchIds(){ //根据多个id查询多个用户信息 //SELECT id,name,age,hobby FROM student WHERE id IN ( ? , ? ) List<Long> idList = Arrays.asList(1L, 2L); List<User> list = userMapper.selectBatchIds(idList); list.forEach(System.out::println); }
-
根据map条件查询
@Test public void testSelectByMap(){ //通过map条件查询用户信息 //SELECT id,name,age,hobby FROM student WHERE name = ? AND age = ? Map<String, Object> map = new HashMap<>(); map.put("age", 15); map.put("name", "jan"); List<User> list = userMapper.selectByMap(map); list.forEach(System.out::println); }
BaseMapper里面也还有不少其他方法,这里贴出完整的BaseMapper源码,下载源码就有中文注释噢~
package com.baomidou.mybatisplus.core.mapper;
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
* @param entity 实体对象
* @since 3.4.4
*/
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where
语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID 批量删除)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 根据 ID 修改
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);
/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);
/**
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
}
return ts.get(0);
}
return null;
}
/**
* 根据 Wrapper 条件,查询总记录数
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page,@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
Service层
仅仅凭借BaseMapper的基础CRUD并不够实现我们项目的复杂逻辑,所以在mybatis-plus中还有一个接口IService
以及它的实现类ServiceImpl
,里面的方法很多,同学们可以点进去自己阅读观看
创建service
包,并在下面建立接口StudentService
:
public interface StudentService extends IService<Student> {
// StudentService继承IService模板提供的基础功能
}
在service
包下面建立包serviceImpl
存放其接口实现类StudentServiceImpl
:
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
// ServiceImpl实现了IService,提供了IService中基础功能的实现
// 若ServiceImpl无法满足业务需求,则可以使用自定的StudentService定义方法,并在实现类中实现
}
测试
-
查询记录总数
@Test public void testGetCount(){ // SELECT COUNT( * ) FROM student long count = studentService.count(); System.out.println("总记录数:"+count); // 总记录数:4 }
-
批量插入
@Test public void testSaveBatch(){ // SQL长度有限制,海量数据插入单条SQL无法实行, // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper ArrayList<Student> stus = new ArrayList<>(); for (int i = 0; i < 3; i++) { Student stu = new Student(); stu.setName("abc" + i); stu.setAge(15 + i); stus.add(stu); } //SQL:INSERT INTO student ( name, age ) VALUES ( ?, ? ) studentService.saveBatch(stus); }
查询以下现有数据:
Student(id=1, name=tom, age=18, hobby=篮球) Student(id=2, name=jack, age=20, hobby=足球) Student(id=3, name=lucy, age=17, hobby=跳舞) Student(id=1642527932894511105, name=jan, age=15, hobby=钢琴) Student(id=1642539138866819074, name=abc0, age=15, hobby=null) Student(id=1642539138908762113, name=abc1, age=16, hobby=null) Student(id=1642539138908762114, name=abc2, age=17, hobby=null)
常用注解
从刚刚的测试我们会发现,我们只是在application.yaml
文件指定了数据库名称,全程没有提及表明,只是在Mapper接口继承BaseMapper的时候设置了泛型Student,而我们的操作表名字也是student。因此,不难想到,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致
@TableName
那如果实体类型的类名和操作表名不一样怎么办?答案就是我们的注解@TableName
假如我们现在数据库的表名已经改为s_student
,我们只需要在实体类上面添加@Table("s_student")
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("s_student")
public class Student {
private Long id;
private String name;
private Integer age;
private String hobby;
}
全局配置解决
在开发中,实体类所对应的表都有固定的前缀,例如 s_ 此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就 不需要在每个实体类上通过@TableName标识实体类对应的表
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: s_
@TableId
在通常情况下,我们的数据表里面主键一般都是id
,Mybatis-plus在实现CRUD操作的时候也会默认将id列作为主键列,那么我们想换一个列作为主键怎么做呢?例如将我们的名字作为主键
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Long id;
@TableId("name") // 注意,这只是一个假设,一般没人会把名字作为主键列
private String name;
private Integer age;
private String hobby;
}
@TableField
当我们的实体类中的属性名和字段名不一致的情况时,我们可以使用此注解
-
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格,无须担心
-
如果不是上种情况,而是名字就是不一样,例如实体类中
name
,数据表中studentname
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private Long id; @TableField("studentname") // 添加注解实现 private String name; private Integer age; private String hobby; }
@TableLogic
在我们的项目开发中,许多的删除并不是真的删除,只是你前端页面看不到了,如果真的删除了,那万一用户后面要求恢复数据怎么办,所以我们引出了逻辑删除is_delete
在实体类,数据表中分布添加字段is_delete
,0表示启用,1表示停用
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Long id;
private String name;
private Integer age;
private String hobby;
@TableLogic
private int is_delete;
}
条件构造器 — 重中之重
Mybatis-Plus为了让用户自由的构建查询条件, 简单便捷,没有额外的负担 , 能够有效提高开发效率,推出了条件构造器,它主要用于 处理 sql 拼接,排序,实体参数查询等。并且条件构造器支持链式结构,可以一直.
部分基本条件说明:(以下引用自官网)
表达式 | 解释 | 实例 |
---|---|---|
ne | 不等于 <> | ne(“name”, “老王”)—>name <> ‘老王’ |
gt | 大于 > | gt(“age”, 18)—>age > 18 |
eq | 等于 = | eq(“name”, “老王”)—>name = ‘老王’ |
ge | 大于等于 >= | ge(“age”, 18)—>age >= 18 |
lt | 小于 < | lt(“age”, 18)—>age < 18 |
le | 小于等于 <= | le(“age”, 18)—>age <= 18 |
between | BETWEEN 值1 AND 值2 | between(“age”, 18, 30)—>age between 18 and 3 |
notBetween | NOT BETWEEN 值1 AND 值2 | notBetween(“age”, 18, 30—>age not between 18 and 30 |
like | LIKE ‘%值%’ | like(“name”, “王”)—>name like '%王% |
notLike | NOT LIKE ‘%值%’ | notLike(“name”, “王”)—>name not like '%王% |
likeLeft | LIKE ‘%值’ | likeLeft(“name”, “王”)—>name like '%王 |
likeRight | LIKE ‘值%’ | likeRight(“name”, “王”)—>name like ‘王%’ |
notLikeLeft | NOT LIKE ‘%值’ | notLikeLeft(“name”, “王”)—>name not like '%王 |
notLikeRight | NOT LIKE ‘值%’ | notLikeRight(“name”, “王”)—>name not like ‘王%’ |
isNull | 字段 IS NULL | isNull(“name”)—>name is null |
isNotNull | 字段 IS NOT NULL | isNotNull(“name”)—>name is not null |
in | 字段 IN (value.get(0), value.get(1), …) | in(“age”,{1,2,3})—>age in (1,2,3) |
notIn | 字段 NOT IN (value.get(0), value.get(1), …) | notIn(“age”,{1,2,3}) —> age not in (1,2,3) |
orderByAsc | 排序:ORDER BY 字段, … ASC,升序 | orderByAsc(“id”, “name”) —> order by id ASC,name ASC |
orderByDesc | 排序:ORDER BY 字段, … DESC,降序 | orderByDesc(“id”, “name”) —> order by id DESC,name DESC |
or | 拼接 OR,主动调用or 表示紧接着下一个方法不是用and 连接!(不调用or 则默认为使用and 连接) | eq(“id”,1).or().eq(“name”,“老王”) —> id = 1 or name = ‘老王’ |
and | AND 嵌套 | and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”)) —> and (name = ‘李白’ and status <> ‘活着’) |
apply | 该方法可用于数据库函数 动态入参的params 对应前面applySql 内部的{index} 部分.这样是不会有sql注入风险的,反之会有! | 1. apply(“id = 1”)—>id = 1 2. apply(“date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”)—>date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’“) 3. apply(“date_format(dateColumn,‘%Y-%m-%d’) = {0}”, “2008-08-08”)—>date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”) |
QueryWrapper
-
组装查询条件
@Test public void test01(){ //查询用户名包含j,年龄在15到25之间,并且爱好不为null的学生信息 // SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0 // AND (name LIKE ? AND age BETWEEN ? AND ? AND hobby IS NOT NULL) QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper.like("name", "j") .between("age", 15, 25) .isNotNull("hobby"); List<Student> list = studentMapper.selectList(queryWrapper); list.forEach(System.out::println); }
-
组装排序条件
@Test public void test02(){ //按年龄降序查询学生,如果年龄相同则按id升序排列 // SELECT id,name,age,hobby,is_delete FROM student // WHERE is_delete=0 ORDER BY age DESC,id ASC QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper .orderByDesc("age") .orderByAsc("id"); List<Student> users = studentMapper.selectList(queryWrapper); users.forEach(System.out::println); }
-
组装删除条件
@Test public void test03(){ //删除hobby为空的学生 // UPDATE student SET is_delete=1 WHERE is_delete=0 AND (name IS NULL) QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper.isNull("hobby"); //条件构造器也可以构建删除语句的条件 int result = studentMapper.delete(queryWrapper); System.out.println("受影响的行数:" + result); }
-
条件的优先级 (lambda表达式内的逻辑优先运算)
@Test public void test04() { QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); //将(年龄大于17并且用户名中包含有j)或hobby为null的学生信息修改 // UPDATE student SET age=?, hobby=? WHERE is_delete=0 // AND (name LIKE ? AND age > ? OR hobby IS NULL) queryWrapper .like("name", "j") .gt("age", 17) .or() .isNull("hobby"); Student stu = new Student(); stu.setAge(18); stu.setHobby("滑雪"); int result = studentMapper.update(stu, queryWrapper); System.out.println("受影响的行数:" + result);
lambda表达式内的逻辑优先运算
@Test public void test05() { QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); //将用户名中包含有j并且(年龄大于17或hobby为null)的学生信息修改 // UPDATE student SET age=?, hobby=? WHERE is_delete=0 // AND (name LIKE ? AND (age > ? OR hobby IS NULL)) //lambda表达式内的逻辑优先运算 queryWrapper .like("name", "j") .and(i -> i.gt("age", 17).or().isNull("hobby")); Student stu = new Student(); stu.setAge(18); stu.setHobby("滑雪"); int result = studentMapper.update(stu, queryWrapper); System.out.println("受影响的行数:" + result); }
-
组装select子句
@Test public void test06() { //查询学生信息的name和age字段 //SELECT name,age FROM student WHERE is_delete=0 QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper.select("name", "age"); //selectMaps()返回Map集合列表,通常配合select()使用, // 避免User对象中没有被查询到的列值为null List<Map<String, Object>> maps = studentMapper.selectMaps(queryWrapper); maps.forEach(System.out::println); }
-
实现子查询
@Test public void test07() { //查询id小于等于3的学生信息 // SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0 // AND (id IN (select id from student where id <= 3)) QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); queryWrapper.inSql("id", "select id from student where id <= 3"); List<Student> list = studentMapper.selectList(queryWrapper); list.forEach(System.out::println); }
UpdateWrapper
@Test
public void test08() {
//将(年龄大于15或hobby为null)并且学生名中包含有j的学生信息修改
//组装set子句以及修改条件
//UPDATE student SET age=?,hobby=? WHERE is_delete=0
//AND (name LIKE ? AND (age > ? OR hobby IS NULL))
UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>();
//lambda表达式内的逻辑优先运算
updateWrapper
.set("age", 15)
.set("hobby", "乒乓球")
.like("name", "j")
.and(i -> i.gt("age", 15).or().isNull("hobby"));
//这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
//Student stu = new Student();
//stu.setName("mark");
//int result = userMapper.update(stu, updateWrapper);
int result = studentMapper.update(null, updateWrapper);
System.out.println(result);
}
condition
我们的组装条件数据来源于用户输入,是可选的,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果
-
方法一,普通方法
@Test public void test09() { //定义查询条件,有可能为null(用户未输入或未选择) String username = null; Integer ageBegin = 10; Integer ageEnd = 24; QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); // SELECT id,name,age,hobby,is_delete FROM student WHERE // is_delete=0 AND (age >= ? AND age <= ?) //StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成 if(StringUtils.isNotBlank(username)){ queryWrapper.like("name","j"); } if(ageBegin != null){ queryWrapper.ge("age", ageBegin); } if(ageEnd != null){ queryWrapper.le("age", ageEnd); } List<Student> users = studentMapper.selectList(queryWrapper); users.forEach(System.out::println); }
-
方法二,使用带condition参数的重载方法构建查 询条件
@Test public void test10() { //定义查询条件,有可能为null(用户未输入或未选择) String username = null; Integer ageBegin = 10; Integer ageEnd = 24; QueryWrapper<Student> queryWrapper = new QueryWrapper<>(); // SELECT id,name,age,hobby,is_delete FROM student WHERE // is_delete=0 AND (age >= ? AND age <= ?) queryWrapper .like(StringUtils.isNotBlank(username), "username", "a") .ge(ageBegin != null, "age", ageBegin) .le(ageEnd != null, "age", ageEnd); List<Student> users = studentMapper.selectList(queryWrapper); users.forEach(System.out::println); }
LambdaQueryWrapper
LambdaQueryWrapper
和QueryWrapper
类似,只不过使用的是lambda语法。
@Test
public void test11() {
//定义查询条件,有可能为null(用户未输入)
String name = "j";
Integer ageBegin = 15;
Integer ageEnd = 24;
LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
// SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0
// AND (name LIKE ? AND age >= ? AND age <= ?)
//避免使用字符串表示字段,防止运行时错误
queryWrapper
.like(StringUtils.isNotBlank(name), Student::getName, name)
.ge(ageBegin != null, Student::getAge, ageBegin)
.le(ageEnd != null, Student::getAge, ageEnd);
List<Student> users = studentMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
LambdaUpdateWrapper
同理
@Test
public void test12() {
//组装set子句
LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
// UPDATE student SET age=?,hobby=? WHERE is_delete=0
// AND (name LIKE ? AND (age < ? OR hobby IS NULL))
updateWrapper
.set(Student::getAge, 18)
.set(Student::getHobby, "芭蕾")
.like(Student::getName, "j")
.and(i -> i.lt(Student::getAge, 24).or().isNull(Student::getHobby));
//lambda表达式内的逻辑优先运算
Student stu = new Student();
int result = studentMapper.update(stu, updateWrapper);
System.out.println("受影响的行数:" + result);
}
链式调用 lambda 式
// 区分:
// 链式调用 普通
UpdateChainWrapper<T> update();
// 链式调用 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 等价示例:
query().eq("id", value).one();
lambdaQuery().eq(Entity::getId, value).one();
// 等价示例:
update().eq("id", value).remove();
lambdaUpdate().eq(Entity::getId, value).remove();
插件
分页插件
MyBatis Plus自带分页插件,但是需要配置,在config包下配置MybatisPlusConfig
@Configuration
@MapperScan("com.pan.mapper") //可以将主类中的注解移到此处,主类上的就可以删了
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
在测试之前我们加几条数据:
编写测试
@Test
public void testPage(){
// 设置分页参数
Page<Student> page = new Page<>(1, 5);
studentMapper.selectPage(page, null);
// 获取分页数据
List<Student> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
输出结果:
Student(id=1, name=tom, age=18, hobby=篮球, is_delete=0)
Student(id=2, name=jack, age=18, hobby=芭蕾, is_delete=0)
Student(id=1642810620078280705, name=abc0, age=15, hobby=游泳0, is_delete=0)
Student(id=1642810620095057921, name=abc1, age=16, hobby=游泳1, is_delete=0)
Student(id=1642810620095057922, name=abc2, age=17, hobby=游泳2, is_delete=0)
当前页:1
每页显示的条数:5
总记录数:9
总页数:2
是否有上一页:false
是否有下一页:true
乐观锁插件
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
同样,先添加配置
@Configuration
@MapperScan("com.pan.mapper") //可以将主类中的注解移到此处
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 这是分页插件
interceptor.addInnerInterceptor(new
PaginationInnerInterceptor(DbType.MYSQL));
// 这是乐观锁插件
interceptor.addInnerInterceptor(new
OptimisticLockerInnerInterceptor());
return interceptor;
}
}
在实体类的字段上加上@Version
注解,引入尚硅谷实例讲解
故事背景:
一个商品成本80,售价100.老板叫小李将价格增加50,小李暂时未改,一会,老板叫小王降价30,此时小李小王同时改价,小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。
如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元
创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
添加mapper
public interface ProductMapper extends BaseMapper<Product> {
}
乐观锁实现流程
数据库中添加version字段
取出记录时,获取当前version
SELECT id,name,price,version FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, version=version + 1 WHERE id=1 AND version=1
测试修改冲突
小李查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=?
小王查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=?
小李修改商品价格,自动将version+1 UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
小王修改商品价格,此时version已更新,条件不成立,修改失败
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最终,小王修改失败,查询价格:150 SELECT id,name,price,version FROM t_product WHERE id=?
相关代码
@Test
public void testConcurrentVersionUpdate() {
//小李取数据
Product p1 = productMapper.selectById(1L);
//小王取数据
Product p2 = productMapper.selectById(1L);
//小李修改 + 50
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改的结果:" + result1);
//小王修改 - 30
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改的结果:" + result2);
if(result2 == 0){
//失败重试,重新获取version并更新
p2 = productMapper.selectById(1L);
p2.setPrice(p2.getPrice() - 30);
result2 = productMapper.updateById(p2);
}
System.out.println("小王修改重试的结果:" + result2);
//老板看价格
Product p3 = productMapper.selectById(1L);
System.out.println("老板看价格:" + p3.getPrice());
}
Mybatis-Plus还有许多强大的功能,这里就不赘述了…怎么说能,要想用的熟练,用的巧妙还是得多看看官网,再次奉上官网地址。
rsion=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最终,小王修改失败,查询价格:150 SELECT id,name,price,version FROM t_product WHERE id=?
相关代码
@Test
public void testConcurrentVersionUpdate() {
//小李取数据
Product p1 = productMapper.selectById(1L);
//小王取数据
Product p2 = productMapper.selectById(1L);
//小李修改 + 50
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改的结果:" + result1);
//小王修改 - 30
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改的结果:" + result2);
if(result2 == 0){
//失败重试,重新获取version并更新
p2 = productMapper.selectById(1L);
p2.setPrice(p2.getPrice() - 30);
result2 = productMapper.updateById(p2);
}
System.out.println("小王修改重试的结果:" + result2);
//老板看价格
Product p3 = productMapper.selectById(1L);
System.out.println("老板看价格:" + p3.getPrice());
}
Mybatis-Plus还有许多强大的功能,这里就不赘述了…怎么说能,要想用的熟练,用的巧妙还是得多看看官网,再次奉上官网地址。