MyBatisPlus学习笔记(SpringBoot版)
- 一、MyBatis-Plus简介
- 1、简介
- 2、特性
- 3、支持数据库
- 4、框架结构
- 5、代码及文档地址
- 二、入门案例
- 1、开发环境
- 2、创建数据库及表
- 2.1 创建表
- 2.2 添加数据
- 3、创建Spring Boot工程
- 3.1 初始化工程
- 3.2 引入依赖
- 3.3 idea中安装lombok插件
- 4、编写代码
- 4.1 配置application.yml
- 4.2 启动类
- 4.3 添加实体
- 4.4 添加UserMapper
- 4.5 测试
- 4.6 查看日志
- 三、基本CRUD
- 1、BaseMapper
- 2、插入
- 3、删除
- 3.1 通过id删除记录
- 3.2 通过id批量删除记录
- 3.3 通过map条件删除记录
- 4、修改
- 5、查询
- 5.1 根据id查询用户信息
- 5.2 根据多个id查询多个用户信息
- 5.3 通过map条件查询用户信息
- 5.4 查询所有数据
- 5.5 自定义mapper查询
- 6、通用Service
- 6.1 IService
- 6.1.1 IService源码:
- 6.1.2 ServiceImpl源码:
- 6.2 创建Service接口和实现类
- 6.3 测试查询记录数
- 6.4 测试批量插入
- 四、常用注解
- 1、@TableName
- 2、@TableId
- 3、@TableField
- 4、@TableLogic
- 4.1 逻辑删除
- 4.2 实现逻辑删除
- 4.3 实体类中添加逻辑删除属性
- 4.4 测试
- 五、条件构造器和常用接口
- 1、wapper介绍
- 2、QueryWrapper(查询和删除)
- 2.1、组装查询条件
- 2.2、组装排序条件
- 2.3、组装删除条件
- 2.4、条件的优先级
- 2.5、组装select子句
- 2.6、实现子查询
- 3、UpdateWrapper(修改)
- 4、condition
- 5、LambdaQueryWrapper
- 6、LambdaUpdateWrapper
- 六、插件
- 1、分页插件
- 1.1、添加配置类
- 1.2、测试
- 2、xml自定义分页
- 2.1、UserMapper中定义接口方法
- 2.2、UserMapper.xml中编写SQL
- 2.3、测试
一、MyBatis-Plus简介
1、简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
2、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
3、支持数据库
任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss
,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift
达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库
4、框架结构
5、代码及文档地址
官方地址: https://www.baomidou.com/
代码发布地址:
Github: https://github.com/baomidou/mybatis-plus
Gitee: https://gitee.com/baomidou/mybatis-plus
文档发布地址: https://www.baomidou.com/pages/24112f/
二、入门案例
1、开发环境
IDE:idea 2020.3
JDK:JDK8+
构建工具:maven 3.6.3
MySQL版本:MySQL 8.0.28
Spring Boot:2.6.6
MyBatis-Plus:3.2.0
2、创建数据库及表
2.1 创建表
CREATE DATABASE `study_mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `study_mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2 添加数据
INSERT INTO user (id, name, age, email) VALUES
(001, 'Mp01', 21, 'Mp01@baomidou.com'),
(002, 'Mp02', 20, 'Mp02@baomidou.com'),
(003, 'Mp03', 25, 'Mp03@baomidou.com'),
(004, 'Mp04', 21, 'Mp04@baomidou.com'),
(005, 'Mp05', 24, 'Mp05@baomidou.com');
3、创建Spring Boot工程
3.1 初始化工程
使用 Spring Initializr 快速初始化一个 Spring Boot 工程
3.2 引入依赖
完整pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tigerhhzz</groupId>
<artifactId>springboot-mybatisplus-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatisplus-demo</name>
<description>Demo project for Spring Boot MP</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 包含spirng Mvc ,tomcat的包包含requestMapping restController 等注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--mysql数据库 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!-- mybatis版本必须与druid版本兼容,否则无法创建DataSource -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<!-- 引入freemarker模板引擎供mp生成代码-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mp代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.0</version>
</dependency>
<!-- hutool工具类-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.3</version>
</dependency>
<!-- lombok注解-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 日志打印-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<!-- 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.3 idea中安装lombok插件
4、编写代码
4.1 配置application.yml
server:
port: 8082
servlet:
context-path: /
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
# test-mybatis
url: jdbc:mysql://127.0.0.1:3306/study_mybatis_plus?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
serialization:
write-dates-as-timestamps: false
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*Mapper.xml
4.2 启动类
package com.tigerhhzz.springbootmybatisplusdemo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author tigerhhzz
* @date 2023/4/28 9:16
*/
@Slf4j
@SpringBootApplication
public class SpringbootMybatisplusDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisplusDemoApplication.class, args);
log.info("SpringbootMybatisplusDemoApplication启动成功~~~~^…^~~~~^…^~~~~~^…^~~~~");
}
}
4.3 添加实体
package com.tigerhhzz.springbootmybatisplusdemo.domain;
import lombok.Data;
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
User类编译之后的结果:
4.4 添加UserMapper
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的 实体类型
package com.tigerhhzz.springbootmybatisplusdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
/**
* @author tigerhhzz
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
}
4.5 测试
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确 的执行。
为了避免报错,可以在mapper接口上添加 @Repository 注解
@Autowired
UserMapper userMapper;
@Test
public void testSelectList(){
//selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
4.6 查看日志
三、基本CRUD
1、BaseMapper
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如 下:
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.baomidou.mybatisplus.core.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/*
:`
.:,
:::,,.
:: `::::::
::` `,:,` .:`
`:: `::::::::.:` `:';,`
::::, .:::` `@++++++++:
`` :::` @+++++++++++#
:::, #++++++++++++++`
,: `::::::;'##++++++++++
.@#@;` ::::::::::::::::::::;
#@####@, :::::::::::::::+#;::.
@@######+@:::::::::::::. #@:;
, @@########':::::::::::: .#''':`
;##@@@+:##########@::::::::::: @#;.,:.
#@@@######++++#####'::::::::: .##+,:#`
@@@@@#####+++++'#####+::::::::` ,`::@#:`
`@@@@#####++++++'#####+#':::::::::::@.
@@@@######+++++''#######+##';::::;':,`
@@@@#####+++++'''#######++++++++++`
#@@#####++++++''########++++++++'
`#@######+++++''+########+++++++;
`@@#####+++++''##########++++++,
@@######+++++'##########+++++#`
@@@@#####+++++############++++;
;#@@@@@####++++##############+++,
@@@@@@@@@@@###@###############++'
@#@@@@@@@@@@@@###################+:
`@#@@@@@@@@@@@@@@###################'`
:@#@@@@@@@@@@@@@@@@@##################,
,@@@@@@@@@@@@@@@@@@@@################;
,#@@@@@@@@@@@@@@@@@@@##############+`
.#@@@@@@@@@@@@@@@@@@#############@,
@@@@@@@@@@@@@@@@@@@###########@,
:#@@@@@@@@@@@@@@@@##########@,
`##@@@@@@@@@@@@@@@########+,
`+@@@@@@@@@@@@@@@#####@:`
`:@@@@@@@@@@@@@@##@;.
`,'@@@@##@@@+;,`
``...``
_ _ /_ _ _/_. ____ / _
/ / //_//_//_|/ /_\ /_///_/_\ Talk is cheap. Show me the code.
_/ /
*/
/**
* Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
* <p>这个 Mapper 支持 id 泛型</p>
*
* @author hubin
* @since 2016-01-23
*/
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
*
* @param wrapper 实体对象封装操作类(可以为 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
/**
* 删除(根据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 条件,查询一条记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Integer 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)
*/
<E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
2、插入
@Test
public void testInsert(){
User user = new User();
user.setName("Mp06");
user.setAge(23);
user.setEmail("Mp06@baomidou.com");
int result = userMapper.insert(user);
System.out.println("受影响行数:"+result);
System.out.println("id自动获取:"+user.getId());
}
最终执行的结果,所获取的id为1475754982694199298
这是因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
查看数据库:
3、删除
3.1 通过id删除记录
@Test
public void testDeleteById(){
//通过id删除用户信息
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1654046614353891329L);
System.out.println("受影响行数:"+result);
}
3.2 通过id批量删除记录
@Test
public void testDeleteBatchIds(){
//通过多个id批量删除
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> idList = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(idList);
System.out.println("受影响行数:"+result);
}
3.3 通过map条件删除记录
@Test
public void testDeleteByMap(){
//根据map集合中所设置的条件删除记录
//DELETE FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 23);
map.put("name", "张三");
int result = userMapper.deleteByMap(map);
System.out.println("受影响行数:"+result);
}
4、修改
@Test
public void testUpdateById(){
User user = new User();
user.setId(2L);
user.setAge(32);
//UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println("受影响行数:"+result);
}
5、查询
5.1 根据id查询用户信息
@Test
public void testSelectById(){
//根据id查询用户信息
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
5.2 根据多个id查询多个用户信息
@Test
public void testSelectBatchIds() {
//根据多个id查询多个用户信息
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
List<Long> idList = Arrays.asList(4L, 5L);
List<User> list = userMapper.selectBatchIds(idList)
}
5.3 通过map条件查询用户信息
@Test
public void testSelectByMap(){
//通过map条件查询用户信息
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 22);
map.put("name", "Mp02");
List<User> list = userMapper.selectByMap(map);
list.forEach(System.out::println);
}
5.4 查询所有数据
@Test
public void testSelectList(){
//selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针
对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所 有数据。
5.5 自定义mapper查询
UserMapper 中添加selectMapById方法
package com.tigerhhzz.springbootmybatisplusdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
import org.springframework.stereotype.Repository;
import java.util.Map;
/**
* @author tigerhhzz
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 根据id查询用户信息为map集合
*/
Map<String,Object> selectMapById(Long id);
}
新建UserMapper.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.tigerhhzz.springbootmybatisplusdemo.mapper.UserMapper">
<!-- Map<String,Object> selectMapById(Long id);-->
<select id="selectMapById" resultType="map">
select id,name,age,email from user where id= #{id}
</select>
</mapper>
测试方法:
//自定义map查询
@Test
public void selectMapById(){
//通过map条件查询用户信息
Map<String, Object> map = userMapper.selectMapById(1L);
System.out.println(map);
}
结果输出:
6、通用Service
说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删 除
list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆, 泛型 T 为任意实体对象 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3
6.1 IService
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
详情查看源码IService和ServiceImpl
6.1.1 IService源码:
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.baomidou.mybatisplus.extension.service;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
/**
* 顶级 Service
*
* @author hubin
* @since 2018-06-23
*/
public interface IService<T> {
/**
* 插入一条记录(选择字段,策略插入)
*
* @param entity 实体对象
*/
boolean save(T entity);
/**
* 插入(批量)
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, 1000);
}
/**
* 插入(批量)
*
* @param entityList 实体对象集合
* @param batchSize 插入批次数量
*/
boolean saveBatch(Collection<T> entityList, int batchSize);
/**
* 批量修改插入
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveOrUpdateBatch(Collection<T> entityList) {
return saveOrUpdateBatch(entityList, 1000);
}
/**
* 批量修改插入
*
* @param entityList 实体对象集合
* @param batchSize 每次的数量
*/
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
boolean removeById(Serializable id);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
boolean removeByMap(Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
*
* @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
boolean remove(Wrapper<T> queryWrapper);
/**
* 删除(根据ID 批量删除)
*
* @param idList 主键ID列表
*/
boolean removeByIds(Collection<? extends Serializable> idList);
/**
* 根据 ID 选择修改
*
* @param entity 实体对象
*/
boolean updateById(T entity);
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
boolean update(T entity, Wrapper<T> updateWrapper);
/**
* 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
*
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(Wrapper<T> updateWrapper) {
return update(null, updateWrapper);
}
/**
* 根据ID 批量更新
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean updateBatchById(Collection<T> entityList) {
return updateBatchById(entityList, 1000);
}
/**
* 根据ID 批量更新
*
* @param entityList 实体对象集合
* @param batchSize 更新批次数量
*/
boolean updateBatchById(Collection<T> entityList, int batchSize);
/**
* TableId 注解存在更新记录,否插入一条记录
*
* @param entity 实体对象
*/
boolean saveOrUpdate(T entity);
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T getById(Serializable id);
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表
*/
List<T> listByIds(Collection<? extends Serializable> idList);
/**
* 查询(根据 columnMap 条件)
*
* @param columnMap 表字段 map 对象
*/
List<T> listByMap(Map<String, Object> columnMap);
/**
* 根据 Wrapper,查询一条记录 <br/>
* <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default T getOne(Wrapper<T> queryWrapper) {
return getOne(queryWrapper, true);
}
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param throwEx 有多个 result 是否抛出异常
*/
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
Map<String, Object> getMap(Wrapper<T> queryWrapper);
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 转换函数
*/
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
int count(Wrapper<T> queryWrapper);
/**
* 查询总记录数
*
* @see Wrappers#emptyWrapper()
*/
default int count() {
return count(Wrappers.emptyWrapper());
}
/**
* 查询列表
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
List<T> list(Wrapper<T> queryWrapper);
/**
* 查询所有
*
* @see Wrappers#emptyWrapper()
*/
default List<T> list() {
return list(Wrappers.emptyWrapper());
}
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
<E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper);
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<T>> E page(E page) {
return page(page, Wrappers.emptyWrapper());
}
/**
* 查询列表
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
/**
* 查询所有列表
*
* @see Wrappers#emptyWrapper()
*/
default List<Map<String, Object>> listMaps() {
return listMaps(Wrappers.emptyWrapper());
}
/**
* 查询全部记录
*/
default List<Object> listObjs() {
return listObjs(Function.identity());
}
/**
* 查询全部记录
*
* @param mapper 转换函数
*/
default <V> List<V> listObjs(Function<? super Object, V> mapper) {
return listObjs(Wrappers.emptyWrapper(), mapper);
}
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default List<Object> listObjs(Wrapper<T> queryWrapper) {
return listObjs(queryWrapper, Function.identity());
}
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 转换函数
*/
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
<E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper);
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<Map<String, Object>>> E pageMaps(E page) {
return pageMaps(page, Wrappers.emptyWrapper());
}
/**
* 获取对应 entity 的 BaseMapper
*
* @return BaseMapper
*/
BaseMapper<T> getBaseMapper();
/**
* 以下的方法使用介绍:
*
* 一. 名称介绍
* 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
* 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
*
* 二. 支持介绍
* 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
* 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
*
* 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
* 1. 根据条件获取一条数据: `query().eq("column", value).one()`
* 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
*
*/
/**
* 链式查询 普通
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return ChainWrappers.queryChain(getBaseMapper());
}
/**
* 链式查询 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(getBaseMapper());
}
/**
* 链式更改 普通
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return ChainWrappers.updateChain(getBaseMapper());
}
/**
* 链式更改 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(getBaseMapper());
}
/**
* <p>
* 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
* 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作)
* </p>
*
* @param entity 实体对象
*/
default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
return update(entity, updateWrapper) || saveOrUpdate(entity);
}
}
6.1.2 ServiceImpl源码:
/*
* Copyright (c) 2011-2020, baomidou (jobob@qq.com).
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.baomidou.mybatisplus.extension.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.MyBatisExceptionTranslator;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* IService 实现类( 泛型:M 是 mapper 对象,T 是实体 , PK 是主键泛型 )
*
* @author hubin
* @since 2018-06-23
*/
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
protected Log log = LogFactory.getLog(getClass());
@Autowired
protected M baseMapper;
@Override
public M getBaseMapper() {
return baseMapper;
}
/**
* 判断数据库操作是否成功
*
* @param result 数据库操作返回影响条数
* @return boolean
*/
protected boolean retBool(Integer result) {
return SqlHelper.retBool(result);
}
protected Class<T> currentModelClass() {
return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
}
/**
* 批量操作 SqlSession
*
* @deprecated 3.3.0
*/
@Deprecated
protected SqlSession sqlSessionBatch() {
return SqlHelper.sqlSessionBatch(currentModelClass());
}
/**
* 释放sqlSession
*
* @param sqlSession session
* @deprecated 3.3.0
*/
@Deprecated
protected void closeSqlSession(SqlSession sqlSession) {
SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(currentModelClass()));
}
/**
* 获取 SqlStatement
*
* @param sqlMethod ignore
* @return ignore
*/
protected String sqlStatement(SqlMethod sqlMethod) {
return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
}
@Override
public boolean save(T entity) {
return retBool(baseMapper.insert(entity));
}
/**
* 批量插入
*
* @param entityList ignore
* @param batchSize ignore
* @return ignore
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
int size = entityList.size();
executeBatch(sqlSession -> {
int i = 1;
for (T entity : entityList) {
sqlSession.insert(sqlStatement, entity);
if ((i % batchSize == 0) || i == size) {
sqlSession.flushStatements();
}
i++;
}
});
return true;
}
/**
* TableId 注解存在更新记录,否插入一条记录
*
* @param entity 实体对象
* @return boolean
*/
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdate(T entity) {
if (null != entity) {
Class<?> cls = entity.getClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
}
return false;
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
Assert.notEmpty(entityList, "error: entityList must not be empty");
Class<?> cls = currentModelClass();
TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
String keyProperty = tableInfo.getKeyProperty();
Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
int size = entityList.size();
executeBatch(sqlSession -> {
int i = 1;
for (T entity : entityList) {
Object idVal = ReflectionKit.getMethodValue(cls, entity, keyProperty);
if (StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal))) {
sqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), entity);
} else {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, entity);
sqlSession.update(sqlStatement(SqlMethod.UPDATE_BY_ID), param);
}
// 不知道以后会不会有人说更新失败了还要执行插入 😂😂😂
if ((i % batchSize == 0) || i == size) {
sqlSession.flushStatements();
}
i++;
}
});
return true;
}
@Override
public boolean removeById(Serializable id) {
return SqlHelper.retBool(baseMapper.deleteById(id));
}
@Override
public boolean removeByMap(Map<String, Object> columnMap) {
Assert.notEmpty(columnMap, "error: columnMap must not be empty");
return SqlHelper.retBool(baseMapper.deleteByMap(columnMap));
}
@Override
public boolean remove(Wrapper<T> wrapper) {
return SqlHelper.retBool(baseMapper.delete(wrapper));
}
@Override
public boolean removeByIds(Collection<? extends Serializable> idList) {
if (CollectionUtils.isEmpty(idList)) {
return false;
}
return SqlHelper.retBool(baseMapper.deleteBatchIds(idList));
}
@Override
public boolean updateById(T entity) {
return retBool(baseMapper.updateById(entity));
}
@Override
public boolean update(T entity, Wrapper<T> updateWrapper) {
return retBool(baseMapper.update(entity, updateWrapper));
}
@Transactional(rollbackFor = Exception.class)
@Override
public boolean updateBatchById(Collection<T> entityList, int batchSize) {
Assert.notEmpty(entityList, "error: entityList must not be empty");
String sqlStatement = sqlStatement(SqlMethod.UPDATE_BY_ID);
int size = entityList.size();
executeBatch(sqlSession -> {
int i = 1;
for (T anEntityList : entityList) {
MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
param.put(Constants.ENTITY, anEntityList);
sqlSession.update(sqlStatement, param);
if ((i % batchSize == 0) || i == size) {
sqlSession.flushStatements();
}
i++;
}
});
return true;
}
@Override
public T getById(Serializable id) {
return baseMapper.selectById(id);
}
@Override
public List<T> listByIds(Collection<? extends Serializable> idList) {
return baseMapper.selectBatchIds(idList);
}
@Override
public List<T> listByMap(Map<String, Object> columnMap) {
return baseMapper.selectByMap(columnMap);
}
@Override
public T getOne(Wrapper<T> queryWrapper, boolean throwEx) {
if (throwEx) {
return baseMapper.selectOne(queryWrapper);
}
return SqlHelper.getObject(log, baseMapper.selectList(queryWrapper));
}
@Override
public Map<String, Object> getMap(Wrapper<T> queryWrapper) {
return SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper));
}
@Override
public int count(Wrapper<T> queryWrapper) {
return SqlHelper.retCount(baseMapper.selectCount(queryWrapper));
}
@Override
public List<T> list(Wrapper<T> queryWrapper) {
return baseMapper.selectList(queryWrapper);
}
@Override
public <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return baseMapper.selectPage(page, queryWrapper);
}
@Override
public List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper) {
return baseMapper.selectMaps(queryWrapper);
}
@Override
public <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
return baseMapper.selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
}
@Override
public <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper) {
return baseMapper.selectMapsPage(page, queryWrapper);
}
@Override
public <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
return SqlHelper.getObject(log, listObjs(queryWrapper, mapper));
}
/**
* 执行批量操作
*
* @param fun fun
* @since 3.3.0
*/
protected void executeBatch(Consumer<SqlSession> fun) {
Class<T> tClass = currentModelClass();
SqlHelper.clearCache(tClass);
SqlSessionFactory sqlSessionFactory = SqlHelper.sqlSessionFactory(tClass);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
fun.accept(sqlSession);
sqlSession.commit();
} catch (Throwable t) {
sqlSession.rollback();
Throwable unwrapped = ExceptionUtil.unwrapThrowable(t);
if (unwrapped instanceof RuntimeException) {
MyBatisExceptionTranslator myBatisExceptionTranslator
= new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true);
throw Objects.requireNonNull(myBatisExceptionTranslator.translateExceptionIfPossible((RuntimeException) unwrapped));
}
throw ExceptionUtils.mpe(unwrapped);
} finally {
sqlSession.close();
}
}
}
6.2 创建Service接口和实现类
UserService
package com.tigerhhzz.springbootmybatisplusdemo.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
/**
* @author tigerhhzz
* @date 2023/5/5 10:51
*/
public interface UserService extends IService<User> {
}
UserServiceImpl
package com.tigerhhzz.springbootmybatisplusdemo.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
import com.tigerhhzz.springbootmybatisplusdemo.mapper.UserMapper;
import com.tigerhhzz.springbootmybatisplusdemo.service.UserService;
/**
* @author tigerhhzz
* @date 2023/5/5 10:53
*/
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
6.3 测试查询记录数
创建测试类和测试方法
package com.tigerhhzz.springbootmybatisplusdemo;
import com.tigerhhzz.springbootmybatisplusdemo.service.UserService;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
/**
* @author tigerhhzz
* @date 2023/5/5 11:10
*/
@SpringBootTest
@MapperScan("com.tigerhhzz.springbootmybatisplusdemo.mapper")
public class MyBatisPlusServiceTest {
@Autowired
UserService userService;
@Test
public void testGetCount(){
long count = userService.count();
System.out.println("总记录数:" + count);
}
}
6.4 测试批量插入
@Test
public void testSaveBatch() {
// SQL长度有限制,海量数据插入单条SQL无法实行,
// 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = new User();
user.setName("tiger" + i);
user.setAge(20 + i);
users.add(user);
}
//SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
userService.saveBatch(users);
}
四、常用注解
1、@TableName
经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在
Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表
由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决
定,且默认操作的表名和实体类型的类名一致
-
问题
若实体类类型的类名和要操作的表的表名不一致,会出现什么问题?
我们将表user更名为t_user,测试查询功能
程序抛出异常,Table ‘mybatis_plus.user’ doesn’t exist,因为现在的表名为t_user,而默认操作
的表名和实体类型的类名一致,即user表
-
通过@TableName解决问题
在实体类类型上添加@TableName(“t_user”),标识实体类对应的表,即可成功执行SQL语句
package com.tigerhhzz.springbootmybatisplusdemo.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
//设置实体类所对应的表名
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
- 通过全局配置解决问题
在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_
此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就
不需要在每个实体类上通过@TableName标识实体类对应的表
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
2、@TableId
经过以上的测试,MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认 基于雪花算法的策略生成id
- 问题
若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus会自动识别uid为主 键列吗?
我们实体类中的属性id改为uid,将表中的字段id也改为uid,测试添加功能 程序抛出异常,Field ‘uid’ doesn’t have a default value,说明MyBatis-Plus没有将uid作为主键 赋值
- 通过@TableId解决问题
在实体类中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句
package com.tigerhhzz.springbootmybatisplusdemo.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
//设置实体类所对应的表名
//@TableName("t_user")
public class User {
@TableId
private Long uid;
private String name;
private Integer age;
private String email;
}
- @TableId的value属性
若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解 @TableId,则抛出异常Unknown column ‘id’ in ‘field list’,即MyBatis-Plus仍然会将id作为表的主键操作,而表中表示主键的是字段uid 此时需要通过@TableId注解的value属性,指定表中的主键字段,@TableId(“uid”) 或者 @TableId(value=“uid”)
package com.tigerhhzz.springbootmybatisplusdemo.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
//设置实体类所对应的表名
//@TableName("t_user")
public class User {
//将属性所对应的字段指定为主键
@TableId("uid")
private Long id;
private String name;
private Integer age;
private String email;
}
- @TableId的type属性
type属性用来定义主键策略
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
//设置实体类所对应的表名
//@TableName("t_user")
public class User {
//将属性所对应的字段指定为主键
@TableId(value = "uid",type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
常用的主键策略:
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效 |
配置全局主键策略:
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath*:mapper/**/*Mapper.xml
global-config:
#MybatisPlus的全局配置
db-config:
# 逻辑不删除的值
logic-not-delete-value: 1
# 逻辑删除
logic-delete-value: 0
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
3、@TableField
经过以上的测试,我们可以发现,MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和 表中的字段名一致
如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?
- 情况1
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格 例如实体类属性userName,表中字段user_name
此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格 相当于在MyBatis中配置
- 情况2
若实体类中的属性和表中的字段不满足情况1 例如实体类属性name,表中字段username
此时需要在实体类属性上使用@TableField(“username”)设置属性所对应的字段名
4、@TableLogic
4.1 逻辑删除
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录
使用场景:可以进行数据恢复
4.2 实现逻辑删除
step1:数据库中创建逻辑删除状态列,设置默认值为1
4.3 实体类中添加逻辑删除属性
package com.tigerhhzz.springbootmybatisplusdemo.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
/**
* @author tigerhhzz
* @date 2023/5/4 16:09
*/
@Data
//设置实体类所对应的表名
//@TableName("t_user")
public class User {
//将属性所对应的字段指定为主键
@TableId(value = "uid",type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDeleted;
}
4.4 测试
测试删除功能,真正执行的是修改
UPDATE t_user SET is_deleted=0 WHERE uid IN ( ? , ? , ? ) AND is_deleted=1
测试查询功能,被逻辑删除的数据默认不会被查询
SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1
五、条件构造器和常用接口
1、wapper介绍
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
2、QueryWrapper(查询和删除)
2.1、组装查询条件
package com.tigerhhzz.springbootmybatisplusdemo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
import com.tigerhhzz.springbootmybatisplusdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author tigerhhzz
* @date 2023/5/8 14:17
*/
@SpringBootTest
@MapperScan("com.tigerhhzz.springbootmybatisplusdemo.mapper")
public class MybatisPlusWrapTest {
@Autowired
UserMapper userMapper;
@Test
public void test01(){
//查询用户名包含m,年龄在21到23之间,邮箱不为空的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","m")
.between("age",21,23)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
}
查询结果:
SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
2.2、组装排序条件
@Test
public void test02(){
//查询用户,按照年龄的降序排序,若年龄相同,则按照id升序排序
// SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 ORDER BY age DESC , uid ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> userList = userMapper.selectList(queryWrapper);
userList.forEach(System.out::println);
}
查询结果:
SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 ORDER BY age DESC , uid ASC
2.3、组装删除条件
因为添加了逻辑删除,此方法其实执行的是修改语句update
@Test
public void test03(){
//删除email为空的用户
//UPDATE t_user SET is_deleted=0 WHERE is_deleted=1 AND (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
//条件构造器也可以构建删除语句的条件
int result = userMapper.delete(queryWrapper);
System.out.println("受影响的行数:" + result);
}
结果:
2.4、条件的优先级
@Test
public void test04() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//将(年龄大于20并且用户名中包含有m)或邮箱为null的用户信息修改
//UPDATE t_user SET age=?, email=? WHERE is_deleted=1 AND (name LIKE ? AND age > ? OR email IS NULL)
queryWrapper
.like("name", "m")
.gt("age", 20)
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("test04@baomidou.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
结果:
@Test
public void test0401() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
//UPDATE t_user SET age=?, email=? WHERE is_deleted=1 AND (name LIKE ? AND ( (age > ? OR email IS NULL) ))
//lambda表达式内的逻辑优先运算
queryWrapper.like("name","m")
.and(i->i.gt("age",20).or().isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("test0401@baomidou.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
结果:
2.5、组装select子句
@Test
public void test05() {
//查询用户信息的username和age字段
//SELECT name,age FROM t_user WHERE is_deleted=1
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("name", "age");
//selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
结果:
2.6、实现子查询
@Test
public void test06() {
//查询id小于等于6的用户信息
//SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 AND (uid IN (select uid from t_user where uid <= 6))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid", "select uid from t_user where uid <= 6");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
结果:
3、UpdateWrapper(修改)
@Test
public void test07() {
//将(年龄大于20或邮箱为null)并且用户名中包含有m的用户信息修改
//组装set子句以及修改条件
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//lambda表达式内的逻辑优先运算
updateWrapper
.set("age", 19)
.set("email", "test07@baomidou.com")
.like("name", "m")
.and(i -> i.gt("age", 20).or().isNull("email"));
//这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
//UPDATE t_user SET age=?,email=? WHERE is_deleted=1 AND (name LIKE ? AND ( (age > ? OR email IS NULL) ))
int result = userMapper.update(null, updateWrapper);
System.out.println(result);
}
结果:
4、condition
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因
此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若 没有选择则一定不能组装,以免影响SQL执行的结果
思路一:
@Test
public void test08() {
//定义查询条件,有可能为null(用户未输入或未选择)
String username = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)
构成
if(StringUtils.isNotBlank(username)){
queryWrapper.like("username","a");
}
if(ageBegin != null){
queryWrapper.ge("age", ageBegin);
}
if(ageEnd != null){
queryWrapper.le("age", ageEnd);
}
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >=
? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
思路一:
上面的实现方案没有问题,但是代码比较复杂,我们可以使用带condition参数的重载方法构建查 询条件,简化代码的编写
@Test
public void test08UseCondition() {
//定义查询条件,有可能为null(用户未输入或未选择)
String username = "aaa";
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace) 构成
queryWrapper.like(StringUtils.isNotBlank(username), "name", "g")
.and(i->i.ge(ageBegin != null, "age", ageBegin).le(ageEnd != null, "age", ageEnd));
//SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 AND (name LIKE ? AND ( (age >= ? AND age <= ?) ))
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
结果:
5、LambdaQueryWrapper
@Test
public void test09() {
//定义查询条件,有可能为null(用户未输入)
String username = "g";
Integer ageBegin = 10;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
//避免使用字符串表示字段,防止运行时错误
queryWrapper
.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
//SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 AND (name LIKE ? AND age >= ? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
6、LambdaUpdateWrapper
@Test
public void test10() {
//将(年龄大于20或邮箱为null)并且用户名中包含有m的用户信息修改
//组装set子句以及修改条件
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
//lambda表达式内的逻辑优先运算
updateWrapper
.set(User::getAge, 19)
.set(User::getEmail, "test10@baomidou.com")
.like(User::getName, "m")
.and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
//这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
//UPDATE t_user SET age=?,email=? WHERE is_deleted=1 AND (name LIKE ? AND ( (age > ? OR email IS NULL) ))
int result = userMapper.update(null, updateWrapper);
System.out.println(result);
}
结果:
六、插件
1、分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
1.1、添加配置类
package com.tigerhhzz.springbootmybatisplusdemo.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.tigerhhzz.springbootmybatisplusdemo.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
1.2、测试
package com.tigerhhzz.springbootmybatisplusdemo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
import com.tigerhhzz.springbootmybatisplusdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author tigerhhzz
* @date 2023/5/9 8:41
*/
@SpringBootTest
@MapperScan("com.tigerhhzz.springbootmybatisplusdemo.mapper")
public class MybatisPlusPluginTest {
@Autowired
UserMapper userMapper;
@Test
public void testPage(){
//设置分页参数
Page<User> page = new Page<>(2, 3);
userMapper.selectPage(page, null);
//获取分页数据 SELECT uid AS id,name,age,email,is_deleted FROM t_user WHERE is_deleted=1 LIMIT ?,?
List<User> 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());
}
}
结果:
2、xml自定义分页
2.1、UserMapper中定义接口方法
package com.tigerhhzz.springbootmybatisplusdemo.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tigerhhzz.springbootmybatisplusdemo.domain.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Map;
/**
* @author tigerhhzz
*/
@Repository
public interface UserMapper extends BaseMapper<User> {
/**
* 根据id查询用户信息为map集合
*/
Map<String,Object> selectMapById(Long id);
/**
* 通过年龄查询用户信息并分页
* @param page mybatis-plus所提供的分页对象,必须位于第一个参数的位置
* @param age
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
}
2.2、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.tigerhhzz.springbootmybatisplusdemo.mapper.UserMapper">
<!-- Map<String,Object> selectMapById(Long id);-->
<select id="selectMapById" resultType="map">
select id,name,age,email from user where id= #{id}
</select>
<select id="selectPageVo" resultType="User">
select uid as id,name,age,email from t_user where age > #{age}
</select>
</mapper>
2.3、测试
@Test
public void testPageVo(){
Page<User> page = new Page<>(1,3);
Page<User> pageVo = userMapper.selectPageVo(page, 20);
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());
}
结果: