扩展mybatis generator

本文介绍了mybatis-spring的执行原理,包括扫描basePackage将Mapper接口注册到Spring,扫描mapperLocation结合namespace绑定Mapper。随后详细探讨了mybatis的执行过程。基于这些理解,文章展示了如何在不修改源码的情况下,通过扩展mybatis generator满足个性化需求,通过BaseMapper和CustomMapper的继承关系,以及base.xml和custom.xml的namespace匹配,实现定制化SQL的执行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前段时间在优化部门的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{

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值