博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringBoot整合mybatisplus
阅读量:2169 次
发布时间:2019-05-01

本文共 32481 字,大约阅读时间需要 108 分钟。

SpringBoot之mybatis-plus实战

文章目录

一、概述

1,综述

mybatis-plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

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 操作智能分析阻断,也可自定义拦截规则,预防误操作

二、实战

准备

全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调用,所以安装集成 MP3.0 要求 JDK 8+

创建一个springboot 项目,引入springboot依赖

mysql 数据库,新建一张表

脚本如下

DROP TABLE IF EXISTS user;CREATE TABLE user(	id BIGINT(20) NOT NULL COMMENT '主键ID',	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',	age INT(11) NULL DEFAULT NULL COMMENT '年龄',	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',	PRIMARY KEY (id));DELETE FROM user;INSERT INTO user (id, name, age, email) VALUES(1, 'Jone', 18, 'test1@baomidou.com'),(2, 'Jack', 20, 'test2@baomidou.com'),(3, 'Tom', 28, 'test3@baomidou.com'),(4, 'Sandy', 21, 'test4@baomidou.com'),(5, 'Billie', 24, 'test5@baomidou.com');

1、核心依赖库

com.baomidou
mybatis-plus-boot-starter
3.4.3.1
mysql
mysql-connector-java

2、配置

1、 最基本的配置

application.yml 配置文件中添加 mysql 数据库的相关配置:

spring:  datasource:    url: jdbc:mysql://localhost:3306/test    username: root    password: 123456    driver-class-name: com.mysql.jdbc.Driver#开启大小写转换mybatis:  configuration:    map-underscore-to-camel-case: trueserver:  port: 8081

3、启动类

在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:

@SpringBootApplication@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")public class Application {    public static void main(String[] args) {        SpringApplication.run(QuickStartApplication.class, args);    }}
 

4、核心代码

编写实体类 User.java

@Datapublic class User {    private Long id;    private String name;    private Integer age;    private String email;}

编写Mapper类 UserMapper.java

public interface UserMapper extends BaseMapper
{}

添加测试类,进行功能测试:

@SpringBootTestpublic class SampleTest {    @Autowired    private UserMapper userMapper;    @Test    public void testSelect() {        System.out.println(("----- selectAll method test ------"));        List
userList = userMapper.selectList(null); Assert.assertEquals(5, userList.size()); userList.forEach(System.out::println); }}

以上几个简单的步骤,实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!

从以上步骤中,我们可以看到集成MyBatis-Plus非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。

三、代码生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

使用教程

添加依赖

com.baomidou
mybatis-plus-generator
3.4.1
org.apache.velocity
velocity-engine-core
2.3

MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎。

演示例子

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;import com.baomidou.mybatisplus.generator.AutoGenerator;import com.baomidou.mybatisplus.generator.InjectionConfig;import com.baomidou.mybatisplus.generator.config.*;import com.baomidou.mybatisplus.generator.config.po.TableInfo;import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;import org.apache.commons.lang3.StringUtils;import java.util.ArrayList;import java.util.List;import java.util.Scanner;/** * 代码生成器 * * @Author Lizhou */public class CodeGenerator {    /**     * 

* 读取控制台内容 *

*/ public static String scanner(String tip) { Scanner scanner = new Scanner(System.in); StringBuilder help = new StringBuilder(); help.append("请输入" + tip + ":"); System.out.println(help.toString()); if (scanner.hasNext()) { String ipt = scanner.next(); if (StringUtils.isNotEmpty(ipt)) { return ipt; } } throw new MybatisPlusException("请输入正确的" + tip + "!"); } /** * 自动生成代码 */ public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // TODO 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); // 生成文件的输出目录【默认 D 盘根目录】 gc.setOutputDir(projectPath + "/src/main/java"); // 作者 gc.setAuthor("lizhou"); // 是否打开输出目录 gc.setOpen(false); // controller 命名方式,注意 %s 会自动填充表实体属性 gc.setControllerName("%sController"); // service 命名方式 gc.setServiceName("%sService"); // serviceImpl 命名方式 gc.setServiceImplName("%sServiceImpl"); // mapper 命名方式 gc.setMapperName("%sMapper"); // xml 命名方式 gc.setXmlName("%sMapper"); // 开启 swagger2 模式 gc.setSwagger2(true); // 是否覆盖已有文件 gc.setFileOverride(true); // 是否开启 ActiveRecord 模式 gc.setActiveRecord(true); // 是否在xml中添加二级缓存配置 gc.setEnableCache(false); // 是否开启 BaseResultMap gc.setBaseResultMap(true); // XML columList gc.setBaseColumnList(false); // 全局 相关配置 mpg.setGlobalConfig(gc); // TODO 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://127.0.0.1:3306/sbm?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=true&characterEncoding=UTF-8"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // TODO 包配置 PackageConfig pc = new PackageConfig(); // 父包名。如果为空,将下面子包名必须写全部, 否则就只需写子包名 pc.setParent("com.zyxx.sbm"); // Entity包名 pc.setEntity("entity"); // Service包名 pc.setService("service"); // Service Impl包名 pc.setServiceImpl("service.impl"); mpg.setPackageInfo(pc); // TODO 自定义配置 InjectionConfig cfg = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 输出文件配置 List
focList = new ArrayList<>(); focList.add(new FileOutConfig("/templates/mapper.xml.ftl") { @Override public String outputFile(TableInfo tableInfo) { // 自定义输入文件名称 return projectPath + "/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper.xml"; } }); // 自定义输出文件 cfg.setFileOutConfigList(focList); mpg.setCfg(cfg); mpg.setTemplate(new TemplateConfig().setXml(null)); // TODO 策略配置 StrategyConfig strategy = new StrategyConfig(); // 数据库表映射到实体的命名策略,驼峰原则 strategy.setNaming(NamingStrategy.underline_to_camel); // 字数据库表字段映射到实体的命名策略,驼峰原则 strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 实体是否生成 serialVersionUID strategy.setEntitySerialVersionUID(false); // 是否生成实体时,生成字段注解 strategy.setEntityTableFieldAnnotationEnable(true); // 使用lombok strategy.setEntityLombokModel(true); // 设置逻辑删除键 strategy.setLogicDeleteFieldName("del_flag"); // TODO 指定生成的bean的数据库表名 strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); // 驼峰转连字符 strategy.setControllerMappingHyphenStyle(true); mpg.setStrategy(strategy); // 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有! mpg.setTemplateEngine(new FreemarkerTemplateEngine()); mpg.execute(); }}

我们需要改变的就是:

  • 数据库的连接信息,地址,用户,密码
  • controller、service、mapper、xml 等文件的命名规则
  • 生成文件 controller、service、mapper、xml 文件的存放位置
  • 数据库与实体类之间的一些策略配置等

自定义代码模板

Controller.java.vm

package ${package}.${moduleName}.controller;import com.baomidou.mybatisplus.core.toolkit.Wrappers;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;import ${package}.${moduleName}.entity.${className};import ${package}.${moduleName}.service.${className}Service;import org.springframework.security.access.prepost.PreAuthorize;import io.swagger.annotations.Api;import io.swagger.annotations.ApiOperation;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.*;/** * ${comments} * * @author ${author} * @date ${datetime} */@RestController@RequiredArgsConstructor@RequestMapping("/${pathName}" )@Api(value = "${pathName}", tags = "${comments}管理")public class ${className}Controller {    private final  ${className}Service ${classname}Service;    /**     * 分页查询     * @param page 分页对象     * @param ${classname} ${comments}     * @return     */    @ApiOperation(value = "分页查询", notes = "分页查询")    @GetMapping("/page" )    @PreAuthorize("@pms.hasPermission('${moduleName}_${pathName}_get')" )    public R get${className}Page(Page page, ${className} ${classname}) {        return R.ok(${classname}Service.page(page, Wrappers.query(${classname})));    }    /**     * 通过id查询${comments}     * @param ${pk.lowerAttrName} id     * @return R     */    @ApiOperation(value = "通过id查询", notes = "通过id查询")    @GetMapping("/{${pk.lowerAttrName}}" )    @PreAuthorize("@pms.hasPermission('${moduleName}_${pathName}_get')" )    public R getById(@PathVariable("${pk.lowerAttrName}" ) ${pk.attrType} ${pk.lowerAttrName}) {        return R.ok(${classname}Service.getById(${pk.lowerAttrName}));    }    /**     * 新增${comments}     * @param ${classname} ${comments}     * @return R     */    @ApiOperation(value = "新增${comments}", notes = "新增${comments}")    @SysLog("新增${comments}" )    @PostMapping    @PreAuthorize("@pms.hasPermission('${moduleName}_${pathName}_add')" )    public R save(@RequestBody ${className} ${classname}) {        return R.ok(${classname}Service.save(${classname}));    }    /**     * 修改${comments}     * @param ${classname} ${comments}     * @return R     */    @ApiOperation(value = "修改${comments}", notes = "修改${comments}")    @SysLog("修改${comments}" )    @PutMapping    @PreAuthorize("@pms.hasPermission('${moduleName}_${pathName}_edit')" )    public R updateById(@RequestBody ${className} ${classname}) {        return R.ok(${classname}Service.updateById(${classname}));    }    /**     * 通过id删除${comments}     * @param ${pk.lowerAttrName} id     * @return R     */    @ApiOperation(value = "通过id删除${comments}", notes = "通过id删除${comments}")    @SysLog("通过id删除${comments}" )    @DeleteMapping("/{${pk.lowerAttrName}}" )    @PreAuthorize("@pms.hasPermission('${moduleName}_${pathName}_del')" )    public R removeById(@PathVariable ${pk.attrType} ${pk.lowerAttrName}) {        return R.ok(${classname}Service.removeById(${pk.lowerAttrName}));    }}

Entity.java.vm

package ${package}.${moduleName}.entity;import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import com.baomidou.mybatisplus.extension.activerecord.Model;import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import lombok.EqualsAndHashCode;#if(${hasBigDecimal})import java.math.BigDecimal;#endimport java.io.Serializable;import java.time.LocalDateTime;/** * ${comments} * * @author ${author} * @date ${datetime} */@Data@TableName("${tableName}")@EqualsAndHashCode(callSuper = true)@ApiModel(value = "${comments}")public class ${className} extends Model<${className}> {private static final long serialVersionUID = 1L;    #foreach ($column in $columns)/**     * $column.comments     */    #if($column.columnName == $pk.columnName)@TableId    #end@ApiModelProperty(value="$column.comments"#if($column.hidden),hidden=$column.hidden#end)    private $column.attrType $column.lowerAttrName;    #end}package ${package}.${moduleName}.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import ${package}.${moduleName}.entity.${className};import org.apache.ibatis.annotations.Mapper;/** * ${comments} * * @author ${author} * @date ${datetime} */@Mapperpublic interface ${className}Mapper extends BaseMapper<${className}> {}
#foreach($column in $columns) #if($column.lowerAttrName==$pk.lowerAttrName)
#else
#end #end
package ${package}.${moduleName}.service;import com.baomidou.mybatisplus.extension.service.IService;import ${package}.${moduleName}.entity.${className};/** * ${comments} * * @author ${author} * @date ${datetime} */public interface ${className}Service extends IService<${className}> {}package ${package}.${moduleName}.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import ${package}.${moduleName}.entity.${className};import ${package}.${moduleName}.mapper.${className}Mapper;import ${package}.${moduleName}.service.${className}Service;import org.springframework.stereotype.Service;/** * ${comments} * * @author ${author} * @date ${datetime} */@Servicepublic class ${className}ServiceImpl extends ServiceImpl<${className}Mapper, ${className}> implements ${className}Service {}

四,基类CRUD介绍

注解

mp一共提供了8个注解,这些注解是用在Java的实体类上面的。

  • @TableName

    注解在类上,指定类和数据库表的映射关系。实体类的类名(转成小写后)和数据库表名相同时,可以不指定该注解。

  • @TableId

    注解在实体类的某一字段上,表示这个字段对应数据库表的主键。当主键名为id时(表中列名为id,实体类中字段名为id),无需使用该注解显式指定主键,mp会自动关联。若类的字段名和表的列名不一致,可用value属性指定表的列名。另,这个注解有个重要的属性type,用于指定主键策略

  • @TableField

    注解在某一字段上,指定Java实体类的字段和数据库表的列的映射关系。这个注解有如下几个应用场景。

    • 排除非表字段
      若Java实体类中某个字段,不对应表中的任何列,它只是用于保存一些额外的,或组装后的数据,则可以设置exist属性为false,这样在对实体对象进行插入时,会忽略这个字段。排除非表字段也可以通过其他方式完成,如使用statictransient关键字,但个人觉得不是很合理,不做赘述
    • 字段验证策略
      通过insertStrategyupdateStrategywhereStrategy属性进行配置,可以控制在实体对象进行插入,更新,或作为WHERE条件时,对象中的字段要如何组装到SQL语句中。
    • 字段填充策略
      通过fill属性指定,字段为空时会进行自动填充
  • @Version

    乐观锁注解

  • @EnumValue

    注解在枚举字段上

  • @TableLogic

    逻辑删除,

  • KeySequence

    序列主键策略(oracle

  • InterceptorIgnore

    插件过滤规则

CRUD 接口

Service CRUD 接口

Save

// 插入一条记录(选择字段,策略插入)boolean save(T entity);// 插入(批量)boolean saveBatch(Collection
entityList);// 插入(批量)boolean saveBatch(Collection
entityList, int batchSize);
参数说明
类型 参数名 描述
T entity 实体对象
Collection entityList 实体对象集合
int batchSize 插入批次数量

SaveOrUpdate

// TableId 注解存在更新记录,否插入一条记录boolean saveOrUpdate(T entity);// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法boolean saveOrUpdate(T entity, Wrapper
updateWrapper);// 批量修改插入boolean saveOrUpdateBatch(Collection
entityList);// 批量修改插入boolean saveOrUpdateBatch(Collection
entityList, int batchSize);
参数说明
类型 参数名 描述
T entity 实体对象
Wrapper updateWrapper 实体对象封装操作类 UpdateWrapper
Collection entityList 实体对象集合
int batchSize 插入批次数量

Remove

// 根据 entity 条件,删除记录boolean remove(Wrapper
queryWrapper);// 根据 ID 删除boolean removeById(Serializable id);// 根据 columnMap 条件,删除记录boolean removeByMap(Map
columnMap);// 删除(根据ID 批量删除)boolean removeByIds(Collection
idList);
参数说明
类型 参数名 描述
Wrapper queryWrapper 实体包装类 QueryWrapper
Serializable id 主键ID
Map<String, Object> columnMap 表字段 map 对象
Collection<? extends Serializable> idList 主键ID列表

Update

// 根据 UpdateWrapper 条件,更新记录 需要设置sqlsetboolean update(Wrapper
updateWrapper);// 根据 whereWrapper 条件,更新记录boolean update(T updateEntity, Wrapper
whereWrapper);// 根据 ID 选择修改boolean updateById(T entity);// 根据ID 批量更新boolean updateBatchById(Collection
entityList);// 根据ID 批量更新boolean updateBatchById(Collection
entityList, int batchSize);
参数说明
类型 参数名 描述
Wrapper updateWrapper 实体对象封装操作类 UpdateWrapper
T entity 实体对象
Collection entityList 实体对象集合
int batchSize 更新批次数量
修改参数丢失(更新部分数据时)

注意:基本数据类型默认值的存在导致属性参与 sql 拼接,引起参数丢失问题

参数实体属性值如果为 null,不参与 sql 拼接;

如果参数实体属性类型是 8 大基本数据类型,有不为 null 的默认值,mybatis-plus 认为有属性值,参数参与 sql 拼接;

解决:

方案一:将基本数据类型属性改为包装类型;

方案二:1> 先查询得到整条数据;2> 修改该条数据上想要修改的字段;3> 更新整条数据;

方案三:使用 update(null,wrapper)方法操作,部分字段更新方法;

updateById(entry) 跟 update(null, wrapper) 怎么选择?

updateById(entry)

1> sql 的 where 条件是 id 时使用;

2> 全部字段更新时使用;

update(null, wrapper)

1> sql 的 where 条件不一定是 id 时使用,例如 eg:where age > 19;

2> 部分字段更新时使用;

Get

// 根据 ID 查询T getById(Serializable id);// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")T getOne(Wrapper
queryWrapper);// 根据 Wrapper,查询一条记录T getOne(Wrapper
queryWrapper, boolean throwEx);// 根据 Wrapper,查询一条记录Map
getMap(Wrapper
queryWrapper);// 根据 Wrapper,查询一条记录
V getObj(Wrapper
queryWrapper, Function
mapper);
参数说明
类型 参数名 描述
Serializable id 主键ID
Wrapper queryWrapper 实体对象封装操作类 QueryWrapper
boolean throwEx 有多个 result 是否抛出异常
T entity 实体对象
Function<? super Object, V> mapper 转换函数

List

// 查询所有List
list();// 查询列表List
list(Wrapper
queryWrapper);// 查询(根据ID 批量查询)Collection
listByIds(Collection
idList);// 查询(根据 columnMap 条件)Collection
listByMap(Map
columnMap);// 查询所有列表List
> listMaps();// 查询列表List
> listMaps(Wrapper
queryWrapper);// 查询全部记录List
listObjs();// 查询全部记录
List
listObjs(Function
mapper);// 根据 Wrapper 条件,查询全部记录List
listObjs(Wrapper
queryWrapper);// 根据 Wrapper 条件,查询全部记录
List
listObjs(Wrapper
queryWrapper, Function
mapper);
参数说明
类型 参数名 描述
Wrapper queryWrapper 实体对象封装操作类 QueryWrapper
Collection<? extends Serializable> idList 主键ID列表
Map<?String, Object> columnMap 表字段 map 对象
Function<? super Object, V> mapper 转换函数

Page

// 无条件分页查询IPage
page(IPage
page);// 条件分页查询IPage
page(IPage
page, Wrapper
queryWrapper);// 无条件分页查询IPage
> pageMaps(IPage
page);// 条件分页查询IPage
> pageMaps(IPage
page, Wrapper
queryWrapper);
参数说明
类型 参数名 描述
IPage page 翻页对象
Wrapper queryWrapper 实体对象封装操作类 QueryWrapper

Count

// 查询总记录数int count();// 根据 Wrapper 条件,查询总记录数int count(Wrapper
queryWrapper);
参数说明
类型 参数名 描述
Wrapper queryWrapper 实体对象封装操作类 QueryWrapper

Chain

query

// 链式查询 普通QueryChainWrapper
query();// 链式查询 lambda 式。注意:不支持 KotlinLambdaQueryChainWrapper
lambdaQuery(); // 示例:query().eq("column", value).one();lambdaQuery().eq(Entity::getId, value).list();

update

// 链式更改 普通UpdateChainWrapper
update();// 链式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper
lambdaUpdate();// 示例:update().eq("column", value).remove();lambdaUpdate().eq(Entity::getId, value).update(entity);

Mapper CRUD 接口

说明:

  • 通用 CRUD 封装接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器
  • 泛型 T 为任意实体对象
  • 参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
  • 对象 Wrapper

Insert

// 插入一条记录int insert(T entity);
参数说明
类型 参数名 描述
T entity 实体对象

Delete

// 根据 entity 条件,删除记录int delete(@Param(Constants.WRAPPER) Wrapper
wrapper);// 删除(根据ID 批量删除)int deleteBatchIds(@Param(Constants.COLLECTION) Collection
idList);// 根据 ID 删除int deleteById(Serializable id);// 根据 columnMap 条件,删除记录int deleteByMap(@Param(Constants.COLUMN_MAP) Map
columnMap);
参数说明
类型 参数名 描述
Wrapper wrapper 实体对象封装操作类(可以为 null)
Collection<? extends Serializable> idList 主键ID列表(不能为 null 以及 empty)
Serializable id 主键ID
Map<String, Object> columnMap 表字段 map 对象

Update

// 根据 whereWrapper 条件,更新记录int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper
whereWrapper);// 根据 ID 修改int updateById(@Param(Constants.ENTITY) T entity);
参数说明
类型 参数名 描述
T entity 实体对象 (set 条件值,可为 null)
Wrapper updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)

Select

// 根据 ID 查询T selectById(Serializable id);// 根据 entity 条件,查询一条记录T selectOne(@Param(Constants.WRAPPER) Wrapper
queryWrapper);// 查询(根据ID 批量查询)List
selectBatchIds(@Param(Constants.COLLECTION) Collection
idList);// 根据 entity 条件,查询全部记录List
selectList(@Param(Constants.WRAPPER) Wrapper
queryWrapper);// 查询(根据 columnMap 条件)List
selectByMap(@Param(Constants.COLUMN_MAP) Map
columnMap);// 根据 Wrapper 条件,查询全部记录List
> selectMaps(@Param(Constants.WRAPPER) Wrapper
queryWrapper);// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值List
selectObjs(@Param(Constants.WRAPPER) Wrapper
queryWrapper);// 根据 entity 条件,查询全部记录(并翻页)IPage
selectPage(IPage
page, @Param(Constants.WRAPPER) Wrapper
queryWrapper);// 根据 Wrapper 条件,查询全部记录(并翻页)IPage
> selectMapsPage(IPage
page, @Param(Constants.WRAPPER) Wrapper
queryWrapper);// 根据 Wrapper 条件,查询总记录数Integer selectCount(@Param(Constants.WRAPPER) Wrapper
queryWrapper);
参数说明
类型 参数名 描述
Serializable id 主键ID
Wrapper queryWrapper 实体对象封装操作类(可以为 null)
Collection<? extends Serializable> idList 主键ID列表(不能为 null 以及 empty)
Map<String, Object> columnMap 表字段 map 对象
IPage page 分页查询条件(可以为 RowBounds.DEFAULT)

条件构造

AbstractWrapper

说明:

QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类

用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为

allEq(map里面的条件用and链接)

allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

filter:声明可以接受两个参数的并且返回boolean类型函数式,过滤函数,是否允许字段传入比对条件中,可以用来判断传入字段是否合法

params:key为数据库字段名,value为字段值

null2IsNull:true则在map的value为null时调用isNull方法(即该字段的条件为 is null),为false则不做添加条件

condition:该条件是否加入最后生成的sql中下面对AbstractWrapper中用于构建SQL语句中的WHERE条件的方法进行部分列举

  • eq:equals,等于
  • allEq:all equals,全等于
  • ne:not equals,不等于
  • gt:greater than ,大于 >
  • ge:greater than or equals,大于等于
  • lt:less than,小于<
  • le:less than or equals,小于等于
  • between:相当于SQL中的BETWEEN
  • notBetween
  • like:模糊匹配。like("name","黄"),相当于SQL的name like '%黄%'
  • likeRight:模糊匹配右半边。likeRight("name","黄"),相当于SQL的name like '黄%'
  • likeLeft:模糊匹配左半边。likeLeft("name","黄"),相当于SQL的name like '%黄'
  • notLikenotLike("name","黄"),相当于SQL的name not like '%黄%'
  • isNull
  • isNotNull
  • in
  • and:SQL连接符AND
  • or:SQL连接符OR
  • apply:用于拼接SQL,该方法可用于数据库函数,并可以动态传参

使用实例

下面通过一些具体的案例来练习条件构造器的使用。(使用前文创建的user表)

// 案例先展示需要完成的SQL语句,后展示Wrapper的写法// 1. 名字中包含佳,且年龄小于25// SELECT * FROM user WHERE name like '%佳%' AND age < 25QueryWrapper
wrapper = new QueryWrapper<>();wrapper.like("name", "佳").lt("age", 25);List
users = userMapper.selectList(wrapper);// 下面展示SQL时,仅展示WHERE条件;展示代码时, 仅展示Wrapper构建部分// 2. 姓名为黄姓,且年龄大于等于20,小于等于40,且email字段不为空// name like '黄%' AND age BETWEEN 20 AND 40 AND email is not nullwrapper.likeRight("name","黄").between("age", 20, 40).isNotNull("email");// 3. 姓名为黄姓,或者年龄大于等于40,按照年龄降序排列,年龄相同则按照id升序排列// name like '黄%' OR age >= 40 order by age desc, id ascwrapper.likeRight("name","黄").or().ge("age",40).orderByDesc("age").orderByAsc("id");// 4.创建日期为2021年3月22日,并且直属上级的名字为李姓// date_format(create_time,'%Y-%m-%d') = '2021-03-22' AND manager_id IN (SELECT id FROM user WHERE name like '李%')wrapper.apply("date_format(create_time, '%Y-%m-%d') = {0}", "2021-03-22") // 建议采用{index}这种方式动态传参, 可防止SQL注入 .inSql("manager_id", "SELECT id FROM user WHERE name like '李%'");// 上面的apply, 也可以直接使用下面这种方式做字符串拼接,但当这个日期是一个外部参数时,这种方式有SQL注入的风险wrapper.apply("date_format(create_time, '%Y-%m-%d') = '2021-03-22'");// 5. 名字为王姓,并且(年龄小于40,或者邮箱不为空)// name like '王%' AND (age < 40 OR email is not null)wrapper.likeRight("name", "王").and(q -> q.lt("age", 40).or().isNotNull("email"));// 6. 名字为王姓,或者(年龄小于40并且年龄大于20并且邮箱不为空)// name like '王%' OR (age < 40 AND age > 20 AND email is not null)wrapper.likeRight("name", "王").or( q -> q.lt("age",40) .gt("age",20) .isNotNull("email") );// 7. (年龄小于40或者邮箱不为空) 并且名字为王姓// (age < 40 OR email is not null) AND name like '王%'wrapper.nested(q -> q.lt("age", 40).or().isNotNull("email")) .likeRight("name", "王");// 8. 年龄为30,31,34,35// age IN (30,31,34,35)wrapper.in("age", Arrays.asList(30,31,34,35));// 或wrapper.inSql("age","30,31,34,35");// 9. 年龄为30,31,34,35, 返回满足条件的第一条记录// age IN (30,31,34,35) LIMIT 1wrapper.in("age", Arrays.asList(30,31,34,35)).last("LIMIT 1");// 10. 只选出id, name 列 (QueryWrapper 特有)// SELECT id, name FROM user;wrapper.select("id", "name");// 11. 选出id, name, age, email, 等同于排除 manager_id 和 create_time// 当列特别多, 而只需要排除个别列时, 采用上面的方式可能需要写很多个列, 可以采用重载的select方法,指定需要排除的列wrapper.select(User.class, info -> { String columnName = info.getColumn(); return !"create_time".equals(columnName) && !"manager_id".equals(columnName); });

分页插件

//Spring boot方式@Configuration@MapperScan("com.baomidou.cloud.service.*.mapper*")public class MybatisPlusConfig {    // 旧版    @Bean    public PaginationInterceptor paginationInterceptor() {        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();        // 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求  默认false        // paginationInterceptor.setOverflow(false);        // 设置最大单页限制数量,默认 500 条,-1 不受限制        // paginationInterceptor.setLimit(500);        // 开启 count 的 join 优化,只针对部分 left join        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));        return paginationInterceptor;    }        // 最新版    @Bean    /**	 * 分页插件, 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型	 */    public MybatisPlusInterceptor mybatisPlusInterceptor() {        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));        return interceptor;    }    }

逻辑删除

新建一张新表

DROP TABLE IF EXISTS user2;CREATE TABLE user2 (id BIGINT(20) PRIMARY KEY NOT NULL COMMENT '主键id',name VARCHAR(30) DEFAULT NULL COMMENT '姓名',age INT(11) DEFAULT NULL COMMENT '年龄',email VARCHAR(50) DEFAULT NULL COMMENT '邮箱',manager_id BIGINT(20) DEFAULT NULL COMMENT '直属上级id',create_time DATETIME DEFAULT NULL COMMENT '创建时间',update_time DATETIME DEFAULT NULL COMMENT '修改时间',version INT(11) DEFAULT '1' COMMENT '版本',deleted INT(1) DEFAULT '0' COMMENT '逻辑删除标识,0-未删除,1-已删除',CONSTRAINT manager_fk FOREIGN KEY(manager_id) REFERENCES user2(id)) ENGINE = INNODB CHARSET=UTF8;INSERT INTO user2(id, name, age, email, manager_id, create_time)VALUES(1, '老板', 40 ,'boss@baomidou.com' ,NULL, '2021-03-28 13:12:40'),(2, '王狗蛋', 40 ,'gd@baomidou.com' ,1, '2021-03-28 13:12:40'),(3, '王鸡蛋', 40 ,'jd@baomidou.com' ,2, '2021-03-28 13:12:40'),(4, '王鸭蛋', 40 ,'yd@baomidou.com' ,2, '2021-03-28 13:12:40'),(5, '王猪蛋', 40 ,'zd@baomidou.com' ,2, '2021-03-28 13:12:40'),(6, '王软蛋', 40 ,'rd@baomidou.com' ,2, '2021-03-28 13:12:40'),(7, '王铁蛋', 40 ,'td@baomidou.com' ,2, '2021-03-28 13:12:40')

首先,为什么要有逻辑删除呢?直接删掉不行吗?当然可以,但日后若想要恢复,或者需要查看这些数据,就做不到了。逻辑删除是为了方便数据恢复,和保护数据本身价值的一种方案

日常中,我们在电脑中删除一个文件后,也仅仅是把该文件放入了回收站,日后若有需要还能进行查看或恢复。当我们确定不再需要某个文件,可以将其从回收站中彻底删除。这也是类似的道理。

mp提供的逻辑删除实现起来非常简单

只需要在application.yml中进行逻辑删除的相关配置即可

mybatis-plus:  global-config:    db-config:      logic-delete-field: deleted  # 全局逻辑删除的实体字段名      logic-delete-value: 1 # 逻辑已删除值(默认为1)      logic-not-delete-value: 0 # 逻辑未删除值(默认为0)      # 若逻辑已删除和未删除的值和默认值一样,则可以不配置这2项

删除发出的SQL不再是DELETE,而是UPDATE

查询语句会自动在WHERE后面拼接逻辑未删除的条件

小结

开启mp的逻辑删除后,会对SQL产生如下的影响

  • INSERT语句:没有影响
  • SELECT语句:追加WHERE条件,过滤掉已删除的数据
  • UPDATE语句:追加WHERE条件,防止更新到已删除的数据
  • DELETE语句:转变为UPDATE语句

**注意,上述的影响,只针对mp自动注入的SQL生效。**如果是自己手动添加的自定义SQL,则不会生效。

自动填充

表中常常会有“新增时间”,“修改时间”,“操作人” 等字段。比较原始的方式,是每次插入或更新时,手动进行设置。mp可以通过配置,对某些字段进行自动填充

在实体类中的某些字段上,通过@TableField设置自动填充 public class User2 { private Long id; private String name; private Integer age; private String email; private Long managerId; @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.UPDATE) // 更新时自动填充 private LocalDateTime updateTime; private Integer version; private Integer deleted;}实现自动填充处理器 package com.example.mp.component;import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import org.apache.ibatis.reflection.MetaObject;import org.springframework.stereotype.Component;import java.time.LocalDateTime;@Component //需要注册到Spring容器中public class MyMetaObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { // 插入时自动填充 // 注意第二个参数要填写实体类中的字段名称,而不是表的列名称  strictFillStrategy(metaObject, "createTime", LocalDateTime::now); } @Override public void updateFill(MetaObject metaObject) { // 更新时自动填充  strictFillStrategy(metaObject, "updateTime", LocalDateTime::now); }}

五、插件

乐观锁插件

当出现并发操作时,需要确保各个用户对数据的操作不产生冲突,此时需要一种并发控制手段。悲观锁的方法是,在对数据库的一条记录进行修改时,先直接加锁(数据库的锁机制),锁定这条数据,然后再进行操作;而乐观锁,正如其名,它先假设不存在冲突情况,而在实际进行数据操作时,再检查是否冲突。乐观锁的一种通常实现是版本号,在MySQL中也有名为MVCC的基于版本号的并发事务控制。

在读多写少的场景下,乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量。

在写多读少的场景下,悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降。

乐观锁的实现如下:

  1. 取出记录时,获取当前version
  2. 更新时,带上这个version
  3. 执行更新时, set version = newVersion where version = oldVersion
  4. 如果oldVersion与数据库中的version不一致,就更新失败

这种思想和CAS(Compare And Swap)非常相似。

乐观锁的实现步骤如下

配置乐观锁插件 package com.example.mp.config;import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationpublic class MybatisPlusConfig { /** 3.4.0以后的mp版本,推荐用如下的配置方式 **/ @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() {  MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();  interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } /** 旧版mp可以采用如下方式。注意新旧版本中,新版的类,名称带有Inner, 旧版的不带, 不要配错了 **/ /*    @Bean public OptimisticLockerInterceptor opLocker() {  return new OptimisticLockerInterceptor(); } */} 在实体类中表示版本的字段上添加注解@Version @Datapublic class User2 { private Long id; private String name; private Integer age; private String email; private Long managerId; private LocalDateTime createTime; private LocalDateTime updateTime; @Version private Integer version; private Integer deleted;}

注意,乐观锁插件仅支持updateById(id)update(entity, wrapper)方法

**注意:如果使用wrapper,则wrapper不能复用!

性能分析插件

该插件会输出SQL语句的执行时间,以便做SQL语句的性能分析和调优。

注:3.2.0版本之后,mp自带的性能分析插件被官方移除了,而推荐使用第三方性能分析插件

使用步骤

引入maven依赖 
p6spy
p6spy
3.9.1
修改application.yml spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver #换成p6spy的驱动 url: jdbc:p6spy:mysql://localhost:3306/yogurt?serverTimezone=Asia/Shanghai #url修改 username: root password: root
  1. src/main/resources资源目录下添加spy.properties
    #spy.properties #3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory # 真实JDBC driver , 多个以逗号分割,默认为空。由于上面设置了modulelist, 这里可以不用设置driverlist #driverlist=com.mysql.cj.jdbc.Driver # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger #若要日志输出到文件, 把上面的appnder注释掉, 或者采用下面的appender, 再添加logfile配置 #不配置appender时, 默认是往文件进行输出的 #appender=com.p6spy.engine.spy.appender.FileLogger #logfile=log.log # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2 # 执行时间设置, 只有超过这个执行时间的才进行记录, 默认值0, 单位毫秒 executionThreshold=10 复制代码

随便运行一个测试用例,可以看到该SQL的执行时长被记录了下来

多数据源

配置

com.baomidou
mybatis-plus-boot-starter
3.4.0
mysql
mysql-connector-java
8.0.18
runtime
com.oracle
ojdbc6
11.2.0.1.0
com.baomidou
dynamic-datasource-spring-boot-starter
2.5.4
spring: autoconfigure: # 为了某些版本的springboot @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) 无法生效 exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure datasource: dynamic: primary: master datasource: master: url: jdbc:mysql://localhost:3306/xxxx?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: xxxx password: xxxx local: url: jdbc:oracle:thin:@localhost:1521:xxxx driver-class-name: oracle.jdbc.driver.OracleDriver username: xxxx password: xxxx

使用方式如下:只需在service添加@Ds注解指定使用数据源即可,默认数据源无需添加。;

转载地址:http://fexzb.baihongyu.com/

你可能感兴趣的文章
Leetcode C++《热题 Hot 100-26》15.三数之和
查看>>
Leetcode C++《热题 Hot 100-28》19.删除链表的倒数第N个节点
查看>>
Leetcode C++《热题 Hot 100-29》22.括号生成
查看>>
Leetcode C++《热题 Hot 100-44》102.二叉树的层次遍历
查看>>
Leetcode C++《热题 Hot 100-47》236.二叉树的最近公共祖先
查看>>
Leetcode C++《热题 Hot 100-48》406.根据身高重建队列
查看>>
《kubernetes权威指南·第四版》第二章:kubernetes安装配置指南
查看>>
Leetcode C++《热题 Hot 100-49》399.除法求值
查看>>
Leetcode C++《热题 Hot 100-51》152. 乘积最大子序列
查看>>
Leetcode C++ 《第181场周赛-1》 5364. 按既定顺序创建目标数组
查看>>
Leetcode C++ 《第181场周赛-2》 1390. 四因数
查看>>
阿里云《云原生》公开课笔记 第一章 云原生启蒙
查看>>
阿里云《云原生》公开课笔记 第二章 容器基本概念
查看>>
阿里云《云原生》公开课笔记 第三章 kubernetes核心概念
查看>>
阿里云《云原生》公开课笔记 第四章 理解Pod和容器设计模式
查看>>
阿里云《云原生》公开课笔记 第五章 应用编排与管理
查看>>
阿里云《云原生》公开课笔记 第六章 应用编排与管理:Deployment
查看>>
阿里云《云原生》公开课笔记 第七章 应用编排与管理:Job和DaemonSet
查看>>
阿里云《云原生》公开课笔记 第八章 应用配置管理
查看>>
阿里云《云原生》公开课笔记 第九章 应用存储和持久化数据卷:核心知识
查看>>