扩展mybatis generator(已同时发布到我的CSDN博客上)

前段时间在优化部门的codegen项目的时候,要将jdbc全部替换成mybatis去执行,有一些个性化的需求单纯的mybatis generator不能满足,于是特意研究了下mybatis,解决在不改造源码的情况下去另类的”扩展“mybatis generator,由于扩展实际上是根据mybatis的套路去进行扩展,所以这里先在第一段介绍一下mybatis-spring的执行原理,第二段会放出例子表明如何进行扩展

一.mybatis-spring执行原理

(1)扫描basePackage,用于将mapper接口扫描成MapperFactoryBean注册到spring

mybatis-spring里面,我们通过MapperScannerConfigurer设置basePackage路径,确定要扫描的Mapper接口,实际上当我们配置了这个basePackage之后,mybatis会扫描这个路径下的所有Mapper接口,并为每个Mapper接口初始化成一个MapperFactoryBean对象,在执行的时候,会通过这个MapperFactoryBean对象的getObject()方法为每个Mapper接口生成一个proxy对象,通过jdk的反射完成,下面来探究一下源码。


从第一张图片可以看到,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在正常的bean注册完之后,可以进一步做一些自定义的bean操作,我们可以看到第二张图哪里,MapperScannerConfigurer会执行一个scanner.scan方法去读取basePackage下面的mapper接口,scanner里面会调用doScan方法去扫描basePackage下面的Mapper接口成MapperFactoryBean并注册到spring容器里面。


在doScan方法里面,首先会调用findCandidateComponents()方法去将basePackage下面的Mapper类扫面成一个BeanDefinition的集合,然后对这些beanDefinition进行解析成BeanDefinitionHolder,最后通过注册到spring容器里面。获取到这些BeanDefinition之后,再去调用processBeanDefinitions方法将beanDefinition设置beanClass为MapperFactoryBean,在第一次需要使用这个bean的时候spring就会根据beanClass通过反射将对应的bean对象生成出来保存在map里面。

Paste_Image.png
Paste_Image.png
(2)扫描mapperLocation,扫描xml将xml里面节点跟namespace对应的mapper结合起来

SqlSessionFactoryBean会在spring初始化的时候调用它的afterPropertiesSet方法,然后再里面调用buildSqlSessionFactory方法,扫描mapperLocation路径下面的xml,根据namespace去找到对应的mapper接口,并调用bindMapperForNamespace()方法将xml和对应的mapper绑定

绑定实际上是通过反射的Class.forName方法根据namespace找到对应的class对象,根据这个class对象创建成MapperProxyFactory对象保存在knownMappers这个map里面。

值得注意的是,mybatis在parsePendingStatements这个方法里面会将每个xml里面的节点(select,delete,update,insert)封装成一个MappedStatement对象,最后保存在一个mappedStatements的map里面(保存的key是获取到xml的namespace+节点id)在执行的时候,mybatis会根据namesapce+id的方式去这个map里面找对应的MappedStatement对象,然后再去执行。

在parseStatementNode方法里面,会解析每个xml节点,最后调用一个builderAssitant.addMappedStatement方法去生成一个MappedStatement对象

builderAssitant.addMappedStatement方法会调用configuration.addMappedStatement方法将创建好的MappedStatement对象put进去mappedStatements的map里面


在put进这个map的时候,mybatis是根据namespace+id(selectByPrimaryKey)作为key来put进去mapper,这样在以后代理类执行的时候就是根据mapper的全路径+方法名就可以找到对应的mappedStatement对象

Paste_Image.png
(3)mybatis执行

mybatis在执行的时候,首先通过MapperFactory.getObject()方法去调用getMapper方法,getMapper会根据type(即根据namespace反射生成的class对象)去knowMapper里面找到对应的MapperProxyFactory对象,然后通过mapperProxyFactory.newInstance(sqlSession)方法为每个mapper生成一个MapperProxy代理类(通过java的jdk代理),然后再通过这个代理类去执行mybatis




MapperProxy类执行的时候,会首先调用invoke方法,除了是Object类的方法之外,其他的都会调用cachedMapperMethod这个方法去获取缓存在methodCache里面的MapperMethod,当在map里面获取不到这个对象时,通过new MapperMethod方法去重新put进去这个map。在创建MapperMethod对象的时候,会调用一个new SqlCommand()方法,我们可以看到经常遇见到的"invalid bound statement"错误也是在这里抛出,statementName实际上就是nameSpace+方法名字(也就是xml里面节点的id),通过这个在mappedStatement的map里面去获取到对应的MappedStatement对象,然后再根据这个MappedStatement对象去执行。





二.扩展mybatis generator

既然我们已经知道mybatis的执行原理,那么去扩展mybatis generator就简单了,因为mybatis是通过namespace去绑定xml和对应的Mapper接口,那么在需要个性化需求的时候,我们可以将一些基础不变的方法放到BaseMapper里面,然后用个性化的CustomMapper去继承那个BaseMapper,然后将基础的xml放到一个base.xml,个性化的xml放到custom.xml,只要两个xml的namespace都是对应于CustomMapper,那么mybatis在初始化的时候就都会将xx.xx.CustomMapper.xx这样作为一个key,对应的MappedStatement对象保存到map里面,在执行的时候就能按照正常的mybatis执行方式去执行sql。

按照这个结构,我们可以将mybatis generator的生成在baseMapper和base.Xml里面,在将个性化的写在CustomMapper和Custom.Xml里面,但是最终我们要使用的时候只需要CustomMapper就可以使用全部的方法。

例子:
基础的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;

/**
 * 对应表名:菜单 个性化处理
 */
public interface CustomMapper extends BaseMapper{

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容