前段時(shí)間在優(yōu)化部門的codegen項(xiàng)目的時(shí)候沉帮,要將jdbc全部替換成mybatis去執(zhí)行,有一些個(gè)性化的需求單純的mybatis generator不能滿足贫堰,于是特意研究了下mybatis穆壕,解決在不改造源碼的情況下去另類的”擴(kuò)展“mybatis generator,由于擴(kuò)展實(shí)際上是根據(jù)mybatis的套路去進(jìn)行擴(kuò)展其屏,所以這里先在第一段介紹一下mybatis-spring的執(zhí)行原理喇勋,第二段會(huì)放出例子表明如何進(jìn)行擴(kuò)展
一.mybatis-spring執(zhí)行原理
(1)掃描basePackage,用于將mapper接口掃描成MapperFactoryBean注冊(cè)到spring
mybatis-spring里面偎行,我們通過(guò)MapperScannerConfigurer設(shè)置basePackage路徑涂籽,確定要掃描的Mapper接口,實(shí)際上當(dāng)我們配置了這個(gè)basePackage之后显押,mybatis會(huì)掃描這個(gè)路徑下的所有Mapper接口袜漩,并為每個(gè)Mapper接口初始化成一個(gè)MapperFactoryBean對(duì)象,在執(zhí)行的時(shí)候,會(huì)通過(guò)這個(gè)MapperFactoryBean對(duì)象的getObject()方法為每個(gè)Mapper接口生成一個(gè)proxy對(duì)象,通過(guò)jdk的反射完成,下面來(lái)探究一下源碼荚守。
從第一張圖片可以看到,MapperScannerConfigurer實(shí)現(xiàn)了BeanDefinitionRegistryPostProcessor接口练般,在正常的bean注冊(cè)完之后矗漾,可以進(jìn)一步做一些自定義的bean操作,我們可以看到第二張圖哪里踢俄,MapperScannerConfigurer會(huì)執(zhí)行一個(gè)scanner.scan方法去讀取basePackage下面的mapper接口缩功,scanner里面會(huì)調(diào)用doScan方法去掃描basePackage下面的Mapper接口成MapperFactoryBean并注冊(cè)到spring容器里面。
在doScan方法里面都办,首先會(huì)調(diào)用findCandidateComponents()方法去將basePackage下面的Mapper類掃面成一個(gè)BeanDefinition的集合嫡锌,然后對(duì)這些beanDefinition進(jìn)行解析成BeanDefinitionHolder,最后通過(guò)注冊(cè)到spring容器里面琳钉。獲取到這些BeanDefinition之后势木,再去調(diào)用processBeanDefinitions方法將beanDefinition設(shè)置beanClass為MapperFactoryBean,在第一次需要使用這個(gè)bean的時(shí)候spring就會(huì)根據(jù)beanClass通過(guò)反射將對(duì)應(yīng)的bean對(duì)象生成出來(lái)保存在map里面歌懒。
(2)掃描mapperLocation啦桌,掃描xml將xml里面節(jié)點(diǎn)跟namespace對(duì)應(yīng)的mapper結(jié)合起來(lái)
SqlSessionFactoryBean會(huì)在spring初始化的時(shí)候調(diào)用它的afterPropertiesSet方法,然后再里面調(diào)用buildSqlSessionFactory方法及皂,掃描mapperLocation路徑下面的xml甫男,根據(jù)namespace去找到對(duì)應(yīng)的mapper接口,并調(diào)用bindMapperForNamespace()方法將xml和對(duì)應(yīng)的mapper綁定
綁定實(shí)際上是通過(guò)反射的Class.forName方法根據(jù)namespace找到對(duì)應(yīng)的class對(duì)象验烧,根據(jù)這個(gè)class對(duì)象創(chuàng)建成MapperProxyFactory對(duì)象保存在knownMappers這個(gè)map里面板驳。
值得注意的是,mybatis在parsePendingStatements這個(gè)方法里面會(huì)將每個(gè)xml里面的節(jié)點(diǎn)(select,delete,update,insert)封裝成一個(gè)MappedStatement對(duì)象碍拆,最后保存在一個(gè)mappedStatements的map里面(保存的key是獲取到xml的namespace+節(jié)點(diǎn)id)在執(zhí)行的時(shí)候,mybatis會(huì)根據(jù)namesapce+id的方式去這個(gè)map里面找對(duì)應(yīng)的MappedStatement對(duì)象若治,然后再去執(zhí)行。
在parseStatementNode方法里面感混,會(huì)解析每個(gè)xml節(jié)點(diǎn)端幼,最后調(diào)用一個(gè)builderAssitant.addMappedStatement方法去生成一個(gè)MappedStatement對(duì)象
builderAssitant.addMappedStatement方法會(huì)調(diào)用configuration.addMappedStatement方法將創(chuàng)建好的MappedStatement對(duì)象put進(jìn)去mappedStatements的map里面
在put進(jìn)這個(gè)map的時(shí)候,mybatis是根據(jù)namespace+id(selectByPrimaryKey)作為key來(lái)put進(jìn)去mapper弧满,這樣在以后代理類執(zhí)行的時(shí)候就是根據(jù)mapper的全路徑+方法名就可以找到對(duì)應(yīng)的mappedStatement對(duì)象
(3)mybatis執(zhí)行
mybatis在執(zhí)行的時(shí)候婆跑,首先通過(guò)MapperFactory.getObject()方法去調(diào)用getMapper方法,getMapper會(huì)根據(jù)type(即根據(jù)namespace反射生成的class對(duì)象)去knowMapper里面找到對(duì)應(yīng)的MapperProxyFactory對(duì)象庭呜,然后通過(guò)mapperProxyFactory.newInstance(sqlSession)方法為每個(gè)mapper生成一個(gè)MapperProxy代理類(通過(guò)java的jdk代理)滑进,然后再通過(guò)這個(gè)代理類去執(zhí)行mybatis
MapperProxy類執(zhí)行的時(shí)候摹迷,會(huì)首先調(diào)用invoke方法,除了是Object類的方法之外郊供,其他的都會(huì)調(diào)用cachedMapperMethod這個(gè)方法去獲取緩存在methodCache里面的MapperMethod,當(dāng)在map里面獲取不到這個(gè)對(duì)象時(shí)近哟,通過(guò)new MapperMethod方法去重新put進(jìn)去這個(gè)map驮审。在創(chuàng)建MapperMethod對(duì)象的時(shí)候,會(huì)調(diào)用一個(gè)new SqlCommand()方法吉执,我們可以看到經(jīng)常遇見到的"invalid bound statement"錯(cuò)誤也是在這里拋出疯淫,statementName實(shí)際上就是nameSpace+方法名字(也就是xml里面節(jié)點(diǎn)的id),通過(guò)這個(gè)在mappedStatement的map里面去獲取到對(duì)應(yīng)的MappedStatement對(duì)象戳玫,然后再根據(jù)這個(gè)MappedStatement對(duì)象去執(zhí)行熙掺。
二.擴(kuò)展mybatis generator
既然我們已經(jīng)知道m(xù)ybatis的執(zhí)行原理,那么去擴(kuò)展mybatis generator就簡(jiǎn)單了咕宿,因?yàn)閙ybatis是通過(guò)namespace去綁定xml和對(duì)應(yīng)的Mapper接口币绩,那么在需要個(gè)性化需求的時(shí)候,我們可以將一些基礎(chǔ)不變的方法放到BaseMapper里面府阀,然后用個(gè)性化的CustomMapper去繼承那個(gè)BaseMapper缆镣,然后將基礎(chǔ)的xml放到一個(gè)base.xml,個(gè)性化的xml放到custom.xml,只要兩個(gè)xml的namespace都是對(duì)應(yīng)于CustomMapper试浙,那么mybatis在初始化的時(shí)候就都會(huì)將xx.xx.CustomMapper.xx這樣作為一個(gè)key董瞻,對(duì)應(yīng)的MappedStatement對(duì)象保存到map里面,在執(zhí)行的時(shí)候就能按照正常的mybatis執(zhí)行方式去執(zhí)行sql田巴。
按照這個(gè)結(jié)構(gòu)钠糊,我們可以將mybatis generator的生成在baseMapper和base.Xml里面,在將個(gè)性化的寫在CustomMapper和Custom.Xml里面壹哺,但是最終我們要使用的時(shí)候只需要CustomMapper就可以使用全部的方法抄伍。
例子:
基礎(chǔ)的baseXml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yue.dao.custom.mybatis.CustomMapper">
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
<!--
WARNING - @mbg.generated
This element is automatically generated by MyBatis Generator, do not modify.
-->
delete from demo_table
where `id` = #{id,jdbcType=BIGINT}
</delete>
</mapper>
自定義的customXml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yue.dao.custom.mybatis.CustomMapper">
<update id="updateByPrimaryKeySelective" parameterType="com.yue.domain.DemoTable">
update demo_table
<set>
<if test="dbNo != null">
`db_no` = #{dbNo},
</if>
<if test="globalId != null">
`global_id` = #{globalId},
</if>
<if test="updatedBy != null">
`updated_by` = #{updatedBy},
</if>
</set>
<where>
`id` = #{id,jdbcType=BIGINT}
<if test="versionNumber != null">
and `version_number` = #{versionNumber}
</if>
</where>
</update>
</mapper>
BaseMapper代碼:
public interface BaseMapper {
int deleteByPrimaryKey(Long id);
/**
* This method was generated by MyBatis Generator. This method corresponds to the database table auth_menu
*
* @mbg.generated
*/
int updateByPrimaryKey(AuthMenu record);
}
CustomMapper代碼:
package com.yue.dao.custom.mybatis;
import com.vip.fcs.app.ar.intfc.dao.mybatis.base.BaseMapper;
/**
* 對(duì)應(yīng)表名:菜單 個(gè)性化處理
*/
public interface CustomMapper extends BaseMapper{
}