java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis-Plus3.x入门使用

MyBatis-Plus3.x版本使用入门和踩过的坑

作者:Linn-cn

Mybatis-Plus是Mybatis的增强版,他只是在Mybatis的基础上增加了功能,且并未对原有功能进行任何的改动,本文给大家说一下MyBatis-Plus3.x版本使用入门和踩过的坑,感兴趣的朋友跟随小编一起看看吧

个人认为呢,Mybatis-Plus是Mybatis的增强版,他只是在Mybatis的基础上增加了功能,且并未对原有功能进行任何的改动。可谓是非常良心的一款开源产品,今天我就来给大家简单的说一下以下几个功能和踩过的坑。

前言

对于看官网看不太懂的朋友,可以看下这个视频,2.0倍速也可哦,https://www.imooc.com/learn/1171 慕课上的mybatis-plus的视频。

2020/9/28 MybatisX 插件重新开始更新并维护

MybatisX plugin Features:

新版的MybatisX支持类似JPA的Defining Query Methods 写法

2021/1/13 Mybatis-Plus 3.X版本代码生成器模板

解决代码生成器中Mybatis的xml文件生成两遍的问题,纠正之前的错误写法。

mybatis-plus提供了非常强大的代码生成器,可以一键生成controller、service、mapper、mapping文件,省去了很多重复的动作,这里提供我简单封装之后的代码生成器的代码

package edu.changda.milk.tea.order.service;
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.HashMap;
import java.util.List;
import java.util.Scanner;
 
/**
 * @classname: CodeGenerator
 * @description: mybatisplus代码生成器
 * @author: Linn-cn@126.com
 **/
public class CodeGenerator {
 
    /**
     * 代码生成器的配置常量
     */
    private static final String outPutDir = "输出目录";
    private static final String dataName = "root";
    private static final String dataPwd = "root";
    private static final String dataUrl = "jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&characterSetResults=utf8";
    private static final String driverName = "com.mysql.cj.jdbc.Driver";
    private static final String parentPackage = "包名";
    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 projectPath = "项目主路径";
 
    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);
 
        // 去掉默认的mybatis的xml生成
        // 不设置的话会生成两份xml
        mpg.setTemplate(new TemplateConfig().setXml(null));
 
        // 自定义xml生成路径
        InjectionConfig cfg = getInjectionConfig();
        mpg.setCfg(cfg);
 
        // 策略配置
        StrategyConfig strategy = getStrategyConfig();
        mpg.setStrategy(strategy);
        mpg.execute();
    }
 
 
    /**
     * 全局配置
     *
     * @return
     */
    public static GlobalConfig getGlobalConfig() {
        return new GlobalConfig()
                .setOutputDir(projectPath)
                .setDateType(DateType.TIME_PACK)
                .setAuthor("Linn-cn")
                .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)
                // 多模块情况下,如果需要自定义生成路径的话,案例如下,配置之后,上述配置全部失效
                .setPathInfo(new HashMap<String, String>(){{
                    put(ConstVal.CONTROLLER_PATH,
                            "绝对路径");
                    put(ConstVal.ENTITY_PATH,
                            "绝对路径");
                    put(ConstVal.SERVICE_PATH,
                            "绝对路径");
                    put(ConstVal.SERVICE_IMPL_PATH,
                            "绝对路径");
                    put(ConstVal.MAPPER_PATH,
                            "绝对路径");
                }});
    }
 
    /**
     * 数据库表配置
     *
     * @return
     */
    public static StrategyConfig getStrategyConfig() {
        return new StrategyConfig()
                .setNaming(NamingStrategy.underline_to_camel)
                .setColumnNaming(NamingStrategy.underline_to_camel)
                .setEntityTableFieldAnnotationEnable(true)
                .setEntityLombokModel(true)
                .setChainModel(true)
                .setEntityBooleanColumnRemoveIsPrefix(true)
                .setInclude(scanner("表名,多个英文逗号分割").split(","))
                // 默认生成全部
                //.setExclude(null)
                .setTablePrefix("milk_tea_")
                .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<FileOutConfig> 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.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
 
}

这里要注意,如上生成的实体类是开启了lombox注解的,所以你如果用的idea的话,可能还要下载一个lombox插件和在pom.xml文件中导入lombox的包,不然木有效果,闲麻烦也可以关闭strategy.setEntityLombokModel(true);删掉即可,默认为false;

关于mybtais-plus3.x的分页

我们知道mybtais-plus3.x版本之前是自带一个内存分页(在分页的时候,是把所有的数据都查询出来,然后通过RowBounds进行在内存分页)的,也有分页插件pagination。

但mybatis-plus3.x之前的分页方法是这样的,且返回的是List<T>集合,并且内存分页并不需要配置分页插件

    /**
     * <p>
     * 根据 entity 条件,查询全部记录(并翻页)
     * </p>
     *
     * @param rowBounds 分页查询条件(可以为 RowBounds.DEFAULT)
     * @param wrapper   实体对象封装操作类(可以为 null)
     * @return List<T>
     */
    List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);

但我使用mybtais-plus3.x之后的分页是这样的

    /**
     * 翻页查询
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
     */
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);

可以看到返回的是IPage接口,其实这里返回的IPage跟传进去的第一个参数page是一样的,我们可以自己实现IPage接口也可以直接使用已经提供

但这里要注意,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() {
        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;
    }
}

上面是springboot的配置形式

3.x版本的链式查询

MyBatis-Plus还提供了一种链式查询的方式,和上面的代码效果一样。
但是这种写法偏向于炫技,可读性没有上面的代码强,大家可以根据需要自行选择方式。

List<BannerItem> bannerItems = new LambdaQueryChainWrapper<>(bannerItemMapper)
                        .eq(BannerItem::getBannerId, id)
                        .list();

如果只想查询一条记录,例如通过id查询某条记录的详情,使用.one()即可,例如

BannerItem bannerItem = new LambdaQueryChainWrapper<>(bannerItemMapper)
                        .eq(BannerItem::getId, id)
                        .one();

2019/7/31 踩坑补充,mybatis-plus有关于时间问题解决,例如查询报错问题

报错如下

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提供了自动注入,这里写几点自动填充时需要注意的点。

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<WxCommentVO> wrapper = Wrappers.lambdaQuery(wxComment);
QueryWrapper<WxCommentVO> wrapper1 = new QueryWrapper<>(wxComment);
 
// 假如你需要判断某字段值是否为1
wrapper.eq(WxComment::某字段,1);
 
必须得这样写,直接通过实体类注入是无效的

2020/5/1字段类型为 bittinyint(1) 时映射为 boolean 类型

默认mysql驱动会把tinyint(1)字段映射为boolean: 0=false,非0=true

MyBatis 是不会自动处理该映射,如果不想把tinyint(1)映射为boolean类型:

jdbc:mysql://127.0.0.1:3306/mp?tinyInt1isBit=false

2020/9/28 数据库关键字如何处理?

@TableField(value = "`status`")
private Boolean status;

2021/1/24 Map下划线自动转驼峰 

@Bean
public ConfigurationCustomizer configurationCustomizer() {
    return i -> i.setObjectWrapperFactory(new MybatisMapWrapperFactory());
}

到此这篇关于MyBatis-Plus3.x版本使用入门和踩过的坑的文章就介绍到这了,更多相关MyBatis-Plus3.x入门使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文