Loading... ## 一、简介 MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上**只做增强不做改变**,为简化开发、提高效率而生。 <!--more--> Mybatis需要实现的:Mapper接口+SQL映射文件 Mybatis-Plus需要实现的:Mapper接口 **前置技能:**`Maven + Spring + SpringMVC + Mybatis` 打地基 **金手指:**不需要那么多花里胡哨,`SpringBoot`引入后自动配置底层 **预加载:**MP启动时会指定加载来自MP提供的BaseMapper接口中常见的CRUD语句,并且将它们封装到了MappedStatement对象(也就是<select>等标签)中。 --- ## 二、配置 添加pom.xml依赖 ```xml <!--MP 金手指已就绪--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> ``` SpringBoot中只需要在启动类中加入一句话 ```Java @SpringBootApplication @MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } ``` yml中配置MP相关: ```XML 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 ```Java @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 ```Java public interface StudentMapper extends BaseMapper<Student> { } ``` controller ```Java @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); } } ``` 上面的演示只是最基础的用法,实际开发中如下: Service继承IService,Mapper继承BaseMapper。 Controller只负责调用Service层和一些简单的数据处理,业务处理全部丢到Service层,而复杂/复用的SQL交给Mapper层,多表查询之类的则是Service采用MP的写法+xml动态SQL。 这部分是关键,根据[官方文档](https://baomidou.com/guide/)学习就够了。 --- ## 四、AR编程 不是桐老爷子的那个AR,而是 `ActiveRecode`译为活动记录。直接将CRUD操作在实体类的实例化对象上,而不是原先的Mapper实例化对象。 但是同样的,仍需要创建XxxMapper去继承BaseMapper,并且在Controller层照样需要引入XXXMapper。而在Entity层需要继承一个Model方法就可以了。 和之前有一点区别就是如果要用AR编程,那么你要写很多个构造函数以满足不同的需要。或者直接在不需要的属性传NULL。 **实际开发中基本不这么写,了解即可。** ```Java @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; } } ``` ```Java @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包来放会更好一点。 ```Java @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); } } ``` ```Java @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具体可以参考[官方文档](https://baomidou.com/guide/generator.html) ```java 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(); } } ``` ```java 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`类型。所以需要自定义转换类型: ```java /** * 自定义类型转换 */ 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:August 14, 2022 © Allow specification reprint Like 0 喵ฅฅ