一、简介

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

Mybatis需要实现的:Mapper接口+SQL映射文件
Mybatis-Plus需要实现的:Mapper接口

前置技能:Maven + Spring + SpringMVC + Mybatis 打地基
金手指:不需要那么多花里胡哨,SpringBoot引入后自动配置底层

预加载:MP启动时会指定加载来自MP提供的BaseMapper接口中常见的CRUD语句,并且将它们封装到了MappedStatement对象(也就是<select>等标签)中。


二、配置

添加pom.xml依赖

<!--MP 金手指已就绪-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
</dependency>

SpringBoot中只需要在启动类中加入一句话

@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

yml中配置MP相关:

mybatis-plus:
  configuration:
    # 不用配置log4j.properties就可以直接打印sql语句
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 如果JDBC无法识别类型则默认为NULL
    jdbc-type-for-null: null

剩下的只需要完成Controller层、entity层(推荐和Lombok一起食用),以及一个空的Mapper接口去继承BaseMapper<T>。更偷懒的做法是通过EasyCode或者Mybatis的逆向工程先生成以上三个再去修改。


三、示例Demo

entity

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Student {
    @TableId(value = "sno", type = IdType.AUTO)
    private Integer sno;
    private String sname;
    private Boolean ssex;
    private Integer shigh;
    private Integer sweigh;
    private Integer scharacter;
}

mapper

public interface StudentMapper extends BaseMapper<Student> {

}

controller

@RestController
@RequestMapping("student")
public class StudentController {

    @Resource
    private StudentMapper studentMapper;

    @GetMapping("selectAll")
    public List<Student> selectAll(){
        List<Student> studentList = studentMapper.selectList(null);
        return studentList;
    }

    @GetMapping("addStudent")
    public void addStudent(){
        Student student = Student.builder().sname("xxx").ssex(true).shigh(170).sweigh(80).build();
        this.studentMapper.insert(student);
        System.out.println(student);
    }

    @GetMapping("queryStudent")
    public void queryStudent(){
        QueryWrapper<Student> wrapper = new QueryWrapper<>();
        wrapper.between("sno",4,10).ge("shigh",170).le("shigh",180);
        List<Student> list = studentMapper.selectList(wrapper);
        System.out.println(list);
    }

    @GetMapping("updateStudent")
    public void updataStudent(){
        UpdateWrapper<Student> wrapper = new UpdateWrapper<>();
        Student student = Student.builder().sname("zxs").scharacter(172).build();
        wrapper.eq("sno",1);
        studentMapper.update(student,wrapper);
        System.out.println(student);
    }

    @GetMapping("delStudent")
    public void delStudent(){
        UpdateWrapper<Student> wrapper = new UpdateWrapper<>();
        wrapper.eq("sno",10);
        studentMapper.delete(wrapper);
    }
}

四、AR编程

不是桐老爷子的那个AR,而是ActiveRecode译为活动记录。直接将CRUD操作在实体类的实例化对象上,而不是原先的Mapper实例化对象。
但是同样的,仍需要创建XxxMapper去继承BaseMapper,并且在Controller层照样需要引入XXXMapper。而在Entity层需要继承一个Model方法就可以了。

和之前有一点区别就是如果要用AR编程,那么你要写很多个构造函数以满足不同的需要。或者直接在不需要的属性传NULL。

@Data
@TableName(value = "game")
public class Game extends Model {
    @TableId(value = "g_id", type = IdType.AUTO)
    private Integer gId;
    private String gName;
    private String gMsg;

    public Game(){}

    public Game(Integer gId, String gName, String gMsg) {
        this.gId = gId;
        this.gName = gName;
        this.gMsg = gMsg;
    }

    public Game(Integer gId) {
        this.gId = gId;
    }

    public Game(String gName, String gMsg) {
        this.gName = gName;
        this.gMsg = gMsg;
    }
}
@RestController
@RequestMapping("game")
public class GameController {
    @Resource
    private GameMapper gameMapper;

    @GetMapping("addGame")
    public void selectAll() {
        Game game = new Game("江南百景图","小破图");
        game.insert();
        System.out.println(game);
    }

    @GetMapping("delGame")
    public void delGame() {
        Game game = new Game();
        game.deleteById(4);
    }

    @GetMapping("updateGame")
    public void updateGame() {
        Game game = new Game(1, "王者荣耀", "王者农药");
        game.updateById();

        Game game1 = new Game("江南百景图","养老");
        UpdateWrapper<Game> wrapper = new UpdateWrapper<>();
        wrapper.eq("g_name","江南百景图");
        game1.update(wrapper);
    }

    @GetMapping("selectGame")
    public void selectGame() {
        Game game = new Game();
        List<Game> games = game.selectAll();
        System.out.println(games);
    }
}

五、Mybatis-Plus分页

MP相比于之前用到的PageHelper配置起来更加简单,内容和用法倒是差不多。
根据官方文档是在mapper包下定义一个MybatisPlusConfig,但是我个人觉得还是新建一个config包来放会更好一点。

@Configuration
@MapperScan("springboot.mybatis_plus.demo.mapper")
public class MybatisPlusConfig {

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}
@RestController
@RequestMapping("game")
public class GameController {
    @GetMapping("selectGameByPage")
    public Object selectGameByPage(Model model){
        int page = 1;
        int pageSize = 2;
        Game game = new Game();
        IPage<Game> iPage = new Page<>(page,pageSize);
        IPage<Game> gamePage = game.selectPage(iPage, null);
        return gamePage;
    }
}

六、逆向工程

AutoGenerateCodeUtils具体可以参考官方文档

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
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 com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.ArrayList;
import java.util.List;

/**
 * 根据实体类自动生成Dao和Service的模板代码
 * 需遵守相关约定,如实体类必须放在model包下等
 *
 * @author zhangyongqi
 * @since 1.0
 */
public class AutoGenerateCodeUtils {
    /**
     * @param packagePath 包路径,根据项目修改
     * @param dbUserName
     * @param dbPassword
     * @param author
     * @param dbUrl
     * @param clazz
     * @param tableNames
     * @throws Exception
     */
    public static void generate(String packagePath, String dbUserName, String dbPassword, String author, String dbUrl, Class<?> clazz, String... tableNames) throws Exception {

        // 文件路径
        String basePath = clazz.getResource("/").getPath();
        String projectPath = basePath.substring(0, basePath.indexOf("target/")) + "src/main/java/";
        String xmlPath = basePath.substring(0, basePath.indexOf("target/")) + "src/main/resources/";

        AutoGenerator mpg = new AutoGenerator();

        mpg.setTemplateEngine(new FreemarkerTemplateEngine());

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        String templatePath = "/templates/mapper.xml.ftl";

        // 配置自定义输出模板
        //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        // templateConfig.setEntity("templates/entity2.java");
        // templateConfig.setService();
        templateConfig.setController(null);
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);


        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };


        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return xmlPath + "mapper/default"
                    + "/" + tableInfo.getXmlName() + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);


        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir(projectPath);
        gc.setFileOverride(true);
        gc.setActiveRecord(false);// 不需要ActiveRecord特性的请改为false
        gc.setEnableCache(false);// XML 二级缓存
        gc.setBaseResultMap(false);// XML ResultMap
        gc.setBaseColumnList(false);// XML columList
        gc.setAuthor(author);// 作者

        // 自定义文件命名,注意 %s 会自动填充表实体属性!
//       gc.setControllerName("%sController");
        gc.setEntityName("%sEntity");
        gc.setServiceName("%sDao");
        gc.setServiceImplName("%sDaoImpl");
        gc.setMapperName("%sMapper");
        gc.setXmlName("%sMapper");
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(DbType.MYSQL);
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername(dbUserName);
        dsc.setPassword(dbPassword);
        dsc.setUrl(dbUrl);

      /* dsc.setTypeConvert(new MySqlTypeConvert(){
            // 自定义数据库表字段类型转换【可选】
            public DbColumnType processTypeConvert(String fieldType) {
                System.out.println("转换类型:" + fieldType);
                // 注意!!processTypeConvert 存在默认类型转换,如果不是你要的效果请自定义返回、非如下直接返回。
                return super.processTypeConvert(fieldType);
            }
       });*/

        mpg.setDataSource(dsc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setTablePrefix(new String[]{"t_"});// 此处可以修改为您的表前缀
        strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setInclude(tableNames); // 需要生成的表

        strategy.setSuperEntityClass("cn.taqu.core.orm.mp.base.BaseEntity");
        strategy.setSuperEntityColumns(new String[]{"id"});
        strategy.setSuperServiceClass(null);
        strategy.setSuperServiceImplClass(null);
        strategy.setSuperMapperClass(null);

        mpg.setStrategy(strategy);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(packagePath);
//       pc.setController("controller");
        pc.setService("dao");
        pc.setServiceImpl("dao.impl");
        pc.setMapper("mapper");
        pc.setEntity("model");
        pc.setXml("xml");
        mpg.setPackageInfo(pc);

        // 执行生成
        mpg.execute();

    }


}
public class CreateMybatisPulsCodeByTableName {

    public static void main(String[] args) {
        try {
            AutoGenerateCodeUtils.generate("generatePath", "xxx",
                    "xxx", "name", "jdbc:mysql://xxxx:xxxx/projectName",
                    Class.class );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

问题记录

int(11)默认转成Integer类型,虽然数据库中秒时间戳10位已经够用了。但是Java获取时间戳System.currentTimeMillis() / 1000推荐就是用long来接收。而且公司业务中对于时间字段的实体类定义也都是Long类型。所以需要自定义转换类型:

/**
 * 自定义类型转换
 */
static class MySqlTypeConvertCustom extends MySqlTypeConvert implements ITypeConvert{
    @Override
    public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
        String t = fieldType.toLowerCase();
        if (t.contains("int(11)")) {
            return DbColumnType.LONG;
        }
        return super.processTypeConvert(globalConfig, fieldType);
    }
}

七、一些细节

(1)insert主键回写和自增问题

【回写】:首先要想主键回写,必须在实体类的主键属性上打@TableId注解。对于没有用Lombok的@Builder来说只要使用@TableId的value或type或两者都行。但是有@Builder存在则不能只单独出现value。

【自增】:如果有用Lombok的@Builder或者有用驼峰命名(JDBC=sName MYSQL=s_name),则自动会自增。否则(如:sname)无法自增。

(2)条件查询问题

还是针对@Builder的讨论,若是加了则无需在写其他构造函数,但是在和@Data连用时可能导致无参构造失效,需要再手动打上@NoArgsConstructor@AllArgsConstructor两个都要;若是不加的话就得手动写上一个无参构造的函数。
否则会出现报错Cannot determine value type from string 'xxx'

(3)主键类型

MP将主键设置成了Serializable类型,是为了方便直接接受八大数据类型+String。(也就是说也可以是泛型T、对象Object)

Last modification:March 30th, 2021 at 03:47 pm
喵ฅฅ