ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IntelliJ IDEA配置MyBatis Mapper.xml模板:告别重复手写骨架代码

IntelliJ IDEA配置MyBatis Mapper.xml模板:告别重复手写骨架代码 说实话在Spring Boot项目里用MyBatis最让我上火的不是写SQL本身而是每次新建Mapper.xml时那堆不得不重复的骨架代码。DOCTYPE声明、namespace、resultMap、Base_Column_List每次都手敲敲完还得检查包名有没有拼错一个字母错了项目启动直接抛BindingException。团队里十个人提交的Mapper.xml格式五花八门代码评审一半精力花在纠正换行和缩进上。这些问题的根源很简单我们一直在靠人工手搓一个本该由工具负责的文件结构。这篇文章就解决一件事在IntelliJ IDEA里配置一套MyBatis映射文件模板让新建Mapper.xml时自动生成标准、干净、可以直接开写的骨架。不管你是刚入门MyBatis的新手还是被重复劳动折磨的资深开发这套方案半小时内就能落地。配置完之后你大概率会想把之前那些手写的文件全删了重新生成一遍——至少我是这么干的。1. 为什么你需要一套映射文件模板1.1 新建Mapper.xml时最烦人的几件事先把我这些年实际见过的场景列一下看你能不能对上号namespace拼错Mapper接口、Service、Controller全部检查一遍都找不到问题结果发现是复制粘贴时namespace少了一个字母Spring容器启动直接报org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。头部信息不统一有人写MyBatis 3.0的DTD有人写4.0有人干脆不写?xml version1.0 encodingUTF-8?IDEA解析XML时一会儿快一会儿慢。从旧文件复制改造这个最常见。复制上一个Mapper.xml结果里面带着上一张表的字段名、老resultMap、历史遗留的sql片段改起来比新建一个还费劲。这些都是“手搓骨架”带来的副作用。实际上Mapper.xml是有严格格式规律的文件。文件头声明、mapper根元素、namespace、通用resultMap、columnList这些内容在项目层面基本是固定的真正有业务差异的只有文件末尾的具体SQL语句。既然规律是固定的就没理由每次都手动输入。如果你不需要在IDEA里配置这个模板而是想找个现成的生成工具MyBatisX插件也能做到类似效果。但插件生成的代码风格是插件的风格团队规范往往还是得靠自定义模板来落地。这两者的取舍我后面会详细说。1.2 IDEA模板机制到底是怎么一回事IntelliJ IDEA自带的模板机制本质上是一个基于Velocity语法的文本生成引擎。你在模板里写死一段文本用$变量表示将来会变化的部分新建文件时IDEA用当前上下文文件名、包名、日期、用户等替换这些变量生成最终文件内容。这套机制的位置在Settings → Editor → File and Code Templates它分两类File模板创建整个新文件适合我们配置Mapper.xml这种场景。Code模板向已有文件中插入代码片段比如创建类的测试方法。还有一个容易混淆的是Live Templates在Settings → Editor → Live Templates里。它是通过输入缩写词比如输入psvm回车生成main方法触发代码补全的不是在新建文件时生效适用场景完全不同。我们这次要用的就是File模板在IDEA里叫File and Code Templates。1.3 模板能解决哪些实际问题模板落地之后至少能看到三个直接变化第一格式统一。整个团队的Mapper.xml头部、缩进、标签顺序完全一致代码评审没人再纠结“你这个resultMap怎么放在select下面”这种问题。第二错误率下降。namespace不靠手敲实体类路径不靠猜主键类型统一在模板里埋好低级拼写错误基本绝迹。第三启动效率提升。新同事入职第一天就能按照规范产出可运行的Mapper文件不用在老文件里删删改改。顺便说一句很多团队折腾MyBatis缓存、二级缓存、TypeHandler这些属于运行时优化而Mapper.xml模板解决的是开发期的工程规范问题。两者不冲突但先把骨架立好后面的缓存配置才有稳定的载体。2. 动手前必须搞懂的三个关键概念2.1 File and Code Templates 和 Live Templates 的区别我在团队里教这个配置的时候发现很多人把File模板和Live Template混为一谈导致配置完发现“新建文件时没反应”。这里统一说清楚File and Code Templates里的File模板对应File → New菜单。你点击新建时IDEA会根据模板生成一个完整文件模板里的变量会被自动替换生成的文件直接落到项目目录里。这就是我们要用的。File and Code Templates里的Code模板是插入到当前编辑器的代码片段一般用于生成类注释、方法注释这种局部内容。Live Templates完全独立靠手动输入缩写触发适合快速生成方法体、循环、try-catch这类编码过程中频繁出现的小片段。所以如果你要让“新建Mapper.xml”这个动作自动套用规范必须去File模板里配置而不是在Live Templates里找半天的缩写词。2.2 Velocity变量在模板里怎么用IDEA的File模板底层支持Velocity语法但不需要你学完整本Velocity教程只需要掌握几个点系统内置变量是直接可用的比如$DATE当前日期、$TIME当前时间、$YEAR、$MONTH、$DAY、$USER系统用户名、$PROJECT_NAME项目名、$NAME新建文件时输入的文件名不含扩展名、$PACKAGE_NAME当前包路径。这些变量在新建文件时由IDEA自动填充。自定义变量是用${变量名}直接写在模板里的。当IDEA发现模板里有未定义的自定义变量创建文件时会弹出一个输入框要求你为这些变量赋值。这就是我们实现Mapper.xml个性化生成的关键——每次新建时按业务填写表名、实体类路径、主键类型。除此之外Velocity还支持条件判断#if/#else/#end、循环#foreach、引用片段#parse。这些进阶语法在团队需要同时支持MySQL和Oracle时会派上用场我在第6章会具体举例。可以这么理解变量就是IDEA留给你的填空位创建文件时IDEA按你填的内容拼接最终文本。和Java里字符串占位符%s的逻辑类似但能力更强。2.3 一个标准Mapper.xml由哪些结构组成为了把模板写好得先明确标准Mapper.xml的组织结构。从我见过的大量生产项目来看一个合格的Mapper.xml通常包含这几个部分XML声明?xml version1.0 encodingUTF-8?DOCTYPE指向mybatis-3-mapper.dtd根元素mapper namespace...namespace必须等于对应Mapper接口的全限定名resultMap定义数据库字段和实体类属性的映射关系一般命名为BaseResultMapsql idBase_Column_List通用查询列便于所有select复用具体SQL块select、insert、update、delete前四部分是所有Mapper.xml都一样的“格式底座”最后一部分是业务差异点。模板要做的就是把“格式底座”彻底固定住让业务SQL在规范化的框架里自由生长。3. 可直接抄作业一份开箱即用的Mapper模板全代码3.1 模板完整代码下面这份模板我直接在IDEA里跑了两年MySQL场景开箱可用。新建文件时只需要填6个变量就能得到带完整CRUD骨架的Mapper.xml。?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespace${MapperFullPath} resultMap idBaseResultMap type${EntityFullPath} id column${pkColumn} property${pkProperty} jdbcType${pkJdbcType}/ result columncreate_time propertycreateTime jdbcTypeTIMESTAMP/ result columnupdate_time propertyupdateTime jdbcTypeTIMESTAMP/ /resultMap sql idBase_Column_List ${pkColumn}, create_time, update_time /sql !-- 根据主键查询单条记录 -- select idselectByPrimaryKey parameterType${pkParameterType} resultMapBaseResultMap select include refidBase_Column_List/ from ${tableName} where ${pkColumn} #{${pkProperty},jdbcType${pkJdbcType}} /select !-- 插入一条记录主键自动生成 -- insert idinsert parameterType${EntityFullPath} useGeneratedKeystrue keyProperty${pkProperty} insert into ${tableName} trim prefix( suffix) suffixOverrides, if testcreateTime ! nullcreate_time,/if if testupdateTime ! nullupdate_time,/if /trim trim prefixvalues ( suffix) suffixOverrides, if testcreateTime ! null#{createTime,jdbcTypeTIMESTAMP},/if if testupdateTime ! null#{updateTime,jdbcTypeTIMESTAMP},/if /trim /insert !-- 按主键更新非空字段 -- update idupdateByPrimaryKeySelective parameterType${EntityFullPath} update ${tableName} set if testcreateTime ! nullcreate_time #{createTime,jdbcTypeTIMESTAMP},/if if testupdateTime ! nullupdate_time #{updateTime,jdbcTypeTIMESTAMP},/if /set where ${pkColumn} #{${pkProperty},jdbcType${pkJdbcType}} /update !-- 按主键删除 -- delete iddeleteByPrimaryKey parameterType${pkParameterType} delete from ${tableName} where ${pkColumn} #{${pkProperty},jdbcType${pkJdbcType}} /delete /mapper这份模板把create_time和update_time两个通用审计字段预置进去了如果你的表没有这两个字段直接在生成后删掉对应行即可比从头写快得多。3.2 核心变量逐个解析模板里的变量看起来多其实每个都是你创建文件时必须想清楚的信息。整理成表格如下变量名含义填写示例${MapperFullPath}Mapper接口的全限定名com.example.dao.UserMapper${EntityFullPath}实体类全限定名com.example.entity.User${tableName}数据库表名t_user${pkColumn}主键字段名数据库列名id${pkProperty}主键属性名实体类字段名id${pkJdbcType}主键对应的JDBC类型BIGINT${pkParameterType}主键参数类型java.lang.Long为什么要用变量而不是写死因为每个项目成员创建不同的Mapper时这7个值都不一样。把这些作为变量后IDEA在新建文件时会弹一个输入框每个成员按当前业务填一遍生成的XML就是一套完整可用的代码不需要再改任何地方。你可能注意到模板里没有${NAME}因为我建议文件名输入时直接填Mapper接口名比如输入UserMapper生成的XML名称自动就是UserMapper.xml。如果你想让文件名能自由命名可以在模板中引用${NAME}变量来拼接但那样在MyBatis的约定中反而不方便——XML文件名和Mapper接口名保持完全一致是最稳妥的。3.3 在IDEA中一步一步配置配置过程不复杂但有几个细节容易被忽略我按步骤拆开讲。第一步打开设置面板Settings → Editor → File and Code Templates选择Files选项卡。第二步点击左上角的加号新建一个模板。在Name一栏填MyBatis MapperFile extension一栏填xml。这里有个坑Name里不要带.xml后缀否则新建文件时默认文件名会变成“MyBatis Mapper.xml”你还得手动删掉前面的“MyBatis Mapper”字样。第三步把上面代码段的内容粘贴到模板正文区点击OK保存。第四步验证效果。在项目任意包目录下右键选择New → Other → MyBatis Mapper新版IDEA可能直接显示New → MyBatis Mapper输入文件名UserMapper点击回车后IDEA会弹出一个变量输入框。在输入框里填好MapperFullPath、EntityFullPath、tableName等值点击OK。第五步检查生成的文件。确认namespace正确、file头完整、resultMap符合预期。如果你在第四步没有看到变量输入框大概率是模板里某个变量被IDEA提前赋了默认值或者IDEA版本对自定义变量弹框的支持有差异。可以试试把模板中所有自定义变量统一用${}包裹确保没有使用$不带花括号的写法。另外IDEA的模板引擎对新修改的模板有缓存偶尔修改后不生效重启一下IDEA基本都能解决。4. 进阶玩法让模板自动感知实体类4.1 为什么普通模板做不到自动生成resultMap有同学可能会问能不能让模板自动读取实体类的全部字段把resultMap完整生成出来我的答案是File模板本身做不到。原因是IDEA的File模板引擎在生成XML时并没有绑定任何Java类的上下文。它不知道你新建的UserMapper.xml对应哪个实体类工具层面没有这个“关联输入”。模板里能用的内置变量$PACKAGE_NAME、$PROJECT_NAME都只是当前目录和项目的信息拿不到类字段列表。那怎么办两个思路一是用Live Template配合Groovy脚本在编写XML的过程中手动触发字段映射生成二是用MyBatis相关插件从数据库表反向生成。下面详细说。4.2 用Live Template Groovy脚本联动实体类Live Templates的特性是在编辑器中通过缩写触发代码插入而且支持调用groovyScript()函数来执行一段Groovy脚本把脚本返回值插入到代码中。这就意味着你可以选中实体类然后在Mapper.xml里输入缩写脚本会读取类的字段列表自动拼出resultMap标签。具体思路是在Settings → Editor → Live Templates里新建一个模板缩写词可以取rm模板文本设置为resultMap idBaseResultMap type$CLASS_NAME$ $FIELDS$ /resultMap其中$FIELDS$变量需要绑定一个Groovy脚本大致逻辑是获取选中类的所有字段遍历生成result column字段名 property字段名/。脚本的核心部分类似这样def fieldTags _1.getFields().collect { f - result column\${f.name}\ property\${f.name}\/ }.join(\n) return fieldTags_1是传入的当前类对象。实现细节会因为IDEA版本有差异实际运行时可能需要微调。不过说实话Groovy脚本这个玩法适合有原生开发经验、喜欢折腾工具的工程师。如果你只是为了提效不想维护脚本我更推荐直接用插件方案效果更直接。4.3 插件方案 vs 模板方案现在IDEA里常用的MyBatis插件有MyBatisX和Free MyBatis Plugin它们可以连接数据库表一键生成实体类、Mapper接口、Mapper.xml而且自动带完整的resultMap和CRUD方法。我用一个表格把三条路线对比一下方案生成速度规范统一性自定义空间维护成本手写File模板快依赖手动填变量高团队统一很大低Live Template Groovy较快需手动触发较高很大中MyBatisX插件自动生成最快一键生成低插件风格小低我的结论是团队骨架规范用File模板打底这是最稳的。如果你经常要新建大量表对应的Mapper用MyBatisX生成后再把模板风格补齐也行。但注意插件生成的字段顺序和标签风格往往和手工规范不一致团队里如果每个人用插件默认配置生成代码风格又会变得不统一。所以我的建议是优先自定义模板插件作为补充工具而不是替代品。5. 实战中踩过的坑与排查速查表5.1 生成的XML文件没有MyBatis语法提示这是配置模板后最常遇到的问题。你以为生成的是标准的MyBatis映射文件IDEA应该能自动识别并给出标签提示但实际打开发现就是一个普通XML连select标签都没有自动补全。这是因为IDEA默认并不认识MyBatis的Mapper.xml除非它通过Mapper接口与XML文件的关联找到了对应关系。解决办法在Settings → Plugins里安装MyBatisX插件插件能自动识别namespace对应的Java接口并为你提供标签补全、跳转等能力。安装后重启IDEA再次打开生成的XML语法提示就来了。如果你所在的网络环境无法顺畅访问插件市场也可以试试把mybatis-3-mapper.dtd下载到本地在Settings → Languages Frameworks → Schemas and DTDs里配置本地DTD映射。这个方法能解决XML解析校验的问题但跳转和补全体验还是比不上插件。5.2 模板里的美元符号和MyBatis占位符错位MyBatis的动态SQL里#{...}是预编译参数占位符${...}是字符串替换。这里有一个大坑IDEA模板引擎基于Velocity${...}恰好也是它的变量语法。如果你在模板里写了${columnName}想作为MyBatis的动态排序字段IDEA在新建文件时会把它当成模板变量处理要么弹框让你填值要么直接变成空字符串。解决办法其实很简单模板里尽量只写#{...}因为#{在Velocity语法里不构成变量定义IDEA会原样输出。如果确实需要生成包含${...}的SQL片段比如动态排序列我的做法是先不在模板里写等文件生成后再用IDEA的全局替换CtrlR把占位符换成实际的${}拼接。这样做的最直接原因是模板变量和MyBatis变量混在一起会互相干扰没必要为了一次性生成去折腾复杂的转义。5.3 中文乱码、模板不生效、变量不解析这三个问题一起说因为它们经常同时出现。中文乱码模板注释里如果有中文生成的文件打开是乱码几乎是编码没统一。检查Settings → Editor → File Encodings把Global Encoding、Project Encoding、Properties Files三处全部设为UTF-8同时确认模板文件本身是以UTF-8保存的。IDEA默认项目编码是UTF-8但如果团队项目从旧仓库迁移过来工程文件里可能残留GBK编码这个得单独处理。模板不生效表现为改了模板内容新建文件时还是旧模板。File模板会读取内存中的配置IDEA偶尔有缓存修改后不生效可以先用File → Invalidate Caches清理缓存再重启IDEA。如果重启后还不行删掉模板重新添加一次。变量不弹框新建文件时IDEA没有弹变量输入框生成的文件里变量变成空串。常见原因有三个一是变量名写错模板里$变量名和${变量名}混用导致IDEA识别不了二是变量在模板里被#set预赋值了三是IDEA版本对模板变量的支持存在差异。遇到这种情况统一改为${变量名}写法并把模板里所有#set删除重启IDEA后再试。下面给一个速查表方便排查症状可能原因处理方式生成文件无高亮缺少MyBatis插件安装MyBatisX生成文件乱码编码未统一全局编码设为UTF-8模板修改不生效IDEA模板缓存清理缓存、重启变量不弹框变量写法不一致或已赋值统一${}写法、删#setnamespace绑定失败生成后手动改错了确认全限定名和接口包一致6. 从模板到规范把团队实践沉淀下来6.1 整套模板的导出与团队共享个人电脑配置好了只解决你一个人的问题。在真实团队协作中真正有价值的是让所有人用同一套模板。IDEA的File and Code Templates支持导出分享在模板配置页右下角有Export按钮可以把当前所有模板打包导出为一个jar文件。团队成员拿到jar后在配置页选择Import导入即可。更规范的做法是把模板代码提交到Git仓库通过CI或人为维护统一分发。这样模板更新时团队能感知到变更而不是默默替换。这里有一个细节IDEA的模板配置文件实际存放在用户目录下的IDEA配置文件夹里比如Windows上的C:\Users\用户名\AppData\Roaming\JetBrains\IDE版本\options\fileTemplates。如果不想用Export/Import把这个文件夹里的文件放到版本库里也可以但直接操作配置目录容易碰到权限和路径问题还是建议用IDEA自带的导入导出功能。6.2 模板之外的映射文件规范化建议模板只是把骨架立起来了真正让项目保持整洁的还有几条不成文的约定。我在团队里一直推这些规则效果不错namespace必须和Mapper接口全限定名一致这是MyBatis绑定的硬性要求不能为了写短路径而偷懒。resultMap统一命名BaseResultMap所有select都用它。避免出现getUserMap、userInfoMap这类随意命名。通用查询列统一用sql idBase_Column_List禁止在多个select里重复手写字段列表。我见过很多项目因为字段更新改了一个select忘了另一个导致线上查询列对不上实体类排查起来极其费劲。insert和update必须使用动态SQL标签trim或set禁止写全字段插入/全字段更新。全字段更新会把null写进数据库覆盖已有值这是生产事故的高发区。主键生成策略在模板里直接固化。MySQL用useGeneratedKeystrueOracle用序列不要让每个开发自己决定。6.3 针对MySQL和Oracle的模板细节差异如果团队里同时有MySQL和Oracle两套数据库环境一套模板走天下会有问题。关键差异在这几个地方对比项MySQLOracle主键生成AUTO_INCREMENTinsert里不写主键序列触发器insert里要写SEQ.NEXTVAL分页写法LIMIT #{offset}, #{size}ROWNUM或FETCH FIRST ? ROWS ONLYjdbcType宽松不写也能跑严格jdbcType类型不对影响性能时间字段DATETIME/TIMESTAMPDATE精确到秒插入需TO_DATE我的建议是除非业务确实需要跨数据库否则模板里直接写死一种数据库的写法不要做过度抽象。真要抽象也可以用Velocity的#if做条件判断在模板里设置一个${dbType}变量创建文件时选择mysql或oracle让模板输出不同的主键片段。举个例子针对Oracle的insert主键片段可以这样insert idinsert parameterType${EntityFullPath} selectKey keyProperty${pkProperty} orderBEFORE resultType${pkJdbcType} select ${tableName}_seq.nextval from dual /selectKey insert into ${tableName} ... /insert这样模板里定义${dbType}变量配合#if让创建者选择比维护两套模板省事得多。把模板放在项目公共工程里跑了三年我最大的感受是它改变的不仅是新建文件的效率。每次新建时弹出的变量输入框其实是在逼你养成一个习惯写代码之前先想清楚表名、实体、主键类型、Mapper接口路径。这个思考过程比省下的五分钟更重要。希望这套方案也能把你的团队从复制粘贴的泥潭里拉出来把精力真正花到SQL优化和业务逻辑上。
返回列表