本文共 32481 字,大约阅读时间需要 108 分钟。
mybatis-plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
全新的 MyBatis-Plus
3.0 版本基于 JDK8,提供了 lambda
形式的调用,所以安装集成 MP3.0 要求 JDK 8+
脚本如下
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');
com.baomidou mybatis-plus-boot-starter 3.4.3.1 mysql mysql-connector-java
在 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
在 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); }}
User.java
@Datapublic class User { private Long id; private String name; private Integer age; private String email;}
UserMapper.java
public interface UserMapper extends BaseMapper{}
@SpringBootTestpublic class SampleTest { @Autowired private UserMapper userMapper; @Test public void testSelect() { System.out.println(("----- selectAll method test ------")); ListuserList = 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 } }; // 输出文件配置 ListfocList = 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.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}> {}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 {} #foreach($column in $columns) #if($column.lowerAttrName==$pk.lowerAttrName) #else #end #end
mp一共提供了8个注解,这些注解是用在Java的实体类上面的。
@TableName
@TableId
value
属性指定表的列名。另,这个注解有个重要的属性type
,用于指定主键策略 @TableField
exist
属性为false
,这样在对实体对象进行插入时,会忽略这个字段。排除非表字段也可以通过其他方式完成,如使用static
或transient
关键字,但个人觉得不是很合理,不做赘述insertStrategy
,updateStrategy
,whereStrategy
属性进行配置,可以控制在实体对象进行插入,更新,或作为WHERE条件时,对象中的字段要如何组装到SQL语句中。fill
属性指定,字段为空时会进行自动填充@Version
@EnumValue
@TableLogic
KeySequence
oracle
) InterceptorIgnore
// 插入一条记录(选择字段,策略插入)boolean save(T entity);// 插入(批量)boolean saveBatch(CollectionentityList);// 插入(批量)boolean saveBatch(Collection entityList, int batchSize);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Collection | entityList | 实体对象集合 |
int | batchSize | 插入批次数量 |
// TableId 注解存在更新记录,否插入一条记录boolean saveOrUpdate(T entity);// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法boolean saveOrUpdate(T entity, WrapperupdateWrapper);// 批量修改插入boolean saveOrUpdateBatch(Collection entityList);// 批量修改插入boolean saveOrUpdateBatch(Collection entityList, int batchSize);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Wrapper | updateWrapper | 实体对象封装操作类 UpdateWrapper |
Collection | entityList | 实体对象集合 |
int | batchSize | 插入批次数量 |
// 根据 entity 条件,删除记录boolean remove(WrapperqueryWrapper);// 根据 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列表 |
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlsetboolean update(WrapperupdateWrapper);// 根据 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)
1> sql 的 where 条件是 id 时使用;
2> 全部字段更新时使用;
update(null, wrapper)
1> sql 的 where 条件不一定是 id 时使用,例如 eg:where age > 19;
2> 部分字段更新时使用;
// 根据 ID 查询T getById(Serializable id);// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")T getOne(WrapperqueryWrapper);// 根据 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 | 转换函数 |
// 查询所有Listlist();// 查询列表List list(Wrapper queryWrapper);// 查询(根据ID 批量查询)Collection listByIds(Collection idList);// 查询(根据 columnMap 条件)Collection listByMap(Map columnMap);// 查询所有列表List
类型 | 参数名 | 描述 |
---|---|---|
Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
Collection<? extends Serializable> | idList | 主键ID列表 |
Map<?String, Object> | columnMap | 表字段 map 对象 |
Function<? super Object, V> | mapper | 转换函数 |
// 无条件分页查询IPagepage(IPage page);// 条件分页查询IPage page(IPage page, Wrapper queryWrapper);// 无条件分页查询IPage > pageMaps(IPage page);// 条件分页查询IPage > pageMaps(IPage page, Wrapper queryWrapper);
类型 | 参数名 | 描述 |
---|---|---|
IPage | page | 翻页对象 |
Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
// 查询总记录数int count();// 根据 Wrapper 条件,查询总记录数int count(WrapperqueryWrapper);
类型 | 参数名 | 描述 |
---|---|---|
Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
// 链式查询 普通QueryChainWrapperquery();// 链式查询 lambda 式。注意:不支持 KotlinLambdaQueryChainWrapper lambdaQuery(); // 示例:query().eq("column", value).one();lambdaQuery().eq(Entity::getId, value).list();
// 链式更改 普通UpdateChainWrapperupdate();// 链式更改 lambda 式。注意:不支持 Kotlin LambdaUpdateChainWrapper lambdaUpdate();// 示例:update().eq("column", value).remove();lambdaUpdate().eq(Entity::getId, value).update(entity);
说明:
Mybatis-Plus
启动时自动解析实体表关系映射转换为 Mybatis
内部对象注入容器T
为任意实体对象Serializable
为任意类型主键 Mybatis-Plus
不推荐使用复合主键约定每一张表都有自己的唯一 id
主键Wrapper
// 插入一条记录int insert(T entity);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
// 根据 entity 条件,删除记录int delete(@Param(Constants.WRAPPER) Wrapperwrapper);// 删除(根据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 对象 |
// 根据 whereWrapper 条件,更新记录int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) WrapperwhereWrapper);// 根据 ID 修改int updateById(@Param(Constants.ENTITY) T entity);
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 (set 条件值,可为 null) |
Wrapper | updateWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
// 根据 ID 查询T selectById(Serializable id);// 根据 entity 条件,查询一条记录T selectOne(@Param(Constants.WRAPPER) WrapperqueryWrapper);// 查询(根据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) |
说明:
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中的BETWEENnotBetween
like
:模糊匹配。like("name","黄")
,相当于SQL的name like '%黄%'
likeRight
:模糊匹配右半边。likeRight("name","黄")
,相当于SQL的name like '黄%'
likeLeft
:模糊匹配左半边。likeLeft("name","黄")
,相当于SQL的name like '%黄'
notLike
:notLike("name","黄")
,相当于SQL的name not like '%黄%'
isNull
isNotNull
in
and
:SQL连接符ANDor
:SQL连接符ORapply
:用于拼接SQL,该方法可用于数据库函数,并可以动态传参下面通过一些具体的案例来练习条件构造器的使用。(使用前文创建的user
表)
// 案例先展示需要完成的SQL语句,后展示Wrapper的写法// 1. 名字中包含佳,且年龄小于25// SELECT * FROM user WHERE name like '%佳%' AND age < 25QueryWrapperwrapper = 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产生如下的影响
**注意,上述的影响,只针对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的基于版本号的并发事务控制。
在读多写少的场景下,乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量。
在写多读少的场景下,悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降。
乐观锁的实现如下:
这种思想和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依赖修改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 p6spy p6spy 3.9.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 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 com.baomidou dynamic-datasource-spring-boot-starter 2.5.4
使用方式如下:只需在service添加@Ds注解指定使用数据源即可,默认数据源无需添加。;
转载地址:http://fexzb.baihongyu.com/