从今天开始一步一步练习mybatis插件开发。
第一步是解析mapper文件,可以实现xml中按照id属性查找resultMap或者sql片段,鼠标点击跳转。
Dom操作Xml的基本只是可以参考之前的文章:IDEA插件开发(二)DOM操作XML文件
本文直接以mybatis的mapper文件解析为例,说明Dom操作的常用写法。
使用Dom解析xml文件,首先需要按照xml包含的标签元素(DTD)编写对应的Java实体。
例如一个典型的mapper xml文件包含的标签元素为:
转化为一个Java实体:
可以看到,每个xml中想解析的标签,都要写一个get方法去标示。
这里解释几个常用的注解:
SubTag
@SubTag
标注最多出现一次的子标签,对应的get方法返回单个实体。
例如resultMap
标签中,最多有一个constructor
子标签,那么需要写为:
SubTagList
@SubTagList
标注可以出现多次的子标签集合,对应的get方法返回实体列表。
例如一个mapper
标签中,可以有多个resultMap
子标签,那么需要写为:
SubTagsList
@SubTagsList
标注的方法,返回多个集合的全集,对应的方法返回实体列表。
通常用于返回一个标签中的多个子标签集合。
例如,我们要定义一个方法,返回mapper中出现的所有增删改查子标签集合,那么可以写为:
NameValue
@NameValue
的作用:使用这个注解标注Element
中的一个方法,这个方法只能返回String
或者GenericValue
,在使用ElementPresentationManager#getElementName(Object)
获取当前Element
的名字标示时,返回这个方法的返回值,或者是直接的String
,或者是GenericValue
,那么返回GenericValue
的getStringValue
方法结果。
Attribute
@Attribute
标注的方法,在xml中是以标签属性的形式出现的,而非标签。例如mapper中的namespace属性。
那么对应的方法为:
上面给出的Mapper
接口继承了DomElement
接口,这样就成为了一个可以DOM被解析的xml元素。Mapper
接口中出现的实体也都是需要自己定义的,如ResultMap
,Insert
等。
下面开始写单个实体,对mapper中的子元素进行解析。
一个包含id属性的基础标签
很多标签都包含id
属性,例如resultMap
,insert
等,那么我们先定义一个基本标签:
动态查询语句元素
mapper中允许使用where
,set
,if
等动态查询语句拼接查询条件,那么可以抽取出来一个动态查询语句元素:
其中Trim
,Where
等子标签又可以包含动态查询语句,因此,可以写为类似递归的形式:
其他子标签与Where
类似,不多写。
其中Include
标签要特殊一些,根据mybatis的规则,该标签用来引用sql代码片段。
所谓sql代码片段,是一段可复用的动态查询语句集合,因此我们先定义Sql
实体:
可以看到它继承了DynamicQueryableDomElement
和IdDomElement
,代表着它包含了动态查询语句,带有id
属性。
下面就可以定义include
实体了:
注意返回值泛型参数为Sql
实体,这样在xml编写时,IDEA可以自动提供代码补全、鼠标点击跳转引用等功能。
带有请求参数的元素
增删改查元素都可以配置请求参数,包含parameterType
指定一个实体类,那么我们抽取一个元素:
其中parameterType
的返回值泛型类型为PsiClass
,这样能映射到一个具体的实体类上。
增删改查标签
有了ParameteredDynamicQueryableDomElement
,就可以在此基础上继续丰富,定义增删改查标签。
Insert
|
|
Delete
|
|
Update
|
|
Select
|
|
其中返回resultType
好理解,指定一个实体类,而mybatis规定select标签是可以返回resultMap
的。
这里提现在ResultMapAttributeDomElement
:
接下来重点介绍这个相对复杂的resultMap
。
resultMap
resultMap
元素有很多子元素和一个值得讨论的结构。
constructor
- 用于在实例化类时,注入结果到构造方法中idArg
- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg
- 将被注入到构造方法的一个普通结果
id
– 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result
– 注入到字段或JavaBean
属性的普通结果association
– 一个复杂类型的关联;许多结果将包装成这种类型- 嵌套结果映射 – associations are resultMaps themselves, or can refer to one
collection
– 一个复杂类型的集合- 嵌套结果映射 – collections are resultMaps themselves, or can refer to one
discriminator
– 使用结果值来决定使用哪个resultMap
case
– 基于某些值的结果映射- 嵌套结果映射 – 一个 case 也是一个映射它本身的结果,因此可以包含很多相 同的元素,或者它可以参照一个外部的 resultMap。
首先,将resultMap
中的基础元素抽取出来,他们可能直接出现在resultMap
标签中,也可能出现在association
等子标签中:
其中id
和result
子标签功能类似,用来做数据库字段与实体字段映射,包含property
属性,那么我们将property
属性抽取出来:
然后让id
和result
去继承它:
而association
和collection
子标签功能会多一些,内部还可能包含一个嵌套的resultMap
,因此会继承多一些,当然,它们会包含自己独有的属性:
属性转换器PropertyConverter
注:属性转换器涉及到 PSI Reference 的知识,在下一篇文章中做介绍。
重新把property
属性请回来,因为我们要讲它的转换器:
首先说目的,加这个转换器,是为了让resultMap
中的property
属性具有鼠标点击跳转操作,即为属性值创建引用到实体上。
假如我们有这样一个User
实体:
我们可能会有如下resultMap
:
按照mybatis规定,除了可以映射基本类型和简单类型,还可以映射带引用嵌套层级的属性,如son.id
。
首先创建个MapperUtils
,写一个方法,用来获取当前property
所属标签对应哪个实体类:
接下来还需要一个工具类JavaUtils
,用来从类中寻找成员变量(非static、非final)的:
有了工具类,就可以写解析器了,将xml属性解析到实体类字段:
外部调用resolve
方法,首先对一级属性进行解析,如果包含了嵌套引用,那么需要逐层解析。
有了解析器,就可以创建一个PsiReference
引用,顾名思义,就是创建一个Psi元素的引用:
上面创建的ContextPsiFieldReference
引用需要传入index
变量,用来指出引用的层级。
例如xml中属性值为user.name
,那么如果是user
这一层的引用,index
为1,如果是name
这一层的引用,index
为2。
接下来创建一个ReferenceSetBase
,用来解析嵌套引用。
继承ReferenceSetBase
并重写createReference
方法,使用我们上面创建的ContextPsiFieldReference
。基类ReferenceSetBase
中包含着对嵌套引用的解析操作:
有了引用,接下来需要放到转换器中,与字段绑定:
最后再看一次使用方式:
这样,再DOM解析时,会使用该converter,而converter实现了CustomReferenceConverter
,可以创建引用,就能实现引用绑定了。
参考:
PSI References
References and Resolve
Reference Contributor