還在手寫CRUD代碼?這款開源框架助你解放雙手!
相信很多朋友在項(xiàng)目中使用的ORM框架都是MyBatis,如果單用MyBatis來操作數(shù)據(jù)庫的話,需要手寫很多單表查詢的SQL實(shí)現(xiàn)。這時(shí)候我們往往會(huì)選擇一個(gè)增強(qiáng)工具來實(shí)現(xiàn)這些單表CRUD操作,這里推薦一款好用的工具M(jìn)yBatis-Plus!
MyBatis-Plus簡(jiǎn)介
MyBatis-Plus(簡(jiǎn)稱 MP)是一個(gè) MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡(jiǎn)化開發(fā)、提高效率而生。MyBatis-Plus 提供了代碼生成器,可以一鍵生成controller、service、mapper、model、mapper.xml代碼,同時(shí)提供了豐富的CRUD操作方法,助我們解放雙手!
MyBatis-Plus集成
首先我們需要在SpringBoot項(xiàng)目中集成MyBatis-Plus,之后我們?cè)僭敿?xì)介紹它的使用方法!
- 在
pom.xml中添加相關(guān)依賴,主要是MyBatis-Plus、MyBatis-Plus Generator和Velocity模板引擎;
<dependencies>
????
????<dependency>
????????<groupId>com.baomidougroupId>
????????<artifactId>mybatis-plus-boot-starterartifactId>
????????<version>3.3.2version>
????dependency>
????
????<dependency>
????????<groupId>com.baomidougroupId>
????????<artifactId>mybatis-plus-generatorartifactId>
????????<version>3.3.2version>
????dependency>
????
????<dependency>
????????<groupId>org.apache.velocitygroupId>
????????<artifactId>velocity-engine-coreartifactId>
????????<version>2.2version>
????dependency>
dependencies>
- 在SpringBoot配置文件
application.yml添加如下配置,配置好數(shù)據(jù)源和MyBatis-Plus;
spring:
??datasource:
????url:?jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
????username:?root
????password:?root
mybatis-plus:
??mapper-locations:?classpath:/mapper/**/*.xml?#指定mapper.xml路徑
??global-config:
????db-config:
??????id-type:?auto?#全局默認(rèn)主鍵類型設(shè)置為自增
??configuration:
????auto-mapping-behavior:?partial?#只對(duì)非嵌套的?resultMap?進(jìn)行自動(dòng)映射
????map-underscore-to-camel-case:?true?#開啟自動(dòng)駝峰命名規(guī)則映射
- 添加MyBatis-Plus的Java配置,使用
@MapperScan注解配置好需要掃碼的Mapper接口路徑,MyBatis-Plus自帶分頁功能,需要配置好分頁插件PaginationInterceptor。
/**
?*?MyBatis配置類
?*?Created?by?macro?on?2019/4/8.
?*/
@Configuration
@MapperScan("com.macro.mall.tiny.modules.*.mapper")
public?class?MyBatisConfig?{
????@Bean
????public?PaginationInterceptor?paginationInterceptor()?{
????????PaginationInterceptor?paginationInterceptor?=?new?PaginationInterceptor();
????????paginationInterceptor.setCountSqlParser(new?JsqlParserCountOptimize(true));
????????return?paginationInterceptor;
????}
}
代碼生成器
MyBatis-Plus 提供了代碼生成器,可以一鍵生成controller、service、mapper、model、mapper.xml代碼,非常方便!
- 首先我們創(chuàng)建代碼生成器類
MyBatisPlusGenerator,直接運(yùn)行其main方法即可生成相關(guān)代碼;
/**
?*?MyBatisPlus代碼生成器
?*?Created?by?macro?on?2020/8/20.
?*/
public?class?MyBatisPlusGenerator?{
????public?static?void?main(String[]?args)?{
????????String?projectPath?=?System.getProperty("user.dir")?+?"/mall-tiny-plus";
????????String?moduleName?=?scanner("模塊名");
????????String[]?tableNames?=?scanner("表名,多個(gè)英文逗號(hào)分割").split(",");
????????//?代碼生成器
????????AutoGenerator?autoGenerator?=?new?AutoGenerator();
????????autoGenerator.setGlobalConfig(initGlobalConfig(projectPath));
????????autoGenerator.setDataSource(initDataSourceConfig());
????????autoGenerator.setPackageInfo(initPackageConfig(moduleName));
????????autoGenerator.setCfg(initInjectionConfig(projectPath,?moduleName));
????????autoGenerator.setTemplate(initTemplateConfig());
????????autoGenerator.setStrategy(initStrategyConfig(tableNames));
????????autoGenerator.setTemplateEngine(new?VelocityTemplateEngine());
????????autoGenerator.execute();
????}
????/**
?????*?讀取控制臺(tái)內(nèi)容信息
?????*/
????private?static?String?scanner(String?tip)?{
????????Scanner?scanner?=?new?Scanner(System.in);
????????System.out.println(("請(qǐng)輸入"?+?tip?+?":"));
????????if?(scanner.hasNext())?{
????????????String?next?=?scanner.next();
????????????if?(StrUtil.isNotEmpty(next))?{
????????????????return?next;
????????????}
????????}
????????throw?new?MybatisPlusException("請(qǐng)輸入正確的"?+?tip?+?"!");
????}
????/**
?????*?初始化全局配置
?????*/
????private?static?GlobalConfig?initGlobalConfig(String?projectPath)?{
????????GlobalConfig?globalConfig?=?new?GlobalConfig();
????????globalConfig.setOutputDir(projectPath?+?"/src/main/java");
????????globalConfig.setAuthor("macro");
????????globalConfig.setOpen(false);
????????globalConfig.setSwagger2(true);
????????globalConfig.setBaseResultMap(true);
????????globalConfig.setFileOverride(true);
????????globalConfig.setDateType(DateType.ONLY_DATE);
????????globalConfig.setEntityName("%s");
????????globalConfig.setMapperName("%sMapper");
????????globalConfig.setXmlName("%sMapper");
????????globalConfig.setServiceName("%sService");
????????globalConfig.setServiceImplName("%sServiceImpl");
????????globalConfig.setControllerName("%sController");
????????return?globalConfig;
????}
????/**
?????*?初始化數(shù)據(jù)源配置
?????*/
????private?static?DataSourceConfig?initDataSourceConfig()?{
????????Props?props?=?new?Props("generator.properties");
????????DataSourceConfig?dataSourceConfig?=?new?DataSourceConfig();
????????dataSourceConfig.setUrl(props.getStr("dataSource.url"));
????????dataSourceConfig.setDriverName(props.getStr("dataSource.driverName"));
????????dataSourceConfig.setUsername(props.getStr("dataSource.username"));
????????dataSourceConfig.setPassword(props.getStr("dataSource.password"));
????????return?dataSourceConfig;
????}
????/**
?????*?初始化包配置
?????*/
????private?static?PackageConfig?initPackageConfig(String?moduleName)?{
????????Props?props?=?new?Props("generator.properties");
????????PackageConfig?packageConfig?=?new?PackageConfig();
????????packageConfig.setModuleName(moduleName);
????????packageConfig.setParent(props.getStr("package.base"));
????????packageConfig.setEntity("model");
????????return?packageConfig;
????}
????/**
?????*?初始化模板配置
?????*/
????private?static?TemplateConfig?initTemplateConfig()?{
????????TemplateConfig?templateConfig?=?new?TemplateConfig();
????????//可以對(duì)controller、service、entity模板進(jìn)行配置
????????//mapper.xml模板需單獨(dú)配置
????????templateConfig.setXml(null);
????????return?templateConfig;
????}
????/**
?????*?初始化策略配置
?????*/
????private?static?StrategyConfig?initStrategyConfig(String[]?tableNames)?{
????????StrategyConfig?strategyConfig?=?new?StrategyConfig();
????????strategyConfig.setNaming(NamingStrategy.underline_to_camel);
????????strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
????????strategyConfig.setEntityLombokModel(true);
????????strategyConfig.setRestControllerStyle(true);
????????//當(dāng)表名中帶*號(hào)時(shí)可以啟用通配符模式
????????if?(tableNames.length?==?1?&&?tableNames[0].contains("*"))?{
????????????String[]?likeStr?=?tableNames[0].split("_");
????????????String?likePrefix?=?likeStr[0]?+?"_";
????????????strategyConfig.setLikeTable(new?LikeTable(likePrefix));
????????}?else?{
????????????strategyConfig.setInclude(tableNames);
????????}
????????return?strategyConfig;
????}
????/**
?????*?初始化自定義配置
?????*/
????private?static?InjectionConfig?initInjectionConfig(String?projectPath,?String?moduleName)?{
????????//?自定義配置
????????InjectionConfig?injectionConfig?=?new?InjectionConfig()?{
????????????@Override
????????????public?void?initMap()?{
????????????????//?可用于自定義屬性
????????????}
????????};
????????//?模板引擎是Velocity
????????String?templatePath?=?"/templates/mapper.xml.vm";
????????//?自定義輸出配置
????????List?focList?=?new?ArrayList<>();
????????//?自定義配置會(huì)被優(yōu)先輸出
????????focList.add(new?FileOutConfig(templatePath)?{
????????????@Override
????????????public?String?outputFile(TableInfo?tableInfo)?{
????????????????//?自定義輸出文件名?,?如果你 Entity 設(shè)置了前后綴、此處注意 xml 的名稱會(huì)跟著發(fā)生變化!!
????????????????return?projectPath?+?"/src/main/resources/mapper/"?+?moduleName
????????????????????????+?"/"?+?tableInfo.getEntityName()?+?"Mapper"?+?StringPool.DOT_XML;
????????????}
????????});
????????injectionConfig.setFileOutConfigList(focList);
????????return?injectionConfig;
????}
}
- 然后在
resources目錄下添加配置文件generator.properties,添加代碼生成器的數(shù)據(jù)源配置及存放業(yè)務(wù)代碼的基礎(chǔ)包名稱;
dataSource.url=jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
dataSource.driverName=com.mysql.cj.jdbc.Driver
dataSource.username=root
dataSource.password=root
package.base=com.macro.mall.tiny.modules
- 細(xì)心的朋友可以發(fā)現(xiàn)
MyBatisPlusGenerator中很多配置代碼都沒添加注釋,其實(shí)MyBatis-Plus源碼中的中文注釋非常完善,只需查看源碼即可,這里摘抄一段DataSourceConfig中的源碼;
/**
?*?數(shù)據(jù)庫配置
?*
?*?@author?YangHu,?hcl
?*?@since?2016/8/30
?*/
@Data
@Accessors(chain?=?true)
public?class?DataSourceConfig?{
????/**
?????*?數(shù)據(jù)庫信息查詢
?????*/
????private?IDbQuery?dbQuery;
????/**
?????*?數(shù)據(jù)庫類型
?????*/
????private?DbType?dbType;
????/**
?????*?PostgreSQL?schemaName
?????*/
????private?String?schemaName;
????/**
?????*?類型轉(zhuǎn)換
?????*/
????private?ITypeConvert?typeConvert;
????/**
?????*?關(guān)鍵字處理器
?????*?@since?3.3.2
?????*/
????private?IKeyWordsHandler?keyWordsHandler;
????/**
?????*?驅(qū)動(dòng)連接的URL
?????*/
????private?String?url;
????/**
?????*?驅(qū)動(dòng)名稱
?????*/
????private?String?driverName;
????/**
?????*?數(shù)據(jù)庫連接用戶名
?????*/
????private?String?username;
????/**
?????*?數(shù)據(jù)庫連接密碼
?????*/
????private?String?password;
????
????//省略若干代碼......
}
- 代碼生成器支持兩種模式,一種生成單表的代碼,比如只生成
pms_brand表代碼可以先輸入pms,后輸入pms_brand;

- 生成單表代碼結(jié)構(gòu)一覽;

- 另一種直接生成整個(gè)模塊的代碼,需要帶通配符
*,比如生成ums模塊代碼可以先輸入ums,后輸入ums_*;

- 生成整個(gè)模塊代碼結(jié)構(gòu)一覽。

自定義生成模板
MyBatis-Plus 使用模板引擎來生成代碼,支持 Velocity(默認(rèn))、Freemarker、Beetl模板引擎,這里以Velocity為了來介紹下如何自定義生成模板。
- 首先我們可以從 MyBatis-Plus Generator依賴包的源碼中找到默認(rèn)模板,拷貝到項(xiàng)目的
resources/templates目錄下;

- 在
MyBatisPlusGenerator類中對(duì)TemplateConfig進(jìn)行配置,配置好各個(gè)模板的路徑;
/**
?*?MyBatisPlus代碼生成器
?*?Created?by?macro?on?2020/8/20.
?*/
public?class?MyBatisPlusGenerator?{
????
????/**
?????*?初始化模板配置
?????*/
????private?static?TemplateConfig?initTemplateConfig()?{
????????TemplateConfig?templateConfig?=?new?TemplateConfig();
????????//可以對(duì)controller、service、entity模板進(jìn)行配置
????????templateConfig.setEntity("templates/entity.java");
????????templateConfig.setMapper("templates/mapper.java");
????????templateConfig.setController("templates/controller.java");
????????templateConfig.setService("templates/service.java");
????????templateConfig.setServiceImpl("templates/serviceImpl.java");
????????//mapper.xml模板需單獨(dú)配置
????????templateConfig.setXml(null);
????????return?templateConfig;
????}
}
- 對(duì)模板進(jìn)行定制,在定制過程中我們可以發(fā)現(xiàn)很多內(nèi)置變量,用于輸出到模板中去,這里以
service.java.vm模板為例子,比如package、table這些變量;
package?${package.Service};
import?${package.Entity}.${entity};
import?${superServiceClassPackage};
/**
?*?
?*?$!{table.comment}?服務(wù)類
?*?
?*
?*?@author?${author}
?*?@since?${date}
?*/
#if(${kotlin})
interface?${table.serviceName}?:?${superServiceClass}<${entity}>
#else
public?interface?${table.serviceName}?extends?${superServiceClass}<${entity}>?{
}
#end
- 搞懂這些變量從哪來的,對(duì)我們定制模板很有幫助,其實(shí)這些變量都來著于
AbstractTemplateEngine的getObjectMap方法,具體變量作用可以參考源碼。
/**
?*?模板引擎抽象類
?*
?*?@author?hubin
?*?@since?2018-01-10
?*/
public?abstract?class?AbstractTemplateEngine?{
????????/**
?????????*?渲染對(duì)象?MAP?信息
?????????*
?????????*?@param?tableInfo?表信息對(duì)象
?????????*?@return?ignore
?????????*/
????????public?Map?getObjectMap(TableInfo?tableInfo)? {
????????????Map?objectMap?=?new?HashMap<>(30);
????????????ConfigBuilder?config?=?getConfigBuilder();
????????????if?(config.getStrategyConfig().isControllerMappingHyphenStyle())?{
????????????????objectMap.put("controllerMappingHyphenStyle",?config.getStrategyConfig().isControllerMappingHyphenStyle());
????????????????objectMap.put("controllerMappingHyphen",?StringUtils.camelToHyphen(tableInfo.getEntityPath()));
????????????}
????????????objectMap.put("restControllerStyle",?config.getStrategyConfig().isRestControllerStyle());
????????????objectMap.put("config",?config);
????????????objectMap.put("package",?config.getPackageInfo());
????????????GlobalConfig?globalConfig?=?config.getGlobalConfig();
????????????objectMap.put("author",?globalConfig.getAuthor());
????????????objectMap.put("idType",?globalConfig.getIdType()?==?null???null?:?globalConfig.getIdType().toString());
????????????objectMap.put("logicDeleteFieldName",?config.getStrategyConfig().getLogicDeleteFieldName());
????????????objectMap.put("versionFieldName",?config.getStrategyConfig().getVersionFieldName());
????????????objectMap.put("activeRecord",?globalConfig.isActiveRecord());
????????????objectMap.put("kotlin",?globalConfig.isKotlin());
????????????objectMap.put("swagger2",?globalConfig.isSwagger2());
????????????objectMap.put("date",?new?SimpleDateFormat("yyyy-MM-dd").format(new?Date()));
????????????objectMap.put("table",?tableInfo);
????????????objectMap.put("enableCache",?globalConfig.isEnableCache());
????????????objectMap.put("baseResultMap",?globalConfig.isBaseResultMap());
????????????objectMap.put("baseColumnList",?globalConfig.isBaseColumnList());
????????????objectMap.put("entity",?tableInfo.getEntityName());
????????????objectMap.put("entitySerialVersionUID",?config.getStrategyConfig().isEntitySerialVersionUID());
????????????objectMap.put("entityColumnConstant",?config.getStrategyConfig().isEntityColumnConstant());
????????????objectMap.put("entityBuilderModel",?config.getStrategyConfig().isEntityBuilderModel());
????????????objectMap.put("chainModel",?config.getStrategyConfig().isChainModel());
????????????objectMap.put("entityLombokModel",?config.getStrategyConfig().isEntityLombokModel());
????????????objectMap.put("entityBooleanColumnRemoveIsPrefix",?config.getStrategyConfig().isEntityBooleanColumnRemoveIsPrefix());
????????????objectMap.put("superEntityClass",?getSuperClassName(config.getSuperEntityClass()));
????????????objectMap.put("superMapperClassPackage",?config.getSuperMapperClass());
????????????objectMap.put("superMapperClass",?getSuperClassName(config.getSuperMapperClass()));
????????????objectMap.put("superServiceClassPackage",?config.getSuperServiceClass());
????????????objectMap.put("superServiceClass",?getSuperClassName(config.getSuperServiceClass()));
????????????objectMap.put("superServiceImplClassPackage",?config.getSuperServiceImplClass());
????????????objectMap.put("superServiceImplClass",?getSuperClassName(config.getSuperServiceImplClass()));
????????????objectMap.put("superControllerClassPackage",?verifyClassPacket(config.getSuperControllerClass()));
????????????objectMap.put("superControllerClass",?getSuperClassName(config.getSuperControllerClass()));
????????????return?Objects.isNull(config.getInjectionConfig())???objectMap?:?config.getInjectionConfig().prepareObjectMap(objectMap);
????????}
}
CRUD操作
MyBatis-Plus的強(qiáng)大之處不止在于它的代碼生成功能,還在于它提供了豐富的CRUD方法,讓我們實(shí)現(xiàn)單表CRUD幾乎不用手寫SQL實(shí)現(xiàn)!
- 我們之前生成的
PmsBrandMapper接口由于繼承了BaseMapper接口,直接擁有了各種CRUD方法;
/**
?*?
?*?品牌表?Mapper?接口
?*?
?*
?*?@author?macro
?*?@since?2020-08-20
?*/
public?interface?PmsBrandMapper?extends?BaseMapper<PmsBrand>?{
}
- 我們來看下
BaseMapper中的方法,是不是基本可以滿足我們的日常所需了;

- 我們之前生成的
PmsBrandService接口由于繼承了IService接口,也擁有了各種CRUD方法;
/**
?*?
?*?品牌表?服務(wù)類
?*?
?*
?*?@author?macro
?*?@since?2020-08-20
?*/
public?interface?PmsBrandService?extends?IService<PmsBrand>?{
}
- 可以看下比
BaseMapper中的更加豐富;

- 有了這些
IService和BaseMapper中提供的這些方法,我們單表查詢就幾乎不用手寫SQL實(shí)現(xiàn)了,使用MyBatis-Plus實(shí)現(xiàn)以前PmsBrandController的方法更輕松了!
/**
?*?
?*?品牌表?前端控制器
?*?
?*
?*?@author?macro
?*?@since?2020-08-20
?*/
@Api(tags?=?"PmsBrandController",?description?=?"商品品牌管理")
@RestController
@RequestMapping("/brand")
public?class?PmsBrandController?{
????private?static?final?Logger?LOGGER?=?LoggerFactory.getLogger(PmsBrandController.class);
????@Autowired
????private?PmsBrandService?brandService;
????@ApiOperation("獲取所有品牌列表")
????@RequestMapping(value?=?"/listAll",?method?=?RequestMethod.GET)
????@ResponseBody
????public?CommonResult>?getBrandList()?{
????????return?CommonResult.success(brandService.list());
????}
????@ApiOperation("添加品牌")
????@RequestMapping(value?=?"/create",?method?=?RequestMethod.POST)
????@ResponseBody
????public?CommonResult?createBrand(@RequestBody?PmsBrand?pmsBrand)?{
????????CommonResult?commonResult;
????????boolean?result?=?brandService.save(pmsBrand);
????????if?(result)?{
????????????commonResult?=?CommonResult.success(pmsBrand);
????????????LOGGER.debug("createBrand?success:{}",?pmsBrand);
????????}?else?{
????????????commonResult?=?CommonResult.failed("操作失敗");
????????????LOGGER.debug("createBrand?failed:{}",?pmsBrand);
????????}
????????return?commonResult;
????}
????@ApiOperation("更新指定id品牌信息")
????@RequestMapping(value?=?"/update",?method?=?RequestMethod.POST)
????@ResponseBody
????public?CommonResult?updateBrand(@RequestBody?PmsBrand?pmsBrand)?{
????????CommonResult?commonResult;
????????boolean?result?=?brandService.updateById(pmsBrand);
????????if?(result)?{
????????????commonResult?=?CommonResult.success(pmsBrand);
????????????LOGGER.debug("updateBrand?success:{}",?pmsBrand);
????????}?else?{
????????????commonResult?=?CommonResult.failed("操作失敗");
????????????LOGGER.debug("updateBrand?failed:{}",?pmsBrand);
????????}
????????return?commonResult;
????}
????@ApiOperation("刪除指定id的品牌")
????@RequestMapping(value?=?"/delete/{id}",?method?=?RequestMethod.GET)
????@ResponseBody
????public?CommonResult?deleteBrand(@PathVariable("id")?Long?id)?{
????????boolean?result?=?brandService.removeById(id);
????????if?(result)?{
????????????LOGGER.debug("deleteBrand?success?:id={}",?id);
????????????return?CommonResult.success(null);
????????}?else?{
????????????LOGGER.debug("deleteBrand?failed?:id={}",?id);
????????????return?CommonResult.failed("操作失敗");
????????}
????}
????@ApiOperation("分頁查詢品牌列表")
????@RequestMapping(value?=?"/list",?method?=?RequestMethod.GET)
????@ResponseBody
????public?CommonResult>?listBrand(@RequestParam(value?=?"pageNum",?defaultValue?=?"1")
????????????????????????????????????????????????????????@ApiParam("頁碼")?Integer?pageNum,
????????????????????????????????????????????????????????@RequestParam(value?=?"pageSize",?defaultValue?=?"3")
????????????????????????????????????????????????????????@ApiParam("每頁數(shù)量")?Integer?pageSize)?{
????????Page?page?=?new?Page<>(pageNum,?pageSize);
????????Page?pageResult?=?brandService.page(page);
????????return?CommonResult.success(CommonPage.restPage(pageResult));
????}
????@ApiOperation("獲取指定id的品牌詳情")
????@RequestMapping(value?=?"/{id}",?method?=?RequestMethod.GET)
????@ResponseBody
????public?CommonResult?brand(@PathVariable("id")?Long?id)? {
????????return?CommonResult.success(brandService.getById(id));
????}
}
項(xiàng)目源碼地址
https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-plus
