Mybatis Plus使用技巧和实践(Mybatis-plus官方文档的补充)

概述

上一篇介绍了Mybatis-Plus(简称MP)的使用介绍,包括了代码生成器、CRUD接口,分页插件、逻辑删除、乐观锁等。此篇是上一篇的衔接,如果没有看过请先看Mybatis Plus的使用(Mybatis增强工具)。今天不讲MP的API、CRUD接口,主要介绍下实践技巧官方文档的扩展说明,相信熟悉了这篇文章后,你将对MP的使用将会更熟练。理解有限,有错误请指正。

一、注解的使用

@TableName-数据库表名称和实体关系配置注解 -- 放在类上

属性类型必须指定默认值描述
valueString""数据库的表名
schemaString""schema(@since 3.1.1)数据库库名
keepGlobalPrefixbooleanfalse是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值)(@since 3.1.1)-一般不用
resultMapString""xml 中 resultMap 的 id
autoResultMapbooleanfalse是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建并注入)(@since 3.1.2)

@TableId-数据库主键字段 -- 放在成员变量上,使用在数据库主键值不是"id"的情况,比如主键是“user_id”,可以@TableId("user_id")

属性类型必须指定默认值描述
valueString""主键字段名
typeEnumIdType.NONE主键类型

 

@TableField -(此注解不能和@TableId同时使用)- 处理数据库字段和实体不对应情况,及如果字段是数据库关键字如‘level’,‘status’等,可以改成这样@TableField(value = "`level`")

属性类型必须指定默认值描述
valueString""字段名
elString""映射为原生 #{ ... } 逻辑,相当于写在 xml 里的 #{ ... }部分
existbooleantrue是否为数据库表字段,如果为false则为:排除非表字段,实体向数据库插入或更新时不会拼接此字段。
conditionString""字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},默认条件是根据参数做“等值”查询,一般都是等值查询,如果不是请参考
updateString""字段 update set 部分注入, 例如:update="%s+1":表示更新时会set version=version+1(该属性优先级高于 el 属性)
strategyEnumFieldStrategy.DEFAULT字段验证策略 3.1.2+使用下面3个替代
insertStrategyEnumNDEFAULT(因为DEFAULE是默认参考全局配置,全局配置默认是NOT_NULl),说明:就是在插入时是否对null字段添加到insert to table 的字段中去。举例:NOT_NULL: insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) (since v_3.1.2)
updateStrategyEnumNDEFAULT举例:IGNORED: update table_a set column=#{columnProperty} (since v_3.1.2)
whereStrategyEnumNDEFAULT举例:NOT_EMPTY: where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> (since v_3.1.2)
fillEnumFieldFill.DEFAULT字段自动填充策略
selectbooleantrue是否进行 select 查询
keepGlobalFormatbooleanfalse是否保持使用全局的 format 进行处理(@since 3.1.1)

这里着重说明一下fill属性的使用(自动填充-默认不填充) 。自动填充就是对字段在insert或update中不用自己设置值,MP自动设置其默认值,不过默认值需要自己设置,常常用在createTimeupdateTime的字段上,createTime一般使用INSERT策略,updateTime一般使用INSERT_UPDATE策略。使用分两步。

第一步:在实体字段上添加@TableFiedl(fill = FiedlFill.填充策略),填充策略有4种如下:

描述
DEFAULT默认不处理
INSERT插入时填充字段
UPDATE更新时填充字段
INSERT_UPDATE插入和更新时填充字段
@TableField(fill = FieldFill.INSERT) // 创建时间只在添加是填充
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE) // 更新时间在添加和更新时都填充
private Date updateTime;

第二步:自定义类实现MetaObjectHandler类即可,重写填充策略的两个方法用于insertupdate,关键的是setFieldValByName()方法,第一个参数是实体的属性,第二个参数是设置的默认值

@Component
public class MetaObjectHandlerConfig implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        setFieldValByName("createTime", new Date(), metaObject);
        setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(), metaObject);
    }
}

这样在实体插入或更新的时候就不用每次都设置像createTime这种字段的值,委托给MP来自动填充了,是不是很厉害。

二、使用实践

2.1 强大的条件构造器

2.1.1 条件构造器中condition的使用

条件构造器所有条件有个重载的方法,第一个入参boolean condition(表示该条件是否加入最后生成的sql中,默认true)。

场景:Controller中由前端传递过来的查询条件,比如查询所有列表信息,如果前端传递过来个name值,表示通过name进行筛选,当name传非空值时才添加查询条件,正常情况下我们可能会这样使用:

@GetMapping("/list")
public Object index(@RequestParam(value = "name", required = false) String name){
    QueryWrapper<Customer> qw = new QueryWrapper<>();
    if (StringUtils.isNoneBlank(name)) {
        qw.eq("name", name);
    }
    return customerService.list(qw);
}

如果筛选的条件比较多,那代码就不好看了。MP提供了查询时根据条件来判断,决定是否加入查询条件语句的方法,就说是condition为true则加入后面的条件,false则不会加入条件

public Object index(@RequestParam(value = "name", required = false) String name){
    QueryWrapper<Customer> qw = new QueryWrapper<>();
    qw.eq(StringUtils.isNoneBlank(name), "name", name);
    return customerService.list(qw);
}

2.1.2 仅查询数据库部分字段

当我们使用条件构造器查询数据库时,默认查询所有实体字段,如果只需要id和name两个字段的值,使用select(String column...)方法可以完成,执行查询的sql如(select id, name from user where xxxx)。
注意:返回的值依然是实体对象,只是除了id和name其他都是null

QueryWrapper<Customer> qw = new QueryWrapper<>();
qw.select("id", "name");
List<Customer> list = customerService.list(qw);

一般条件构造器添加了select字段后,使用普通的list()方法查询出实体就没有必要了,因为带着一堆null的值很不爽,实践是查询方法使用listMap()来查询,这样会根据查询语句中select的具体字段来赋值给map,如下示例中map只有id和name两个key了。

QueryWrapper<Customer> qw = new QueryWrapper<>();
qw.select("id", "name");
List<Map<String, Object>> list = customerService.listMaps(qw);
return list;

2.1.3 告别“条件构造器”使用"Lambda条件构造器"

使用LambdaQueryWrapper条件构造器和QueryWrapper构造器相比方法几乎相同,唯一不同的就是传递条件的表字段不是通过手敲的方式而是使用lambda的形式来完成获取表字段。这样就不用担心将 “name”写成“nema”了,例子中使用方法引用来获取name的字段。

三种创建LambdaQueryWrapper方式,个人推荐第二种,只在普通QueryWrapper构造器前面加上Lambda即可完成构造:

LambdaQueryWrapper<Customer> lambda = new QueryWrapper<Customer>().lambda();
LambdaQueryWrapper<Customer> lambda2 = new LambdaQueryWrapper<>();
LambdaQueryWrapper<Customer> lambda3 = Wrappers.lambdaQuery();
LambdaQueryWrapper<Customer> lqw = new LambdaQueryWrapper<>();
lqw.eq(Customer::getName, "Tom"); // 表字段使用lambda的方法引用来获取
List<Map<String, Object>> list = customerService.listMaps(lqw);

当然LambdaQueryWrapper的select()中的查询字段也需要使用lambda来获取字段名称

2.2 万剑归宗的查询LambdaQueryChainWrapper(链式条件构造器查询)

前面的查询都是创建一个条件构造器,再通过mapper或service以条件构造器为参数进行查询。LambdaQueryChainWrapper将前面的两句合二为一,一句来完成带条件的查询。使用起来就是:MP简直想起飞。

@Autowired
private CustomerMapper customerMapper;

@Test
public void contextLoads() {
    List<Customer> list = new LambdaQueryChainWrapper<Customer>(customerMapper)
            .select(Customer::getId, Customer::getName)
            .like(Customer::getName, "王").list();
    System.out.println(list);
}

创建LambdaQueryChainWrapper对象需要传递mapper对象,虽然酒好不能贪杯,请酌情使用链式查询。还有个缺点链式查询只有list()和one()两个方法获取数据,返回的都是实体对象或实体对象的集合,没有对应的Map()查询方法。

2.3 自定义SQL

项目中自己写sql也是在所难免的,可这么强大的条件构造器我们也想使用怎么办呢,别着急MP也帮我们想到了。两处注意事项:

第一处:在自己的mapper接口文件中添加自己的查询方法,只需要传递@Param(Constants.WRAPPER) Wrapper wrapper条件构造器参数即可,MP会自动赋值给wrapper。

List<MysqlData> getMyself(@Param(Constants.WRAPPER) Wrapper wrapper);

第二处:在xml文件中,使用(固定格式)${ew.customSqlSegment}作为参数放在表名的后面就可以了。

<select id="getMyself" resultType="MysqlData">
	SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>

三、两款用于开发环境的sql打印、分析功能的插件

3.1 执行 SQL 分析打印插件(第三方插件)

该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长,对于开发时研究sql语句很有用途。

3.1.1 添加依赖

<dependency>
    <groupId>p6spy</groupId>
    <artifactId>p6spy</artifactId>
    <version>3.8.1</version>
</dependency>

3.1.2 修改数据库连接配置为p6spy的配置

只需要改动driverurl即可,如下配置

spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
spring.datasource.url=jdbc:p6spy:mysql://127.0.0.1:3306/database?useUnicode=true&characterEncoding=utf-8

3.1.3 添加p6spy的配置文件spy.properties

module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,batch,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2

3.1.4 运行sql语句,控制台查看,则会有sql运行语句消耗时间信息,如下图:sql消耗了37ms

3.2 MP自带sql性能分析插件

MP自带了sql性能分析拦截器,用于输出每条 SQL 语句及其执行时间,只需要定义一个bean就可以用来做性能分析。如果对 SQL 的打印效果要求较高,请使用上面提到的第三方扩展 执行 SQL 分析打印 功能。

3.2.1 配置性能分析

  • spring boot方式配置(推荐)
  • @Bean
    @Profile({"dev","test"})// 设置 dev test 环境开启
    public PerformanceInterceptor performanceInterceptor() {
       return new PerformanceInterceptor();
    }
  • spring mvc方式配置
<plugins>
    <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
        <property name="maxTime" value="100" />
        <!--SQL是否格式化 默认false-->
        <property name="format" value="true" />
    </plugin>
</plugins>

3.2.1 运行sql语句,控制台查看,则会有sql运行语句消耗时间信息,如下图:sql消耗了82ms

四、总结

通过这两篇文章,已经将MP的大部分功能都说明的差不多了,真的感谢MP的开发者们,写出这么好的东西,核心功能和各种插件都将DAO层的东西写尽了,再结合Spring Boot的强大,真的是开箱就开枪,让我们开发者可以开开心心的写业务。学会了MP后,感觉对后台业务操作的简化程度有了质的提高。

花了一天的时间边测试,边写文章,如果有错误的地方,请指正。希望对你理解Mybatis-Plus有更深的认识。

(完) 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值