一、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
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()); }
结果: