个人认为呢,Mybatis-Plus是Mybatis的增强版,他只是在Mybatis的基础上增加了功能,且并未对原有功能进行任何的改动。可谓是非常良心的一款开源产品,今天我就来给大家简单的说一下以下几个功能和踩过的坑。
前言
对于看官网看不太懂的朋友,可以看下这个视频,2.0倍速也可哦,https://www.imooc.com/learn/1171 慕课上的mybatis-plus的视频。
最新的3.x版本踩的几个坑 2020/3/4补充
mybatis-plus提供了非常强大的代码生成器,可以一键生成controller、service、mapper、mapping文件,省去了很多重复的动作,这里提供我简单封装之后的代码生成器的代码,更多详细配置见官网https://mp.baomidou.com/config/generator-config.html
package com.changda.flea.common.util;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @classname: CodeGenerator
* @description: mybatisplus代码生成器
* @author: 南街
**/
public class CodeGenerator {
/**
* 代码生成器的配置常量
*/
private static final String outPutDir = "/模块名/src/main/java";
private static final String dataName = "xxxxx";
private static final String dataPwd = "xxxx";
private static final String dataUrl = "jdbc:mysql://localhost:3306/dbName?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8";
private static final String driverName = "com.mysql.cj.jdbc.Driver";
private static final String parentPackage = "com.changda.flea.common";
private static final String mapperName = "dao";
private static final String serviceName = "service";
private static final String implName = "service.impl";
private static final String pojoName = "entity";
private static final String controllerName = "controller";
private static final String xmlName = "mapper";
// 当前工程路径 配合outPutDir使用,例如多模块开发 Demo/test1,Demo/test2
// projectPath拿到的是Demo路径,把outPutDir设置成/test1即可
private static final String projectPath = System.getProperty("user.dir");
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = getGlobalConfig();
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = getDataSourceConfig();
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = getPackageConfig();
mpg.setPackageInfo(pc);
InjectionConfig cfg = getInjectionConfig();
mpg.setCfg(cfg);
// 策略配置
StrategyConfig strategy = getStrategyConfig();
mpg.setStrategy(strategy);
mpg.execute();
}
/**
* 全局配置
*
* @return
*/
public static GlobalConfig getGlobalConfig() {
return new GlobalConfig()
.setOutputDir(projectPath + outPutDir)
.setDateType(DateType.ONLY_DATE)
.setAuthor("zhulin")
.setOpen(false)
.setBaseResultMap(true)
.setBaseColumnList(true)
// 覆盖生成的文件
.setFileOverride(true)
.setServiceName("%sService");
}
/**
* 数据源配置
*
* @return
*/
public static DataSourceConfig getDataSourceConfig() {
return new DataSourceConfig()
.setUrl(dataUrl)
.setDriverName(driverName)
.setUsername(dataName)
.setPassword(dataPwd);
}
/**
* 包配置
*
* @return
*/
public static PackageConfig getPackageConfig() {
return new PackageConfig()
.setParent(parentPackage)
.setMapper(mapperName)
.setEntity(pojoName)
.setService(serviceName)
.setController(controllerName)
.setServiceImpl(implName);
// .setXml(xmlName);
}
/**
* 策略配置
*
* @return
*/
public static StrategyConfig getStrategyConfig() {
return new StrategyConfig()
.setNaming(NamingStrategy.underline_to_camel)
.setColumnNaming(NamingStrategy.underline_to_camel)
.setEntityTableFieldAnnotationEnable(true)
.setEntityLombokModel(true)
.setInclude(scanner("表名,多个英文逗号分割").split(","))
//默认生成全部
//.setExclude(null)
.setTablePrefix("表前缀_")
.setControllerMappingHyphenStyle(true);
}
/**
* 自定义xml文件生成路径
* 这里注意会生成两个xml,一个是在你指定的下面,一个是在mapper包下的xml
* 暂时无法解决,因为源码中的判断,判断的是tableInfo和pathInfo的xml属性是否为null,这两个类都是默认生成属性的
* 且对if (null != injectionConfig)自定义生成的判断在默认的前面,所以会生成两遍。
* 具体可见AbstractTemplateEngine batchOutput()的方法
*
* @return
*/
public static InjectionConfig getInjectionConfig() {
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
}
};
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/模块名/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
return cfg;
}
/**
* 读取控制台内容
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入" + tip + ":");
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
}
这里要注意,如上生成的实体类是开启了lombox注解的,所以你如果用的idea的话,可能还要下载一个lombox插件和在pom.xml文件中导入lombox的包,不然木有效果,闲麻烦也可以关闭strategy.setEntityLombokModel(true);删掉即可,默认为false;
至于生成的service和mapper层的方法就不说了,直接看官方文档https://mp.baomidou.com/guide/crud-interface.html会更详细
关于mybtais-plus3.x的分页
我们知道mybtais-plus3.x版本之前是自带一个内存分页(在分页的时候,是把所有的数据都查询出来,然后通过RowBounds进行在内存分页)的,也有分页插件pagination。
但mybatis-plus3.x之前的分页方法是这样的,且返回的是List
/**
*
* 根据 entity 条件,查询全部记录(并翻页)
*
*
* @param rowBounds 分页查询条件(可以为 RowBounds.DEFAULT)
* @param wrapper 实体对象封装操作类(可以为 null)
* @return List
*/
List selectPage(RowBounds rowBounds, @Param("ew") Wrapper wrapper);
但我使用mybtais-plus3.x之后的分页是这样的
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
IPage page(IPage page, Wrapper queryWrapper);
可以看到返回的是IPage接口,其实这里返回的IPage跟传进去的第一个参数page是一样的,我们可以自己实现IPage接口也可以直接使用已经提供好的Pagehttps://mp.baomidou.com/guide/page.html
但这里要注意,3.x版本要使用分页,必须配置插件,否则会没有效果,不像2.x没配插件就是内存分页。
package com.jianghaichao.infantmom.config;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @program: infantmom
* @classname: MybatisPlusConfig
* @description: mybatis-plus配置
* @author: zhulin
* @create: 2019-05-25 09:58
**/
//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.jianghaichao.infantmom.mapper")
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
上面是springboot的配置形式
2.x的EntityWrapper到3.x也变成了QueryWrapper
还有lambdaWrapper等就不一一列举了。用到在了解就行
2019/7/31踩坑补充,mybatis-plus代码生成器生成LocalDateTime,查询报错问题
报错如下
org.springframework.dao.InvalidDataAccessApiUsageException: Error attempting to get column 'created' from result set. Cause: java.sql.SQLFeatureNotSupportedException ; null; nested exception is java.sql.SQLFeatureNotSupportedException
解决方法:
在代码生成器的全局策略中加上一行 gc.setDateType(DateType.ONLY_DATE);
设置生成的时间类型为Date即可解决问题,官网上也有这个问题的介绍
2020/1/6踩坑补充,mp的自动填充
我们都知道,例如阿里开发规范里面要求,每张表都要有create_time和update_time字段,所以我们在插入数据和修改数据的时候往往需要自己set值进去,既然是一样的操作,那么mp提供了自动注入,这里写几点自动填充时需要注意的点。
更多用法见官网https://mp.baomidou.com/guide/auto-fill-metainfo.html
1. 自动注入在每次insert或update时都会触发,所以我们要考虑性能问题,那么就会有如下优化写法。这里只举例insert的写法,具体看官网更为详细
这里需要注意的一点,这里的cretaeTime是实体类属性而不是数据库字段名
且填充有如下条件:
补充,在3.3.0版本之后官方建议使用strictInsertFill方法,这里需要注意一定区分开insert和update的区别
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
/* 上面选其一使用,下面的已过时(注意 strictInsertFill 有多个方法,详细查看源码) */
//this.setFieldValByName("operator", "Jerry", metaObject);
//this.setInsertFieldValByName("operator", "Jerry", metaObject);
2020/1/9踩坑补充,Wrapper 自定义SQL
最近使用mybatis-plus3.2.0版本碰到一个问题,从官方文档我们可看到3.0.7以后有了该功能
这里要提醒各位,如果在实例化的时候直接注入entity,你会发现无效,必须得用eq等方法
// 如下类似写法均无效
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(wxComment);
QueryWrapper wrapper1 = new QueryWrapper<>(wxComment);
// 假如你需要判断某字段值是否为1
wrapper.eq(WxComment::某字段,1);
必须得这样写,直接通过实体类注入是无效的